From c3874cf6a4b81867ab43d19350e649294eea030f Mon Sep 17 00:00:00 2001 From: David Plowman Date: Wed, 1 Jul 2026 16:38:22 +0100 Subject: [PATCH] Fix some NV12 and NV21 format related issues The array reshaping for NV12 was incorrect. Some extra support is added for NV21, although there still appear to be some libcamera-internal problems with it, so it isn't recommended for now. Signed-off-by: David Plowman --- picamera2/encoders/encoder.py | 9 ++++++++- picamera2/request.py | 8 +++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/picamera2/encoders/encoder.py b/picamera2/encoders/encoder.py index 9f77fa5e..45919d91 100644 --- a/picamera2/encoders/encoder.py +++ b/picamera2/encoders/encoder.py @@ -289,7 +289,14 @@ def send_streams(self, output): def _send_streams(self, output): # Send video stream information to the output. - FORMAT_TABLE = {"YUV420": "yuv420p", "BGR888": "rgb24", "RGB888": "bgr24", "XBGR8888": "rgba", "XRGB8888": "bgra"} + FORMAT_TABLE = { + "YUV420": "yuv420p", + "BGR888": "rgb24", + "RGB888": "bgr24", + "XBGR8888": "rgba", + "XRGB8888": "bgra", + "NV12": "nv12", + } pix_fmt = FORMAT_TABLE.get(self._format) rate = Fraction(1000000, self._framerate) if pix_fmt is None and output.needs_add_stream: diff --git a/picamera2/request.py b/picamera2/request.py index e0bb610d..85e8f567 100644 --- a/picamera2/request.py +++ b/picamera2/request.py @@ -244,11 +244,13 @@ def save( V = reshaped[2 * height + height // 2 :, : width // 2] output_bytes = simplejpeg.encode_jpeg_yuv_planes(Y, U, V, quality) Y = reshaped = U = V = None - elif format == 'NV12': + elif format in ('NV12', 'NV21'): width, height = self.config[name]['size'] Y = m.array[:height, :width] U = np.ascontiguousarray(m.array[height : height + height // 2, 0 : 2 * width : 2]) V = np.ascontiguousarray(m.array[height : height + height // 2, 1 : 2 * width : 2]) + if format == 'NV21': + U, V = V, U output_bytes = simplejpeg.encode_jpeg_yuv_planes(Y, U, V, quality) Y = U = V = None else: @@ -333,10 +335,10 @@ def _make_array_shared(self, buffer: np.ndarray, config: Dict[str, Any]) -> np.n # These dimensions seem a bit strange, but mean that # cv2.cvtColor(image, cv2.COLOR_YUV2BGR_YUYV) will convert directly to RGB. image = array.reshape(h, stride // 2, 2) - elif fmt == "NV12": + elif fmt in ("NV12", "NV21"): # The UV rows should be as long as the Y rows, so we can slice off any # padding. - image = array.reshape((h * 3 // 2, stride))[:w] + image = array.reshape((h * 3 // 2, stride))[:, :w] elif fmt == "MJPEG": image = np.array(Image.open(io.BytesIO(array))) # type: ignore elif formats.is_raw(fmt):