关于OpenCV稠密光流适用性、断流问题及颜色追踪的技术问询
需求:在视频中追踪工人是否手动将垃圾投入卡车

问题1:OpenCV中的Dense Optical Flow是否是适用于该场景的解决方案?
稠密光流可以作为该场景的辅助方案,但并非最优解。它能捕捉画面中所有像素的运动轨迹,确实能反映工人投垃圾的动作,但存在以下局限:
- 易受光照变化、背景干扰(如卡车车身反光、周围环境移动)影响,导致运动轨迹判断失真;
- 单纯依赖光流无法直接完成“垃圾是否投入卡车”的行为判定,需要额外对光流结果做运动区域提取与分析,比如锁定工人手部和垃圾的运动范围,判断其是否进入卡车投放区域。
如果场景固定、光照稳定,光流可作为基础手段,但更推荐目标检测+光流追踪的组合方案:先检测出工人、垃圾、卡车投放口,再用光流追踪目标的运动路径,以此判断动作是否完成。
问题2:编写的示例代码生成的光流图像不连续——帧41和43显示正常,帧42却为全黑,这是什么原因?
先看异常帧的表现:


全黑帧的核心原因及解决办法:
视频帧损坏或读取失败
如果视频本身存在损坏帧,读取到的origin_img会无效(比如全黑、尺寸异常),导致后续灰度转换、光流计算无意义,最终mag(运动幅度)全为0,hsv的V通道(亮度)被归一化为0,转BGR后呈现全黑。
解决:在循环中增加帧有效性校验,跳过无效帧:ret, origin_img = cap.read() if not ret or origin_img.size == 0: print(f'Skip invalid frame {frame_no}') continueHSV画布尺寸不匹配
代码中hsv是用第一帧初始化的固定尺寸画布,如果后续帧因视频编码问题出现尺寸突变,会导致hsv与当前帧尺寸不匹配,赋值后出现异常。
解决:将hsv的创建放到循环内部,每帧都生成与当前帧尺寸一致的画布:# 移除初始化时的hsv创建,放到循环里 while 1: ret, origin_img = cap.read() if not ret or origin_img.size == 0: print(f'Skip invalid frame {frame_no}') continue hsv = np.zeros_like(origin_img) hsv[..., 1] = 255 # 后续代码不变帧计数与实际帧不匹配
代码中frame_no从0开始计数,但初始化时已经读取了第一帧,循环中读取的是第二帧及以后,导致帧编号和实际视频帧存在偏移,可能你看到的“帧42”对应视频中实际损坏的帧。可调整frame_no初始值为1,或者在初始化后先处理第一帧。
问题3:能否通过监测小区域的颜色变化来追踪垃圾是否被投入?
可以,但需满足场景条件,同时要规避干扰:
适用前提
- 监测的小区域(如卡车投放口内部)基准颜色稳定,且与垃圾颜色有明显差异;
- 光照变化小,不会导致区域颜色出现大幅波动。
实现思路
- 标定监测区域:提前确定卡车投放口的ROI(感兴趣区域)坐标,比如你提供的示例区域:

- 建立基准颜色模型:取视频前几帧无垃圾时的区域颜色,计算平均颜色或颜色直方图;
- 实时对比判断:每帧计算该区域的颜色特征,与基准对比,若出现明显且持续的颜色突变,则判定有垃圾投入。
局限性与优化
- 若垃圾颜色与投放口颜色接近,无法有效识别;工人手部误进入区域会触发误判;
- 优化方案:结合背景减除或光流运动检测,只有当有运动物体进入该区域,且颜色变化符合预期时,才判定为垃圾投入,减少误判。
附:测试代码
import numpy as np import cv2 as cv def put_frame_no(image, frame_no): # font font = cv.FONT_HERSHEY_SIMPLEX # org org = (50, 450) # fontScale font_scale = 2 # Blue color in BGR color = (0, 0, 255) # Line thickness of 2 px thickness = 2 # Using cv2.putText() method image = cv.putText(image, "frame no: " + str(frame_no), org, font, font_scale, color, thickness, cv.LINE_AA) return image cap = cv.VideoCapture(cv.samples.findFile("0116-sample4-edited-short-throw.mp4")) ret, frame1 = cap.read() prv_frame = cv.cvtColor(frame1, cv.COLOR_BGR2GRAY) hsv = np.zeros_like(frame1) hsv[..., 1] = 255 cv.namedWindow("flow image", cv.WINDOW_NORMAL) cv.resizeWindow("flow image", 800, 600) frame_no = 0 while 1: ret, origin_img = cap.read() if not ret: print('No frames grabbed!') break next_frame = cv.cvtColor(origin_img, cv.COLOR_BGR2GRAY) flow = cv.calcOpticalFlowFarneback(prv_frame, next_frame, None, 0.5, 3, 15, 3, 5, 1.2, 0) mag, ang = cv.cartToPolar(flow[..., 0], flow[..., 1]) hsv[..., 0] = ang * 180 / np.pi / 2 hsv[..., 2] = cv.normalize(mag, None, 0, 255, cv.NORM_MINMAX) flow_image = cv.cvtColor(hsv, cv.COLOR_HSV2BGR) flow_image = put_frame_no(flow_image, frame_no) origin_img = put_frame_no(origin_img, frame_no) frame_no += 1 vis_frame = np.concatenate((origin_img, flow_image), axis=1) cv.imshow('flow image', vis_frame) # cv.imshow('origin', flow_image) k = cv.waitKey(30) & 0xff if k == 27: break elif k == ord('s'): cv.imwrite('opticalfb.png', origin_img) cv.imwrite('opticalhsv.png', flow_image) prv_frame = next_frame cv.destroyAllWindows()
内容的提问来源于stack exchange,提问作者Dr.Fail to fall asleep
相关产品推荐
相关产品推荐

