# 03 视觉篇:OpenCV 检测、阈值/形态学、摄像头画面、帧率优化与 RKNN YOLOv5 部署
03 视觉篇OpenCV 检测、阈值/形态学、摄像头画面、帧率优化与 RKNN YOLOv5 部署系列第 4 篇。这一篇坑最多55 个从 Windows 摄像头画面偏色、OpenCV 基础语法坑、空帧崩溃到阈值/形态学调参、帧率优化再到 RKNN YOLOv5 部署的九宫格假框主坑。按主题分组。一、Windows 摄像头画面偏绿/偏黄1.cv2.cvtColor(work, COLOR_BGR2RGB)显示偏色07-29现象画面整体偏黄/偏蓝。根因cv2.imshow()期望 BGR把 BGR 转 RGB 后显示就反了。解决显示用work.copy()保持 BGR。教训cvtColor只在保存/传输用imshow 永远要 BGR。2. USB 摄像头画面偏绿Windows 相机 App 却正常07-29 详细记录、07-30、07-31 重复排查链BGR/RGB 反转→ 修好仍绿FOURCCMJPG→ 无效自动白平衡→ 无效内置摄像头正常、USB 偏绿 → 不是代码换 DSHOW 后端 → 仍绿Windows 相机 App 正常→ 后端问题。根因OpenCV 默认/DSHOW 后端对该 USB 摄像头颜色解码不一致Windows 相机 App 用的是Media FoundationMSMF。解决backends[cv2.CAP_MSMF,cv2.CAP_DSHOW]forbinbackends:capcv2.VideoCapture(cam_id,b)ifcap.isOpened():break教训颜色异常先拿系统原生 AppWindows 相机/Cheese对比能快速排除硬件不要上来就调白平衡/FOURCC。二、OpenCV 基础语法坑反复出现先记住3.cv2.findContours的 hierarchy 判空NoneType object is not subscriptable07-20根因全黑无轮廓时 hierarchy 可能是空 ndarray 而不是 Python Noneif hierarchy is None拦不住。解决if hierarchy is None or len(hierarchy) 0 or len(contours) 0。教训OpenCV 返回值判空不要只看is None也要看len()/.size。4. 初始化cx0导致cx is not None永远是 True07-20现象没找到目标时ptsNone但if cx is not None通过了取pts[0]崩溃。根因cx0数字 0 不是 None守卫条件选错变量。解决画框用if pts is not None算误差用if cx is not None。教训None 守卫的变量必须初始化为 None画框和算误差分开判断。5.cv2.medianBlur(mask, 3)没接收返回值 没调用07-20根因cv2 所有处理函数返回新数组、不原地修改。教训mask cv2.medianBlur(mask, 3)import 后第一条确认是否赋值。6. Python 3.9 不支持int | None语法07-20现象TypeError: unsupported operand type(s) for |: type and NoneType。根因int | None是 Python 3.10 语法泰山派板子是 3.8/3.9。解决from typing import Optional→Optional[int]。7. LABcv2.inRange上下界 numpy 类型不匹配07-19、07-21 重复现象left是 uint8、upper是 int64inRange 报错/误检。根因L*2.55、A128混算产生 float/int64。解决上下界 dtype 保持一致统一np.array(..., dtypenp.uint8)别用np.uint8强转单个值。8.adaptiveThreshold断言失败blockSize % 2 1 blockSize 107-31 重复解决blockSize 必须为1的奇数。9. 霍夫圆acc_threshold must be a positive number07-31根因param2累加器阈值被调成 0/负。解决param2 必须 0最终 15 左右。10. 函数参数大小写不一致analyze() got an unexpected keyword argument MIN_R07-31解决统一min_r/min_circ小写参数名。11.connectedComponentsWithStats报image is not a numerical tuple07-31根因传入的 mask 不是有效 uint8 单通道图传了 None/错误类型。解决保证入参是 uint8 二值图。12.HoughLinesP解包失败cannot unpack non-iterable numpy.int3207-31根因x1,y1,x2,y2 line[0]结构理解错误。解决按lines[i][0]为[x1,y1,x2,y2]数组的正确结构遍历。三、空帧崩溃“找不到目标是常态”重复 10 次的元凶13.UnboundLocalError: local variable pts/sx/bx/mask referenced before assignment07-15 ~ 08-01 反复出现现象find_rects/find_ji/analyze里空帧直接崩cx,cy 0报解包错误drawMarker括号套错while 前sx未定义。根因变量只在 if/for 分支内赋值该帧没检测到目标时走到使用处变量不存在。解决模板ptsNone;cxcyNone# 函数/循环前先初始化ifptsisnotNone:# 调用方判空再画图...sxsysrNone# while 前初始化所有 return 路径返回相同数量的值return pts, cx, cy, frame_drawn。教训检测类函数找不到目标是常态不是异常。14. 低通平滑状态放函数内每次调用被重置07-18 重复 2 次现象sx 0.3*(cx-sx)永不生效调用写成find_ji(frame, sxNone)每帧硬编码传 None。根因平滑状态是上一帧的值放函数内部每次调用清零关键字参数名 ≠ 变量。解决函数接收上次状态find_ji(frame, sx, sy, sr)主循环 while 前初始化一次。15.find_rects提前 return 3 个值调用方解包 4 个 → ValueError07-18解决所有 return 路径统一return pts, cx, cy, frame_drawn。四、检测算法坑矩形 / 激光 / 轮廓16.findContours后再加 Canny矩形轮廓翻倍07-15现象回字形胶布框的内/外边界变成 4 条细线轮廓面积≈0 被过滤漏检。根因findContours 本身从二值图提边界再加 Canny 重复提边。解决删掉 Canny 直接findContours(RETR_LIST)要内框用RETR_CCOMPhierarchy[0][i][3] ! -1。17.boundingRect在矩形倾斜时中心不准07-16解决倾斜场景用cv2.minAreaRect(cnt)返回值第一个元素即中心注意 float 转 int、画框用cv2.boxPoints正对场景boundingRect即可。18. 斜视角下四点平均找中心有系统性偏差07-16根因透视投影下矩形真实中心在两条对角线的交点不在四点平均处。解决对角线交点函数rect_center()点0-点2、点1-点3 求交分母abs(d)1e-6时退回平均法。19.approxPolyDP角点起点每帧随机依赖哪个是左上角的逻辑时好时坏07-16根因approxPolyDP只保证点沿轮廓有序起点随机。解决需要固定顺序时用order_corners()xy 最小左上、x-y 最大右上等只画框/算中心不需要排序。20. 激光点检测过曝中心发白LAB 红色阈值抓不住07-14 起解决双路检测合并过曝中心 BGR235 红色光晕 LAB V 通道亮度约束 3×3 小核 圆度过滤 EMA 平滑 cv2.moments()质心过曝中心不要比颜色白色中心 R≈B直接用 HSV 的V245抓。21.best[2]存的是半径却和面积比较07-16、07-18 重复现象if best is None or area best[2]几乎恒真“最后一个轮廓胜出”。解决单独best_area变量跟踪最大面积。22.drawMarker括号全塞进坐标元组07-17解决cv2.drawMarker(frame, (cx,cy), (0,255,0), cv2.MARKER_CROSS, 20, 2)。23. 变量名冲突h被色相通道和 boundingRect 高度先后占用07-16解决bx,by,bw,bh cv2.boundingRect(cnt)计时用last_time、判重用last_count。五、阈值与光照HSV / LAB / 二值化24. HSV 调参红色在 H 两端、调参顺序、光照影响 V07-28根因OpenCV 的 H 是 0~179真实 0~360 除 2红色同时位于 0 和 179 两端单区间圈不全。解决红色用两个区间 ORmask1cv2.inRange(hsv,(0,50,50),(10,255,255))mask2cv2.inRange(hsv,(170,50,50),(179,255,255))maskmask1|mask2调参顺序固定先 H → 再 S → 最后 V光照变了优先重调 V。25. 淡蓝/浅色目标 HSV 噪点多07-28根因低饱和度颜色接近白/灰背景HSV 本身难分离。解决阈值前加GaussianBlur((5,5),0)收窄 S_min、放大最小轮廓面积治本用形状筛选/模板匹配。26. 钢珠银灰与浅灰桌面 HSV 分不开07-29解决垫黑布/黑纸物理分离 霍夫圆/Canny圆形度与颜色无关后续发现原题钢珠在白色管壁内对比度好HSV 重新可行。27. 阈值方向假设完全反了钢珠比背景亮07-30现象THRESH_BINARY_INV暗变白下 mask 全黑。根因钢珠球面反光 → 钢珠是亮点不是暗点白管银白珠同色系。解决换THRESH_BINARY亮变白后续管槽喷哑光黑漆黑管亮珠灰度差 100阈值随便设都稳再换回THRESH_BINARY_INV阈值 128。28. ROI 太宽导致 OTSU 阈值波动07-30根因ROI 把管外大片白背景框进来直方图被污染。解决ROI 缩到管槽内部360×60弃用 OTSU 改固定阈值。29. 光照不均全局阈值顾不到暗角07-19、07-26 重复现象白纸铺满视野时边缘暗区被误判成黑线左边暗左边框检测不到。解决换cv2.adaptiveThreshold(gray,255,ADAPTIVE_THRESH_GAUSSIAN_C,THRESH_BINARY_INV,31,5)每像素与 31×31 邻域比较或用 LAB CLAHE 均衡 L 通道。代价adaptiveThreshold 31×31 核极吃算力见坑 43。30. 巡线镜头套没摘OTSU 全黑乱跳07-26现象画面全黑中间一条绿线重心全在正中binary 时黑时白。根因摄像头镜头保护套没摘OTSU 把纯黑画面当最暗的噪点是线。教训binary 异常先排查成像本身别急着改算法。31. 数字识别复用巡线的THRESH_BINARY_INV背景前景反了07-27现象findContours把整屏框成一个大轮廓。解决数字场景THRESH_BINARY | THRESH_OTSU不反转white_ratio 0.6时bitwise_not自动纠正黑底白字。六、形态学与钢珠检测调优ball_balancer 阶段核心32. 小光斑5~40px被 3×3 MORPH_OPEN/CLOSE 抹掉07-20现象before38 像素after0。解决删掉 OPEN/CLOSE/dilate只用medianBlur(3)小光斑直接对 mask 求矩。教训形态学核必须小于目标尺寸。33. 圆形度 0.5 对小光斑太严07-20现象圆形度打印 0.2~0.49 全被筛掉10px blob 天然圆不了。根因小目标周长离散化误差占比大同样形状缩小 10 倍圆形度接近减半。解决面积30 放宽到 0.25大光斑保持 0.5。教训阈值必须跟随面积缩放。34. 钢珠反光碎斑 → 闭运算填孔07-30、07-31现象钢珠白斑碎成多块圆形度不够开运算又把刚弥合的珠腐蚀碎。解决闭运算 3×3×1 →5×5×2填反光碎洞顺序OPEN→DILATE→CLOSEOPEN 放最前面去噪靠min_areacircularity兜底。35. 影子也圆圆形度筛不掉07-30解决加亮核过滤钢珠中心比边缘亮、球面高光面积 → 圆形度(0.75) → 半径 → 位置 → 亮核 五重过滤。36. 钢珠与管槽二值化后连通成一体07-31解决边缘定管槽 阈值定钢珠两步分离Canny 找管槽长边定 ROIROI 内再二值化。37. 霍夫圆在管壁误检假圆07-31解决管边过滤圆心距 ROI 上下边 10~12px 的圆排除被切半的假圆 半径范围约束 取最小半径优先。38. 霍夫圆跳半径/忽大忽小07-31解决收窄min_r/max_r最终 8~13加位置平滑EMAparam1/param2110/10。39. 固定阈值白球被截/白区虚大07-31、08-01 重复根因形态学膨胀过重DILATE(5,5)CLOSE(5,5) 把白区外扩好几像素超出 ROI 被截。解决固定阈值改轻形态学OPEN(3,3)x1 CLOSE(3,3)x1白区≈钢珠实际大小。40. 固定阈值 vs 自适应阈值形态学强度冲突07-31、08-01现象固定要轻防虚大、自适应要重填毛刺一套参数无法兼顾。解决按模式分两套固定OPEN(3,3)x1CLOSE(3,3)x1自适应OPEN(3,3)x1DILATE(5,5)x1CLOSE(5,5)x2。教训不要偷懒共用形态学参数当初统一版本就是错的已回退。41. 金属反光月牙只露出边缘暗圈07-31、08-01 重复根因球面反光THRESH_BINARY_INV只把暗边缘变白。解决降FIXED_THRESH170→130 附近 放宽MIN_CIRC0.6→0.4 填充率筛选或切自适应/霍夫。42.cv2.putText中文显示成?07-31重复 6 次根因Hershey 字体不支持中文。解决所有显示文字改英文TASK_NAME[0]idle、Mode:Fixed等。教训嵌入式 OpenCV 显示永远用英文。43. 自适应阈值拖慢 FPS07-26、07-31现象换 adaptiveThreshold 后 FPS 掉到 13。根因31×31 核 961 次乘加/像素比 OTSU 慢 10 倍以上。解决先缩到 320×240 处理再放大坐标或ADAPTIVE_THRESH_MEAN_C替代 GAUSSIAN_C无暗角场景直接 OTSU。44. SCALE 传递链断裂config 改了 pos 纹丝不动07-31、08-01 重复 2 次根因detect.py/detect_v2.py 用硬编码_scale10.0set_scale()从未被任何地方调用。解决processor.py 启动时set_scale(SCALE)/hough_set_scale(px_per_cmSCALE)。同类坑自适应ADAPT_BLOCK/ADAPT_C配置没生效detect.py 硬编码 21/20thresh_mask 只传阈值没传 block/c→ thresh_mask 加参数、processor 显式传霍夫center_px250硬编码与 config 206 不一致 →hough_set(center_pxCENTER_PX)。教训config 与检测模块之间必须有显式调用链模块内部禁止硬编码默认值。45. 静态标定公式错误SCALE(pos2-pos1)/5只在 SCALE_old1 时成立07-31解决SCALE_new SCALE_old × (pos2 - pos1) / 5。46. 管槽是斜的1-2°水平 ROI 两难07-31解决把摄像头跟着管子斜着摆画面里管子就水平了固定牢后重标 ROI/CENTER_PX/SCALE1-2° 倾斜对 10cm 行程误差 2mm 可忽略。47. ROI 点错导致cvtColor: !_src.empty()崩溃07-31、08-01解决改回正确 ROI用户选择不加边界保护代码。48. gap0 帧率统计失真 / FPS 虚高07-31、08-01 重复根因测量 bug——重复帧continue在_last_t1更新/_gap计算之后gap 量的是重复帧循环时间≈2ms。解决重复帧检查移到_gap计算之前重复帧跳过整轮不刷新_last_t1FPS 用真实处理帧间隔_cur_seq差值。49.minEnclosingCircle圈出的圆比钢珠大07-31根因包住整个残缺轮廓的最小圆畸形/反光缺口被包进去。解决圆心偏移 2px 不受影响offset_cm 只用圆心半径偏大不影响控制除非超 MAX_R 丢检。50. 统计打印间隔 300/900/1500 不规律08-01根因if _stat[total] % 300 0是满 300 倍数才打印。解决改300后清零重计按键开关时重置_stat。七、摄像头与帧率优化一条完整排查链51. 自动曝光把帧率压到 12fps07-16现象帧率在 28 和 12 之间来回飘跟镜头对准的场景亮度变。根因暗场景 AEC 把曝光时间拉到 83ms一帧曝光不可能短于帧间隔直接压死帧率激光静止时 AEC 把光斑也压暗07-20 复现。解决v4l2-ctl 锁手动曝光命令行比 OpenCV CAP_PROP 可靠v4l2-ctl-d/dev/video9 --set-ctrlauto_exposure1v4l2-ctl-d/dev/video9 --set-ctrlexposure_time_absolute100# 10ms330 才能回 30fps注意控制名随内核版本变旧版exposure_auto/exposure_absolute新版auto_exposure/exposure_time_absolute先v4l2-ctl -l查命令行设置重启/拔插会重置要固化进代码。52. 1080p MJPG 解码瓶颈Cortex-A55 上 60~80ms/帧07-16现象判重改造后 FPS 仍只有十几采集线程却 28fps。根因cap.read()内含 JPEG 解码1080p 在 ARM 上极慢且cap.set失败是静默的回落 YUYV 后 1080p 超 USB2.0 带宽。解决降到 640×480ROI 坐标等比 /3、面积阈值 /9很多摄像头 480p 还能解锁 60fps。53.CAP_PROP_BUFFERSIZE1反而让 read 翻倍 / 卡 15fps07-25、07-26现象分段计时read: 66.6ms裸开 33.3ms另一场景卡死 15fps。根因RK3566 的 OpenCV 上 BUFFERSIZE1 反而拖慢读取。解决删掉 BUFFERSIZE 设置与 test.py 保持一致。54. MJPG 格式比 YUYV 慢一倍07-25根因ARM CPU 软件解压 JPEGYUYV 裸像素直传。解决默认 YUYV 640×48030fps33ms/帧。55. 采集线程解耦后同一帧被处理好几遍07-16现象主循环测采集耗时 0ms但主循环每秒空转 200 圈串口坐标重复轰炸。解决给帧加count序号if count last: continue判重。56. 每帧print拖慢 FPS07-16、07-26解决if count % 30 0汇总打印推流降频if count % 3 0。57. 帧率从 10 FPS 优化到 15-18 FPS 的完整链条07-25步骤① SPI 屏推流放后台线程② decode 向量化np.where→ numpy 批量切片③np.meshgrid模块级预计算④ 先算confobj*cls过滤只对命中格子算坐标⑤cap.read()放后台线程与 NPU 并行⑥ 去掉 BUFFERSIZE、用 YUYV。最终瓶颈NPU INT8 640×640 推理 37ms硬上限 取帧 33ms ≈ 15-18 FPS。58. OpenCV 读到旧帧导致追踪滞后07-16根因VideoCapture有缓冲队列处理跟不上时读到旧帧。解决CAP_PROP_BUFFERSIZE1驱动不支持就连读两次取最新/开线程持续 grab运动拖影手动锁曝光。59. 丢帧时坐标跳动/云台乱甩07-16、07-18、07-21、08-01 重复现象检测丢失的几帧坐标置空/回 0云台乱抖、PID 误判已瞄准。解决丢帧保留上一帧有效值last_center/last_errlost_count上限内沿用 低通滤波fx alpha*cx (1-alpha)*fx_prev启动默认 (0,0) 云台不动。进阶08-01丢帧不发_last_found门控下位机靠超时感知任务切换用阶梯逼近TASK1_RAMP_STEP0.18cm/帧速度隔 3 帧差分。八、RKNN YOLOv5 部署含全项目最核心的坑60. 31 张图训练 mAP 只有 9.5%07-23解决一步步提升--epochs 300 --patience 100 --hyp hyp.scratch-low.yaml→ mAP 70.8%合并自标 102 张 → 74.5%--img 320改--img 640 --batch 4→90.7%分辨率是关键小钢球在 320 下太糊。旁坑checkpoint 取指标KeyError→ 直接读runs/train/exp*/results.csv微信图片corrupt JPEG→ YOLO 自动修复MakeSense.ai 导出的标注文件名对不上20 张微信图没标上→ 图片/标签按同名配对。61. ★ RKNN 非量化转换表面成功、输出全错07-24排查一天的核心坑现象do_quantizationFalsebuild/export/init 全返回 0推理不报错但输出 700 候选 → NMS 后 36 个假框排成3×3 网格/九宫格同一张图 PC 端 ONNX Runtime 正常Top10 obj≈0.92板端与 ONNX 逐元素 diff 0.186PC 上 RKNN Simulator 结果也错max_diff16.4→ 问题出在rknn.build()编译转换不在 NPU 硬件。根因RK3566 NPU 不支持 float32 计算只支持 INT8/INT16/FP16/BFP16。do_quantizationFalse看似非量化实际内部强转低精度150 层误差层层累积输出完全崩坏。解决INT8 量化 3 个独立 sigmoid 输出不要 concat Python 端做 grid/stride/anchor 解码rknn.config(mean_values[[0,0,0]],std_values[[255,255,255]],target_platformrk3566,quantized_dtypeasymmetric_quantized-8,quantized_algorithmnormal)rknn.build(do_quantizationTrue,datasetcalib_clean.txt)最终模型 2.89MB4 个钢球全部正确检测NPU 推理 36.8ms ≈ 27 FPS。旁坑FP16 量化遇Concat dtype5SiLU 混合精度产生 int64→ 放弃 FP16 直接 INT8。62. RKNN 校准数据集文件名带空格导致解析失败07-24现象ValueError: Not support file_ext in file .../images/images。根因Roboflow 文件名如images (23)_jpeg.rf.xxx.jpeg带空格RKNN 按空格截断路径。解决grep -v calibration_images.txt calib_clean.txt剔除 31 个带空格文件classes.txt别放 labels 目录418 图 vs 419 标签对不上。63. 图像双重归一化导致检测不到球07-24根因RKNN 转换时设了std_values[[255,255,255]]NPU 内部自动 ÷255Python 端又/255.0除了两次输入全黑。解决input_data img_rgb.astype(np.float32)不归一化RKNN 已内置。64. 板端检测40 个结果但图上一个框没画07-24根因① YOLOv5 导出 ONNX 后cx,cy,w,h是 640 空间像素坐标代码却按归一化*orig_w直接乘框巨大被 NMS 全杀② 单类别模型 cls 列常数 0.998用obj*cls当分数不当。解决x1int((cx-w/2)*orig_w/IMG_SIZE)# 像素坐标除以 IMG_SIZE 再乘回原图scoresout[:,4]# 单类别直接用 obj_conf65.cv2.dnn.blobFromImage优化后检测突然变回九宫格07-25根因blobFromImage 输出NCHW通道在前RKNN 模型要求NHWC。解决改回 NHWC 写法imgcv2.resize(frame,(IMG_SIZE,IMG_SIZE))imgcv2.cvtColor(img,cv2.COLOR_BGR2RGB)inpimg[np.newaxis,:,:,:].astype(np.uint8)# (1,640,640,3)66. 480 模型 vs 640 模型精度几乎无损帧率 21→2907-25、07-27现象远处小球 0 命中640 下像素太小 INT8 精度损失CONF 0.5 下 0/5 命中。解决val.py对比yaml 相对路径会解析失败改用绝对路径640 mAP0.901480 mAP0.899仅降 0.2%帧率 21→29远处召回可调低CONF到 0.2 左右。67. 板子librknnrt.so平台不符 get_os_platform()返回 Linux_x6407-24、07-28详见系统篇坑 7、8。重新烧录会丢需重做。68. 检测到球时帧率从 22-24 掉到 16-1907-25根因候选框变多后draw_overlay里for i in idx纯 Python 逐框画循环暴增。解决接受现状比赛够用再快只能缩输入尺寸或跳帧。小结视觉篇避坑清单空帧是常态所有检测函数变量先初始化 调用方判空最反复的坑。cv2 函数都返回新数组不原地修改判空看len()不只is None。形态学核必须小于目标尺寸固定/自适应阈值形态学参数分开调。config 与检测模块之间必须有显式调用链set_scale/set_params禁止模块内硬编码。显示文字永远用英文Hershey 字体无中文。RK3566 NPU 只认 INT8非量化输出九宫格假框别浪费时间排查。嵌入式帧率YUYV MJPG、640×480 1080p、别设 BUFFERSIZE1、采集线程解耦 count 判重 print 节流。丢帧不发 0发上一次有效值。下一篇04_串口篇UART 协议、半包处理、限频与丢帧策略

相关新闻

最新新闻

日新闻

周新闻

月新闻