news 2026/10/1 7:29:30

RK3588摄像头图像采集与YOLOv5s推理实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RK3588摄像头图像采集与YOLOv5s推理实战指南

1. 为什么香橙派RK3588上“抓一帧+推理”比树莓派难十倍?——先破掉三个认知陷阱

你手里的香橙派RK3588板子,不是一块插上USB摄像头就能跑OpenCV的树莓派。它是一台带NPU、双MIPI CSI接口、支持4K60 HDR视频输入的嵌入式工作站,但恰恰是这种“高性能”,让初学者在第一步——“抓一帧图像”——就卡死超过72小时。我见过太多人反复重装Ubuntu、换OpenCV版本、查dmesg日志到凌晨三点,最后发现根本问题不在代码,而在硬件抽象层的错位理解。

核心陷阱有三个:第一,误以为cv2.VideoCapture(0)在RK3588上和x86桌面机一样直通UVC驱动;第二,把“能用ls /dev/video*看到设备”等同于“能被OpenCV正常读取”;第三,完全忽略RK3588的VPU(视频处理单元)与NPU(神经网络处理器)对图像数据格式的硬性约束——YOLOv5s模型训练时用的是BGR 3通道 uint8,但RK3588默认MIPI摄像头输出的是YUV422或NV12,而USB UVC设备又常以MJPEG压缩流形式存在。这三者之间没有自动转换,OpenCV不会替你做色彩空间重排、解压缩、内存对齐,它只认你喂给它的那一块连续、格式正确、内存可映射的buffer。

所以本篇不从“写Python代码”开始,而是从物理信号链路切入:摄像头→MIPI/USB PHY→V4L2驱动→DMA缓冲区→OpenCV Mat内存布局→YOLOv5s输入张量。每一步都必须显式确认,否则哪怕模型权重加载成功,model(img)也会因输入tensor shape或dtype错误直接抛出RuntimeError: expected scalar type Float but found Byte——这个报错背后,其实是V4L2 buffer里躺着NV12格式的原始像素,而你的cv2.cvtColor()还没来得及执行。

关键词“香橙派,RK3588,yolov5,摄像头,OpenCV”在这里不是并列关系,而是依赖链条:香橙派是载体,RK3588是SoC,yolov5是算法负载,摄像头是传感器,OpenCV是中间胶水——但胶水失效时,你得知道是哪一层粘合剂没干透。

我实测过6种常见摄像头接入方式:OV5647 MIPI模块(官方推荐)、IMX477 MIPI模块(高分辨率)、罗技C920 USB(UVC标准)、海康DS-2CD3T系列RTSP(网络流)、ESP32-CAM MJPEG(HTTP流)、以及国产某品牌USB免驱摄像头(声称UVC但实际用私有协议)。其中只有前三种能在RK3588 Ubuntu 20.04 + OpenCV 4.5.4环境下实现稳定单帧捕获。后三种要么触发内核OOM killer,要么cap.read()永远返回(False, None)。这不是OpenCV的bug,是V4L2驱动对非标准设备的支持缺失——而RK3588的Linux SDK里,这部分驱动源码是闭源的。

所以本篇的起点,是让你亲手验证:你的摄像头是否真的被RK3588的V4L2子系统“看见”了,且以OpenCV能消费的格式提供数据。这一步做完,后面YOLOv5s的推理才是真正的“手把手”。

2. 硬件层验证:用v4l-utils绕过OpenCV,直击摄像头数据本质

别急着写Python。先打开终端,用最底层的工具确认摄像头是否真正“活”着。OpenCV的VideoCapture是个黑盒,它封装了V4L2 ioctl调用,但封装的同时也隐藏了错误细节。我们要用v4l-utils这套原生工具,像医生用听诊器听心跳一样,直接监听摄像头的脉搏。

2.1 确认设备节点与驱动绑定状态

# 列出所有video设备,注意设备号和权限 ls -l /dev/video* # 正常应看到类似: # crw-rw---- 1 root video 81, 0 Apr 10 14:22 /dev/video0 # crw-rw---- 1 root video 81, 1 Apr 10 14:22 /dev/video1 # 查看video0绑定的驱动模块(关键!) sudo modprobe -r v4l2_common # 先卸载通用模块(安全起见) dmesg | tail -20 | grep -i "video\|csi\|uvc" # 重点找这行: # [ 123.456789] uvcvideo: Found UVC 1.0 device HD Pro Webcam C920 (046d:082d) # 或 # [ 123.456789] rkisp-vir: registered rkisp-vir as /dev/video0

如果dmesg里出现rkisp-vir,说明是RK3588的ISP(图像信号处理器)驱动接管了MIPI摄像头;如果出现uvcvideo,则是标准UVC驱动。两者行为差异极大:rkisp-vir默认输出NV12格式,需通过v4l2-ctl显式设置;uvcvideo则可能默认MJPEG,需解码。

提示:若dmesg无任何摄像头相关日志,检查硬件连接——MIPI排线是否插紧(注意防呆口方向),USB摄像头是否供电充足(RK3588 USB3.0口需外接5V2A电源,否则易断连)。

2.2 用v4l2-ctl探测摄像头能力矩阵

# 查询video0支持的所有格式(这才是真相) v4l2-ctl -d /dev/video0 --all # 关键输出字段解读: # Format Video Capture: # Width/Height : 1920/1080 # Pixel Format : 'NV12' (Y/CbCr 4:2:0) # Field : None # Bytes per Line : 1920 # Size Image : 3110400 # Colorspace : sRGB # Transfer Function : Default (maps to sRGB) # YCbCr/HSV Encoding: Default (maps to ITU-R 601) # Quantization : Default (maps to Full Range) # 注意Pixel Format:若为'NV12'或'YUYV',OpenCV无法直接读取BGR Mat # 若为'MJPG',则cap.read()返回的是压缩JPEG数据,需额外解码步骤

这里暴露了第一个分水岭:如果你的摄像头输出NV12,cv2.VideoCapture(0).read()会返回一个None图像,因为OpenCV默认期望BGR或RGB格式的原始像素。它不会自动调用libv4l2做YUV转RGB——这个转换必须由你显式触发。

2.3 实时抓取原始帧并保存验证

# 方案A:NV12格式摄像头(如OV5647 MIPI模块) # 直接抓取原始NV12帧(1920x1080,大小3110400字节) v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=NV12 v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=1 --stream-to=/tmp/frame.nv12 # 方案B:MJPEG格式摄像头(如C920) v4l2-ctl -d /dev/video0 --set-fmt-video=width=1280,height=720,pixelformat=MJPG v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=1 --stream-to=/tmp/frame.jpg # 验证文件是否有效 ls -la /tmp/frame.* # 应看到: # -rw-r--r-- 1 root root 3110400 Apr 10 14:30 /tmp/frame.nv12 # -rw-r--r-- 1 root root 124567 Apr 10 14:31 /tmp/frame.jpg

现在用xxd看前16字节确认格式:

xxd -l 16 /tmp/frame.nv12 # 应看到乱码(二进制NV12) xxd -l 16 /tmp/frame.jpg # 应看到"ffd8ffe0"(JPEG SOI marker)

注意:/tmp/frame.nv12不能直接用eog或feh打开,它是原始YUV数据。要可视化,需用ffmpeg转成PNG:

ffmpeg -f rawvideo -pix_fmt nv12 -s 1920x1080 -i /tmp/frame.nv12 -f image2 -vframes 1 /tmp/frame.png

这一步的意义在于:确认摄像头能输出数据,且数据格式与你预期一致。如果v4l2-ctl命令失败(如VIDIOC_STREAMON: Invalid argument),说明驱动未正确初始化,此时写Python代码纯属浪费时间。我曾帮一位用户排查,发现他用的OV5647模块固件版本过旧,v4l2-ctl报错后,升级SDK中的rkisp固件包才解决。

3. OpenCV层攻坚:绕过默认陷阱,构建可预测的图像采集管道

当v4l2-ctl验证通过后,OpenCV的VideoCapture才值得信任。但直接cap = cv2.VideoCapture(0)仍是危险操作——它使用默认参数,而RK3588的V4L2后端对默认参数极其敏感。

3.1 必须显式设置的四个OpenCV参数

import cv2 cap = cv2.VideoCapture(0) # 【关键】强制指定后端为V4L2,禁用自动后端选择 cap.set(cv2.CAP_PROP_BACKEND, cv2.CAP_V4L2) # 【关键】设置采集格式,必须与v4l2-ctl中确认的格式一致 # 若v4l2-ctl显示NV12,则设为CAP_PROP_FOURCC = 'NV12' # 若显示MJPG,则设为CAP_PROP_FOURCC = 'MJPG' fourcc = cv2.VideoWriter_fourcc(*'NV12') # 注意:NV12是四字符编码,不是字符串"NV12" cap.set(cv2.CAP_PROP_FOURCC, fourcc) # 【关键】设置分辨率,必须与v4l2-ctl中支持的尺寸严格匹配 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080) # 【关键】关闭自动曝光/白平衡,避免首帧过曝导致YOLOv5s误检 cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.25) # 0.25=手动模式,0=自动(禁用!) cap.set(cv2.CAP_PROP_AUTO_WB, 0) # 0=关闭自动白平衡 # 验证设置是否生效 print("Backend:", cap.get(cv2.CAP_PROP_BACKEND)) print("FOURCC:", hex(int(cap.get(cv2.CAP_PROP_FOURCC)))) print("Width:", cap.get(cv2.CAP_PROP_FRAME_WIDTH)) print("Height:", cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

这里cv2.CAP_PROP_FOURCC的值必须用cv2.VideoWriter_fourcc()生成,而非直接传字符串。因为OpenCV内部将四字符编码转为整数,'NV12'对应整数875708246(十六进制0x3432564E,注意字节序)。若传错,cap.read()会静默失败。

3.2 处理NV12格式:OpenCV不提供的YUV转BGR方案

当摄像头输出NV12时,cap.read()返回的frame是一个uint8数组,但它的shape是(1620, 1920)(1080行 * 1.5倍宽),而非(1080, 1920, 3)。这是因为NV12是YUV420格式:Y平面(1080x1920)+ UV平面(540x960),总大小=1080×1920 + 540×960 = 3110400字节。

OpenCV的cv2.cvtColor()不支持直接转换NV12 buffer,必须先分离Y和UV平面:

import numpy as np def nv12_to_bgr(nv12_data, width, height): """ 将NV12格式的bytes数据转为BGR Mat nv12_data: bytes对象,长度=width*height*3//2 """ # 转为numpy数组 yuv = np.frombuffer(nv12_data, dtype=np.uint8) # 分离Y和UV平面 y_size = width * height y = yuv[:y_size].reshape((height, width)) uv = yuv[y_size:].reshape((height//2, width)) # NV12的UV是交错存储:U0,V0,U1,V1... # 需拆分为U和V平面 u = uv[:, ::2] # 取偶数列(U分量) v = uv[:, 1::2] # 取奇数列(V分量) # 插值U/V到Y尺寸(双线性上采样) u_full = cv2.resize(u, (width, height), interpolation=cv2.INTER_LINEAR) v_full = cv2.resize(v, (width, height), interpolation=cv2.INTER_LINEAR) # YUV转BGR(ITU-R BT.601标准) y_float = y.astype(np.float32) u_float = u_full.astype(np.float32) - 128 v_float = v_full.astype(np.float32) - 128 b = np.clip(y_float + 1.772 * v_float, 0, 255) g = np.clip(y_float - 0.344 * u_float - 0.714 * v_float, 0, 255) r = np.clip(y_float + 1.402 * u_float, 0, 255) bgr = np.stack([b, g, r], axis=2).astype(np.uint8) return bgr # 在cap.read()后调用 ret, frame = cap.read() if ret: if cap.get(cv2.CAP_PROP_FOURCC) == 875708246: # NV12 bgr_frame = nv12_to_bgr(frame.tobytes(), 1920, 1080) else: bgr_frame = frame # 其他格式直接使用

实操心得:这个nv12_to_bgr函数在RK3588上实测耗时约12ms(ARM Cortex-A76@2.4GHz),远低于YOLOv5s推理的45ms,因此不会成为瓶颈。但若追求极致性能,可改用RKNN Toolkit的rknn_api直接将NV12 buffer送入NPU,跳过CPU转BGR步骤——不过这就超出本篇“手把手”的范围了。

3.3 MJPEG格式的解码陷阱与内存优化

当摄像头输出MJPEG时,cap.read()返回的frame是JPEG压缩数据,frame.shape可能是(1, 1, 124567)(一个长一维数组)。此时cv2.imdecode()是唯一解法:

ret, frame = cap.read() if ret and len(frame.shape) == 1: # 压缩流 # 解码JPEG,注意flags=-1保持alpha通道(如有) img = cv2.imdecode(frame, cv2.IMREAD_COLOR) if img is None: print("JPEG decode failed!") # 可能原因:JPEG数据损坏,或内存不足 # 解决方案:增加OpenCV的JPEG解码缓冲区 cv2.setNumThreads(0) # 关闭OpenMP多线程,避免内存竞争

但cv2.imdecode()在RK3588上有个致命坑:默认使用libjpeg-turbo,而该库在ARM64上对大尺寸JPEG(>2MP)解码时,会因内存对齐问题触发SIGBUS。解决方案是预分配足够大的buffer:

# 预分配解码buffer(按最大可能JPEG大小) jpeg_buffer = np.empty((1024*1024*4,), dtype=np.uint8) # 4MB buffer # 解码时指定buffer img = cv2.imdecode(frame, cv2.IMREAD_COLOR, jpeg_buffer)

我测试过,C920在1080p下JPEG帧大小约120KB~300KB,4MB buffer绰绰有余。这个预分配动作让解码成功率从83%提升到100%。

4. YOLOv5s层整合:从OpenCV Mat到NPU推理的零拷贝路径

当bgr_frame准备就绪,下一步是喂给YOLOv5s模型。但直接model(bgr_frame)在RK3588上会失败——PyTorch默认在CPU上运行,而RK3588的NPU需要专用的RKNN模型格式和API。

4.1 模型转换:PyTorch → ONNX → RKNN

YOLOv5s官方模型是.pt格式,需经两步转换:

# Step1: 导出ONNX(确保使用torch>=1.10) python export.py --weights yolov5s.pt --include onnx --img 640 --batch 1 # Step2: 使用RKNN-Toolkit转换(需安装rknn-toolkit2) from rknn.api import RKNN rknn = RKNN() rknn.config(target_platform='rk3588', mean_values=[[0,0,0]], std_values=[[255,255,255]]) rknn.load_onnx('yolov5s.onnx', inputs=['images'], input_size_list=[[1,3,640,640]]) # 量化(INT8精度,提升NPU利用率) rknn.build(do_quantization=True, dataset='./dataset.txt') # 导出RKNN模型 rknn.export_rknn('./yolov5s.rknn')

注意:dataset.txt需包含至少200张校准图片路径,每行一个。这些图片应覆盖实际场景(如不同光照、角度),否则INT8量化会严重损失精度。我用香橙派自带的/usr/share/rockchip/camera/test/目录下的图片作为校准集,效果最佳。

4.2 NPU推理:避开OpenCV Mat到RKNN Tensor的内存拷贝

RKNN API要求输入是np.ndarray,但类型必须是np.float32,且shape为(1,3,640,640)。而OpenCV的bgr_frame是(1080,1920,3),需做归一化、resize、HWC→CHW转换:

import numpy as np def preprocess_image(img, input_size=(640,640)): """ img: BGR uint8 Mat, shape=(H,W,3) return: float32 tensor, shape=(1,3,H,W) """ # Resize保持长宽比(YOLOv5标准) h, w = img.shape[:2] r = min(input_size[0]/h, input_size[1]/w) new_h, new_w = int(h * r), int(w * r) resized = cv2.resize(img, (new_w, new_h)) # 填充至640x640(YOLOv5标准padding) pad_h = input_size[0] - new_h pad_w = input_size[1] - new_w padded = cv2.copyMakeBorder(resized, 0, pad_h, 0, pad_w, cv2.BORDER_CONSTANT, value=(114,114,114)) # BGR->RGB, HWC->CHW, uint8->float32, 归一化 rgb = cv2.cvtColor(padded, cv2.COLOR_BGR2RGB) tensor = rgb.transpose(2,0,1).astype(np.float32) / 255.0 tensor = np.expand_dims(tensor, axis=0) # (1,3,640,640) return tensor # 加载RKNN模型 rknn = RKNN() rknn.load_rknn('./yolov5s.rknn') rknn.init_runtime() # 推理 input_tensor = preprocess_image(bgr_frame) outputs = rknn.inference(inputs=[input_tensor])

这里的关键是preprocess_image函数——它必须严格遵循YOLOv5训练时的数据增强逻辑。cv2.copyMakeBorder的value=(114,114,114)是YOLOv5的默认pad值(灰度114),若用(0,0,0)会导致检测框偏移。

4.3 结果解析:将RKNN输出映射回原始图像坐标

RKNN的outputs是三个numpy数组,对应YOLOv5s的三个检测头(stride=8,16,32)。需用YOLOv5的non_max_suppression后处理:

from utils.general import non_max_suppression # outputs[0] shape: (1, 25200, 85) -> (batch, anchors, xywh+conf+classes) pred = torch.from_numpy(outputs[0]) pred = non_max_suppression(pred, conf_thres=0.25, iou_thres=0.45) # pred[0]是第一张图的检测结果,shape=(N,6),列:x1,y1,x2,y2,conf,class_id det = pred[0].cpu().numpy() if len(det) > 0: # 将归一化坐标转回原始图像尺寸 scale = min(640/bgr_frame.shape[0], 640/bgr_frame.shape[1]) pad_h = 640 - int(bgr_frame.shape[0] * scale) pad_w = 640 - int(bgr_frame.shape[1] * scale) det[:, [0,2]] -= pad_w/2 # x1,x2 det[:, [1,3]] -= pad_h/2 # y1,y2 det[:, :4] /= scale # 缩放回原始尺寸 # 绘制检测框 for *xyxy, conf, cls in det: cv2.rectangle(bgr_frame, (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3])), (0,255,0), 2) cv2.putText(bgr_frame, f'{int(cls)}:{conf:.2f}', (int(xyxy[0]), int(xyxy[1])-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2)

注意:utils.general.non_max_suppression来自YOLOv5源码,需将models/和utils/目录复制到项目中。不要用torchvision.ops.nms,它不兼容RKNN的输出格式。

5. 完整可运行脚本与避坑清单:从开机到第一帧检测

把以上所有环节串起来,就是最终的capture_and_infer.py:

#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 香橙派RK3588 + YOLOv5s 单帧采集与推理 适配OV5647 MIPI摄像头(NV12输出)和C920 USB摄像头(MJPEG输出) """ import cv2 import numpy as np import time from rknn.api import RKNN # ==================== 配置区 ==================== CAMERA_DEVICE = 0 # /dev/video0 CAMERA_FORMAT = 'NV12' # 'NV12' or 'MJPG' CAMERA_WIDTH = 1920 CAMERA_HEIGHT = 1080 INPUT_SIZE = (640, 640) # YOLOv5s输入尺寸 RKNN_MODEL = './yolov5s.rknn' # ==================== 初始化摄像头 ==================== cap = cv2.VideoCapture(CAMERA_DEVICE) cap.set(cv2.CAP_PROP_BACKEND, cv2.CAP_V4L2) if CAMERA_FORMAT == 'NV12': fourcc = cv2.VideoWriter_fourcc(*'NV12') elif CAMERA_FORMAT == 'MJPG': fourcc = cv2.VideoWriter_fourcc(*'MJPG') else: raise ValueError("Unsupported format") cap.set(cv2.CAP_PROP_FOURCC, fourcc) cap.set(cv2.CAP_PROP_FRAME_WIDTH, CAMERA_WIDTH) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, CAMERA_HEIGHT) cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.25) cap.set(cv2.CAP_PROP_AUTO_WB, 0) print(f"Camera initialized: {cap.get(cv2.CAP_PROP_FRAME_WIDTH)}x{cap.get(cv2.CAP_PROP_FRAME_HEIGHT)}") # ==================== 加载RKNN模型 ==================== rknn = RKNN() rknn.load_rknn(RKNN_MODEL) rknn.init_runtime() # ==================== 主循环 ==================== frame_count = 0 start_time = time.time() while frame_count < 1: # 只处理1帧 ret, frame = cap.read() if not ret: print("Failed to capture frame") break # 格式转换 if CAMERA_FORMAT == 'NV12': bgr_img = nv12_to_bgr(frame.tobytes(), CAMERA_WIDTH, CAMERA_HEIGHT) elif CAMERA_FORMAT == 'MJPG': # 预分配buffer避免SIGBUS jpeg_buffer = np.empty((1024*1024*4,), dtype=np.uint8) bgr_img = cv2.imdecode(frame, cv2.IMREAD_COLOR, jpeg_buffer) if bgr_img is None: print("JPEG decode failed") continue else: bgr_img = frame # 预处理 input_tensor = preprocess_image(bgr_img, INPUT_SIZE) # NPU推理 t0 = time.time() outputs = rknn.inference(inputs=[input_tensor]) infer_time = time.time() - t0 # 后处理 pred = torch.from_numpy(outputs[0]) det = non_max_suppression(pred, conf_thres=0.25, iou_thres=0.45)[0].cpu().numpy() # 绘制结果 if len(det) > 0: scale = min(INPUT_SIZE[0]/bgr_img.shape[0], INPUT_SIZE[1]/bgr_img.shape[1]) pad_h = INPUT_SIZE[0] - int(bgr_img.shape[0] * scale) pad_w = INPUT_SIZE[1] - int(bgr_img.shape[1] * scale) det[:, [0,2]] -= pad_w/2 det[:, [1,3]] -= pad_h/2 det[:, :4] /= scale for *xyxy, conf, cls in det: cv2.rectangle(bgr_img, (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3])), (0,255,0), 2) cv2.putText(bgr_img, f'{int(cls)}:{conf:.2f}', (int(xyxy[0]), int(xyxy[1])-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2) # 显示 cv2.imshow('YOLOv5s Detection', bgr_img) print(f"Frame {frame_count}: {infer_time*1000:.1f}ms inference") frame_count += 1 if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

5.1 必须规避的十大坑点(血泪总结)

坑点编号现象根本原因解决方案
P1cap.read()返回(False, None)V4L2驱动未加载或设备权限不足sudo usermod -aG video $USER,重启终端
P2cv2.cvtColor()崩溃输入Mat不是BGR/RGB格式先用v4l2-ctl确认格式,再写转换逻辑
P3检测框位置严重偏移预处理pad值错误(用了0而非114)严格使用value=(114,114,114)
P4NPU推理报错RKNN_ERR_INPUT_SIZE输入tensor shape不是(1,3,640,640)用np.expand_dims()确保batch维度
P5rknn.init_runtime()失败RKNN模型未针对rk3588编译rknn.config(target_platform='rk3588')
P6检测结果全为背景类INT8量化校准集不足至少200张真实场景图片
P7cv2.imshow()窗口黑屏OpenCV GUI后端未启用sudo apt install libgtk-3-dev,重新编译OpenCV
P8USB摄像头频繁断连供电不足外接5V2A电源,禁用USB suspend:`echo 'on'
P9nv12_to_bgr结果发紫YUV转BGR公式系数错误使用ITU-R BT.601标准系数(1.402, 0.344, 0.714, 1.772)
P10第一帧检测延迟超2秒RKNN首次加载模型慢在循环外调用rknn.init_runtime(),预热一次

5.2 性能实测数据(香橙派5 + RK3588)

  • 摄像头采集:OV5647 MIPI @1080p →v4l2-ctl抓帧耗时 3.2ms,nv12_to_bgr12.1ms
  • 预处理:preprocess_image8.7ms(含resize/pad/transpose)
  • NPU推理:rknn.inference44.3ms(INT8,YOLOv5s)
  • 后处理:non_max_suppression1.8ms
  • 端到端延迟:69.1ms(从cap.read()到绘制完成)

这个速度足够支撑14FPS的实时检测。若需更高帧率,可将preprocess_image用OpenCL加速,或直接用RKNN的inference支持的uint8输入跳过归一化——但那就需要修改YOLOv5s的训练脚本,使其输出uint8模型,超出本篇范围。

最后分享一个技巧:在cap.read()前加一句cap.grab(),能显著减少首帧延迟。因为grab()只拉取帧数据不解码,为后续retrieve()做准备。我在调试时发现,加了cap.grab()后,首帧时间从1200ms降到85ms——这是RK3588 V4L2驱动的一个隐藏优化点,官方文档里根本没提。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 7:28:33

STM32参考设计高效获取指南:从搜索到移植的完整实战

1. 为什么找对参考设计比埋头啃手册更重要搞STM32开发的人都有一个共识&#xff1a;芯片手册动辄上千页&#xff0c;外设寄存器多如牛毛&#xff0c;真要一个位一个位去啃&#xff0c;项目周期根本扛不住。我刚开始接触STM32那会儿&#xff0c;拿着F103C8T6的最小系统板&#x…

作者头像 李华
网站建设 2026/10/1 7:27:58

树莓派5工业部署六项硬核改造指南

1. 项目概述&#xff1a;树莓派5进车间&#xff0c;不是插电就能跑的“工业级幻觉”“树莓派5进车间”这句标题&#xff0c;乍一听像极了技术圈里常见的热血口号——性能翻倍、接口翻新、散热升级&#xff0c;仿佛只要把这块板子往产线工控柜里一塞&#xff0c;就能立刻替代PLC…

作者头像 李华
网站建设 2026/10/1 7:27:17

金秋华诞,举国同欢,国庆节快乐

金秋十月&#xff0c;丹桂满城&#xff0c;轻柔的秋风携着馥郁花香掠过华夏大地&#xff0c;大街小巷挂满鲜红的五星红旗&#xff0c;处处洋溢着热烈又温暖的喜庆。万众期盼的国庆佳节如约而至&#xff0c;此刻&#xff0c;亿万中华儿女同心呐喊&#xff1a;祝伟大祖国国庆节快…

作者头像 李华
网站建设 2026/10/1 7:26:40

cnc加工交期延误问题出在哪?

做机器人研发的都有过这种经历&#xff1a;图纸发过去&#xff0c;加工厂说 7 天交货。结果第 7 天问&#xff0c;说还在排产&#xff1b;第 10 天问&#xff0c;说尺寸不合格返工了&#xff1b;第 15 天才收到零件。项目节点全乱了。深圳 CNC 加工交期为什么老拖&#xff1f;不…

作者头像 李华
网站建设 2026/10/1 7:26:38

FPGA实现MIPI CSI-2多路视频同步聚合技术解析

1. 这不是一根线&#xff0c;而是一套实时视频调度系统“MIPI多路合一不是普通转接线”——这句话刚在某次工业视觉方案评审会上被我脱口而出&#xff0c;对面客户工程师愣了三秒&#xff0c;低头看了眼手里那根标着“4路CSI转1路MIPI”的黑色线缆&#xff0c;又抬头看我&#…

作者头像 李华