A MIPI CSI camera that starts cleanly and then takes OpenCV down with Segmentation fault (core dumped) on the very first read is almost never a broken camera. The pipeline string asked for a width, height and framerate combination that appears nowhere in the sensor's advertised mode list, the camera stack quietly picked a mode of its own, and the first buffer arrived in a shape the sink never agreed to. Work through the log, the gst-launch-1.0 test and the caps rewrite below, and you will have BGR frames in NumPy instead of a core dump.
The crash signature already names the cause
Read the console output as three facts rather than noise: the camera started, the stack printed every mode it can run, and it selected one that you never asked for. The request that produced this log asked for 640 x 480 at framerate 30/1, untyped. Here is the relevant part of the output:
GST_ARGUS: Available Sensor modes :
GST_ARGUS: 2592 x 1944 FR = 29.999999 fps Duration = 33333334
GST_ARGUS: 2592 x 1458 FR = 29.999999 fps Duration = 33333334
GST_ARGUS: 1280 x 720 FR = 120.000005 fps Duration = 8333333
GST_ARGUS: Running with following settings:
GST_ARGUS: Camera index = 0 Camera mode = 2
GST_ARGUS: Output Stream W = 1280 H = 720 Frame Rate = 120.000005
CONSUMER: Producer has connected; continuing.
Segmentation fault (core dumped)
No row in that list offers 640 x 480 at 30 fps, so the stack chose row 2, the third entry, which is 1280 x 720 at 120 fps, and started it. The camera did its job. The negotiation did not end in a shape the rest of the chain could deliver, and the first buffer into the sink ended the process.
The odd-looking frame rates are printed from frame durations, and they matter for your caps. One second divided by 33,333,334 ns gives 29.999999 fps, which is the same 30/1 you write in caps, and 1 s / 8,333,333 ns = 120.000005 fps. Write framerate=(fraction)30/1 and framerate=(fraction)120/1, not the decimal values.
Every capture interface works from a table like this one. The USB version of the same logic, where a camera refuses sizes it never advertised, is covered in USB camera stuck at 4K: how sensor resolution modes work.
Likely causes, most probable first
- The caps name no mode row. Width, height and framerate must match one printed row together. This causes most of these crashes.
- Right size, wrong framerate. In the list above, 1280 x 720 exists only at 120 fps; a 30/1 request at that size is as dead as 640 x 480 was.
- Missing type casts. The running examples write width=(int), height=(int), format=(string) and framerate=(fraction); untyped values are where silent misparses live.
- The crash happens only at exit. The frames were fine and the teardown order was not; that is a different fix, covered further down.
- The camera never negotiated at all. Sensor driver, device-tree overlay or carrier wiring; dmesg decides this one, not the pipeline string.
Treat causes 1 and 2 as the default assumption and work down the list only after the gst-launch-1.0 test clears them.
Prove the camera outside OpenCV first
Two commands separate a caps problem from a camera problem, and they take a minute. Run them before touching the Python side again.
- Start with no caps at all:
gst-launch-1.0 nvarguscamerasrc ! nvoverlaysink. If live video does not appear, the fault sits in the driver, the overlay or the wiring; begin at CSI camera carrier board compatibility: what to verify and camera loads the wrong sensor driver: read dmesg, fix the overlay. - Add your exact caps and a passive end point:
gst-launch-1.0 nvarguscamerasrc ! 'video/x-raw(memory:NVMM), width=(int)1920, height=(int)1080, format=(string)NV12, framerate=(fraction)30/1' ! fakesink. On the release behind the log above, a 1920 x 1080 request failed there withFailed to create CaptureSession— the source saying, plainly, that no mode matches. - Swap in a printed row:
gst-launch-1.0 nvarguscamerasrc ! 'video/x-raw(memory:NVMM), width=(int)2592, height=(int)1944, format=(string)NV12, framerate=(fraction)30/1' ! fakesink. This one runs without error. - Only now go back to
cv2.VideoCapturewith the identical string.
If step 3 runs clean and the identical string still segfaults inside OpenCV, the caps are exonerated; skip to the signature-specific fixes further down.
The fix: a listed row at the source, scaling at the converter
Name one printed row in the caps right after the source, and ask for your working resolution in a second caps element after the converter. The converter scales; the sensor mode never changes.
import cv2
gst_str = (
'nvarguscamerasrc ! '
'video/x-raw(memory:NVMM), width=(int)2592, height=(int)1944, '
'format=(string)NV12, framerate=(fraction)30/1 ! '
'nvvidconv ! '
'video/x-raw, width=(int)640, height=(int)480, format=(string)BGRx ! '
'videoconvert ! '
'video/x-raw, format=(string)BGR ! '
'appsink'
)
cap = cv2.VideoCapture(gst_str, cv2.CAP_GSTREAMER)
assert cap.isOpened(), 'pipeline failed to open'
success, image = cap.read()
print(success, image.shape) # True (480, 640, 3)
- The source caps copy row 0 exactly: 2592 x 1944, NV12, 30/1, with the
memory:NVMMfeature that keeps frames where the converter works on them. - nvvidconv scales 2592 x 1944 down to 640 x 480 — both 4:3, so nothing distorts — and produces BGRx for the CPU-side chain.
- videoconvert finishes the BGRx to BGR conversion, and appsink hands NumPy-ready frames to cap.read().
- The print at the end is the test: success comes back True and image.shape reads (480, 640, 3).
For the fast row, only the source caps change: width=(int)1280, height=(int)720, format=(string)NV12, framerate=(fraction)120/1.
What the pipeline moves: two worked examples
Where to scale is arithmetic, not taste. Frame size is width times height times bytes per pixel, and the rate multiplies by frame rate and by eight for bits:
At the source, row 0 moves 2592 × 1944 × 1.5 = 7,558,272 bytes ≈ 7.2 MiB per frame. At 30 fps that is 7,558,272 × 30 = 226,748,160 B/s ≈ 226.7 MB/s, or 226,748,160 × 8 = 1,813,985,280 bit/s ≈ 1.81 Gbps. All of it stays on the board's memory side; none of it should reach your Python loop.
Your process receives the small stream: 640 × 480 × 3 = 921,600 bytes ≈ 0.9 MiB per frame, and 921,600 × 30 = 27,648,000 B/s ≈ 27.6 MB/s. That is 7,558,272 / 921,600 ≈ 8.2 times less data per frame than pulling full frames and resizing them in NumPy. The 120 fps row works out at 1280 × 720 × 1.5 = 1,382,400 bytes ≈ 1.3 MiB per frame, so 1,382,400 × 120 × 8 = 1,327,104,000 bit/s ≈ 1.33 Gbps — the busiest mode in the list.
The rule that falls out: match the sensor mode at the source, scale at the converter, and carry the smallest format you actually need into Python.
If a listed mode still crashes: sort by signature
When the caps copy a printed row and gst-launch-1.0 runs clean, the remaining faults split by where and when they fire:
- Crash only when the script exits. The frames were fine and the teardown was not. Call cap.release() before the object is collected, because GStreamer elements must reach the NULL state before their last reference drops. Camera crash when stopping acquisition works this signature in detail.
- No video and plugin warnings instead of a crash. A different family: elements missing from the build. The GStreamer missing plugins diagnosis covers it.
-
isOpened() returns False. Check
cv2.getBuildInformation()for GStreamer support first, because a build without that backend cannot run any string. The same backend-availability failure on another interface is described in why OpenCV VideoCapture cannot open a GigE Vision camera. - The host rejects the mode you asked for. Some boards cap what their CSI receiver accepts; MIPI CSI camera resolution limits: verify the host lists what to check there.
- Runs for minutes, then dies. A thermal or link-integrity signature, not a caps one; CSI cameras that stop after a working period describes that failure on another CSI host.
When you compare runs or ask for help, include the first line of /etc/nv_tegra_release: the way a mismatched request fails has shifted between stack releases, so notes from different releases do not mix.
When the CSI link is not worth another day
Sometimes the right engineering call is to stop debugging the receiver chain. A USB3 or GigE board camera replaces the CSI receiver, the sensor driver and the device-tree overlay with a link the host already knows how to enumerate, and OpenCV then talks to a standard device. The trade-offs between the two connections are laid out in CSI versus USB camera connections on an industrial controller.
If you re-specify the camera itself, the board camera evaluation checklist settles resolution, shutter and interface in one pass.
Two modules from the board-level cameras range show the pattern: the LS-BCM-0003 streams 120 fps over USB3 from a 3.1 MP global-shutter sensor, and the MV-CB016-10GM-M-W carries a 1.6 MP global-shutter sensor over GigE on an M12 mount.
- Sensor format
- 1/1.8"
- Lens mount
- C-mount
- Sensor format
- 1/2.9"
- Lens mount
- M12
Both sit in the camera finder alongside the rest of the range, with filters for interface, resolution and shutter.
Prevention: the pattern that survives
- Paste the printed mode rows into your code as named constants, with the row index in a comment. The log is the contract.
- Keep the casts on every caps value: width=(int), height=(int), format=(string), framerate=(fraction).
- Guard the open and every read: check isOpened() once, then the boolean from each cap.read() in the loop.
- Measure the delivered rate instead of trusting the caps; video FPS returns zero in OpenCV shows the timing loop that still works when the metadata reports zero.
- Record
head -1 /etc/nv_tegra_releasein your run notes, because mismatch behavior shifts between stack releases.
For a new build, the camera configurator walks the chain from module to mount; the systems topic page collects the other capture-path guides, and the contact page reaches the applications team when a fault is still open.
Questions
Why does gst-launch-1.0 run cleanly but OpenCV segfaults on the same caps?
gst-launch-1.0 proves only that the source and the end point negotiate. OpenCV adds the appsink hand-off into your process, and that is where a shape nobody agreed on finally bites. Keep the caps identical on both sides, with the (int), (fraction) and (string) casts, and check cv2.getBuildInformation() lists GStreamer support. If gst-launch-1.0 itself fails, fix the caps there before touching Python.
Can I capture 1920 x 1080 from a sensor whose full mode is 2592 x 1944?
Only if the mode list prints that row; the example list does not, so the request fails. The 2592 x 1458 row is the exact 16:9 crop of the same sensor, since 2592 divided by 1458 equals 1.78, so request it with framerate=(fraction)30/1 and scale to 1920 x 1080 at the converter. The full 4:3 row scales cleanly to sizes such as 640 x 480.
Why did the log say Camera mode = 2 when the pipeline asked for 640 x 480?
640 x 480 at 30 fps matches no row, so the stack picked its own nearest row; the log shows it selected 1280 x 720 at 120 fps. You do not control that pick. Name a listed row yourself in the source caps and do any resizing at the converter, and the selection stops being a surprise.
Does the same mode-matching fix apply to USB3 or GigE cameras?
No. USB3 and GigE cameras negotiate formats through their own drivers and never print a mode list like this one; read the enumerated formats from the camera's SDK or viewer instead. When the format is wrong there, the capture usually refuses to open or delivers wrong-sized buffers rather than crashing. The typed caps and scale-at-the-converter habits still pay off once the format is right.

