Raspberry Pi OS Bullseye 版本发布时弃用的 Picamera 以新面貌回归了,那就是 Picamera2 库。
下面介绍使用树莓派摄像头模块,用 Picamera2 库和 TensorFlow Lite 实现实时对象检测。
安装 Picamera2 库
因为目前 Picamera2 库还处于预览版,因此暂时只能通过 GitHub 编译安装。
$ sudo apt update $ sudo apt install -y libboost-dev $ sudo apt install -y libgnutls28-dev openssl libtiff5-dev $ sudo apt install -y qtbase5-dev libqt5core5a libqt5gui5 libqt5widgets5 $ sudo apt install -y meson $ sudo pip3 install pyyaml ply $ sudo pip3 install --upgrade meson $ sudo apt install -y libglib2.0-dev libgstreamer-plugins-base1.0-dev $ git clone --branch picamera2 https://github.com/raspberrypi/libcamera.git $ cd libcamera $ meson build --buildtype=release -Dpipelines=raspberrypi -Dipas=raspberrypi -Dv4l2=true -Dgstreamer=enabled -Dtest=false -Dlc-compliance=disabled -Dcam=disabled -Dqcam=enabled -Ddocumentation=disabled -Dpycamera=enabled $ ninja -C build $ sudo ninja -C build install $ cd ~ $ git clone https://github.com/tomba/kmsxx.git $ cd kmsxx $ git submodule update --init $ sudo apt install -y libfmt-dev libdrm-dev $ meson build $ ninja -C build $ cd ~ $ sudo pip3 install pyopengl $ sudo apt install python3-pyqt5 $ git clone https://git@github.com:raspberrypi/picamera2.git $ sudo pip3 install opencv-python==4.4.0.46 $ sudo apt install -y libatlas-base-dev $ sudo pip3 install numpy --upgrade $ cd ~ $ git clone https://github.com/RaspberryPiFoundation/python-v4l2.git
要跑起来还需要设置 PYTHONPATH 运行环境。比如你需要将下面的内容添加到 .bashrc 文件中。
export PYTHONPATH=/home/pi/picamera2:/home/pi/libcamera/build/src/py:/home/pi/kmsxx/build/py:/home/pi/python-v4l2
安装 TensorFlow Lite
由于我们将通过 Python 代码进行推理而不是训练,因此我们可以安装轻量级 TensorFlow Lite 运行时库以及我们需要的其他一些东西:
$ sudo apt install build-essentials $ sudo apt install git $ sudo apt install libatlas-base-dev $ sudo apt install python3-pip $ pip3 install tflite-runtime $ pip3 install opencv-python==4.4.0.46 $ pip3 install pillow $ pip3 install numpy
开始使用 TensorFlow Lite
安装完所有东西之后,下面我们构建一个演示程序:寻找图像中的苹果和香蕉。
代码会启用摄像头,并将采集到的图像不断传给 TensorFlow 的图像缓冲区。TensorFlow 随之在图像上进行对象检测。如果检测到任何对象,将用矩形框进行标注。
import tflite_runtime.interpreter as tflite import sys import os import argparse import cv2 import numpy as np from PIL import Image from PIL import ImageFont, ImageDraw from qt_gl_preview import * from picamera2 import * normalSize = (640, 480) lowresSize = (320, 240) rectangles = [] def ReadLabelFile(file_path): with open(file_path, 'r') as f: lines = f.readlines() ret = {} for line in lines: pair = line.strip().split(maxsplit=1) ret[int(pair[0])] = pair[1].strip() return ret def DrawRectangles(request): stream = request.picam2.stream_map["main"] fb = request.request.buffers[stream] with fb.mmap(0) as b: im = np.array(b, copy=False, dtype=np.uint8).reshape((normalSize[1],normalSize[0], 4)) for rect in rectangles: print(rect) rect_start = (int(rect[0]*2) - 5, int(rect[1]*2) - 5) rect_end = (int(rect[2]*2) + 5, int(rect[3]*2) + 5) cv2.rectangle(im, rect_start, rect_end, (0,255,0,0)) if len(rect) == 5: text = rect[4] font = cv2.FONT_HERSHEY_SIMPLEX cv2.putText(im, text, (int(rect[0]*2) + 10, int(rect[1]*2) + 10), font, 1, (255,255,255),2,cv2.LINE_AA) del im def InferenceTensorFlow( image, model, output, label=None): global rectangles if label: labels = ReadLabelFile(label) else: labels = None interpreter = tflite.Interpreter(model_path=model, num_threads=4) interpreter.allocate_tensors() input_details = interpreter.get_input_details() output_details = interpreter.get_output_details() height = input_details[0]['shape'][1] width = input_details[0]['shape'][2] floating_model = False if input_details[0]['dtype'] == np.float32: floating_model = True rgb = cv2.cvtColor(image,cv2.COLOR_GRAY2RGB) initial_h, initial_w, channels = rgb.shape picture = cv2.resize(rgb, (width, height)) input_data = np.expand_dims(picture, axis=0) if floating_model: input_data = (np.float32(input_data) - 127.5) / 127.5 interpreter.set_tensor(input_details[0]['index'], input_data) interpreter.invoke() detected_boxes = interpreter.get_tensor(output_details[0]['index']) detected_classes = interpreter.get_tensor(output_details[1]['index']) detected_scores = interpreter.get_tensor(output_details[2]['index']) num_boxes = interpreter.get_tensor(output_details[3]['index']) rectangles = [] for i in range(int(num_boxes)): top, left, bottom, right = detected_boxes[0][i] classId = int(detected_classes[0][i]) score = detected_scores[0][i] if score > 0.5: xmin = left * initial_w ymin = bottom * initial_h xmax = right * initial_w ymax = top * initial_h box = [xmin, ymin, xmax, ymax] rectangles.append(box) if labels: print(labels[classId], 'score = ', score) rectangles[-1].append(labels[classId]) else: print ('score = ', score) def main(): parser = argparse.ArgumentParser() parser.add_argument('--model', help='Path of the detection model.', required=True) parser.add_argument('--label', help='Path of the labels file.') parser.add_argument('--output', help='File path of the output image.') args = parser.parse_args() if ( args.output): output_file = args.output else: output_file = 'out.jpg' if ( args.label ): label_file = args.label else: label_file = None picam2 = Picamera2() preview = QtGlPreview(picam2) config = picam2.preview_configuration(main={"size": normalSize}, lores={"size": lowresSize, "format": "YUV420"}) picam2.configure(config) stride = picam2.stream_configuration("lores")["stride"] picam2.request_callback = DrawRectangles picam2.start() while True: buffer = picam2.capture_buffer("lores") grey = buffer[:stride*lowresSize[1]].reshape((lowresSize[1], stride)) result = InferenceTensorFlow( grey, args.model, output_file, label_file ) if __name__ == '__main__': main()
via