Raspberry Pi AI Camera#
The Raspberry Pi AI Camera pairs Sony’s IMX500 image sensor with an
on-chip neural-network accelerator. The sensor runs inference
concurrently with video capture — at no host-CPU cost — by loading a
compiled network file (.rpk) onto the sensor at startup.
QVideo supports the AI Camera through
QIMXCamera, a subclass of
QPicamera that adds one extra signal:
QPicamera QIMXCamera
────────────────────────── ──────────────────────────────────────
newFrame (ndarray) newFrame (ndarray) ← inherited
all picamera2 controls all picamera2 controls ← inherited
newOutput (list|None) ← new
newOutput is emitted once
per frame. Its payload is either a list of
numpy.ndarray objects (the raw tensor outputs produced by the
sensor’s AI accelerator) or None when the sensor has not yet
produced results (typically the first few frames after start-up).
Installation#
Install picamera2 with IMX500 support on Raspberry Pi OS:
pip install "picamera2[imx500]"
Then obtain a pre-compiled network (see Model format below).
Basic usage#
from QVideo.cameras.Picamera import QIMXCamera, QIMXSource
MODEL = (
'/usr/share/imx500-models/'
'imx500_network_nanodet_plus_416x416.rpk'
)
source = QIMXSource(model=MODEL)
source.source.newOutput.connect(my_widget.setOutputs)
source.start()
Because QIMXCamera is a full
QPicamera subclass, all standard
controls (exposure, gain, white balance, etc.) remain accessible
through QPicameraTree as usual.
Writing an inference consumer#
Connect newOutput to a slot
that interprets the tensor list for the specific model loaded. The slot
runs in the Qt main thread, so processing should be lightweight. For
heavy post-processing, offload to a QThread or follow the same
drop-frame strategy used by
AsyncVideoFilter.
The example below reads scores and bounding boxes from a NanoDet-Plus model:
import numpy as np
from qtpy import QtCore, QtWidgets
class DetectionWidget(QtWidgets.QGroupBox):
'''Display object-detection results from an IMX500 AI Camera.'''
def __init__(self, parent=None) -> None:
super().__init__('Detections', parent)
self.setCheckable(True)
self.setChecked(True)
self._source = None
@property
def source(self):
return self._source
@source.setter
def source(self, src) -> None:
if self._source is not None:
self._source.source.newOutput.disconnect(
self._setOutputs)
self._source = src
if src is not None:
src.source.newOutput.connect(self._setOutputs)
@QtCore.Slot(object)
def _setOutputs(self, outputs) -> None:
if outputs is None or not self.isChecked():
return
scores = outputs[0] # model-specific layout
boxes = outputs[1]
# ... update graphics items here
The tensor layout (which index holds scores, boxes, class IDs, etc.) is
model-specific. Refer to the model’s documentation or the picamera2
examples for the correct interpretation.
Wiring into QCamcorder#
Subclass QCamcorder and add
DetectionWidget alongside the camera tree:
from QVideo import QCamcorder
from QVideo.cameras.Picamera import QIMXSource
MODEL = (
'/usr/share/imx500-models/'
'imx500_network_nanodet_plus_416x416.rpk'
)
class AIDemo(QCamcorder):
def __init__(self, source=None, parent=None) -> None:
source = source or QIMXSource(model=MODEL)
super().__init__(source, parent)
self._detections = DetectionWidget(self)
self._detections.source = source
self._layout.addWidget(self._detections)
if __name__ == '__main__': # pragma: no cover
import pyqtgraph as pg
from qtpy import QtGui, QtWidgets
app = pg.mkQApp('AIDemo')
widget = AIDemo()
QtWidgets.QShortcut(
QtGui.QKeySequence('Ctrl+Q'), widget
).activated.connect(app.quit)
widget.show()
pg.exec()
Model format#
The IMX500 requires a packed-network file (.rpk) compiled for the
sensor’s AI accelerator. Sources:
Pre-built models — installed to
/usr/share/imx500-models/on Raspberry Pi OS via theimx500-allapt package.Raspberry Pi model zoo — available via the
picamera2GitHub repository examples.Custom models — convert any supported ONNX or TFLite model with the IMX500 model-conversion tools provided by Sony.
Note
QIMXCamera is intentionally
excluded from the automatic camera-discovery backend registry
because it requires a model path at construction time. Create it
directly rather than via Camera().