You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于OpenCV稠密光流适用性、断流问题及颜色追踪的技术问询

需求:在视频中追踪工人是否手动将垃圾投入卡车

工人扔垃圾


问题1:OpenCV中的Dense Optical Flow是否是适用于该场景的解决方案?

稠密光流可以作为该场景的辅助方案,但并非最优解。它能捕捉画面中所有像素的运动轨迹,确实能反映工人投垃圾的动作,但存在以下局限:

  • 易受光照变化、背景干扰(如卡车车身反光、周围环境移动)影响,导致运动轨迹判断失真;
  • 单纯依赖光流无法直接完成“垃圾是否投入卡车”的行为判定,需要额外对光流结果做运动区域提取与分析,比如锁定工人手部和垃圾的运动范围,判断其是否进入卡车投放区域。

如果场景固定、光照稳定,光流可作为基础手段,但更推荐目标检测+光流追踪的组合方案:先检测出工人、垃圾、卡车投放口,再用光流追踪目标的运动路径,以此判断动作是否完成。


问题2:编写的示例代码生成的光流图像不连续——帧41和43显示正常,帧42却为全黑,这是什么原因?

先看异常帧的表现:
正常帧41
异常帧42
正常帧43

全黑帧的核心原因及解决办法:

  1. 视频帧损坏或读取失败
    如果视频本身存在损坏帧,读取到的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}')
        continue
    
  2. HSV画布尺寸不匹配
    代码中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
        # 后续代码不变
    
  3. 帧计数与实际帧不匹配
    代码中frame_no从0开始计数,但初始化时已经读取了第一帧,循环中读取的是第二帧及以后,导致帧编号和实际视频帧存在偏移,可能你看到的“帧42”对应视频中实际损坏的帧。可调整frame_no初始值为1,或者在初始化后先处理第一帧。


问题3:能否通过监测小区域的颜色变化来追踪垃圾是否被投入?

可以,但需满足场景条件,同时要规避干扰:

适用前提

  • 监测的小区域(如卡车投放口内部)基准颜色稳定,且与垃圾颜色有明显差异;
  • 光照变化小,不会导致区域颜色出现大幅波动。

实现思路

  1. 标定监测区域:提前确定卡车投放口的ROI(感兴趣区域)坐标,比如你提供的示例区域:
    小区域示意图
  2. 建立基准颜色模型:取视频前几帧无垃圾时的区域颜色,计算平均颜色或颜色直方图;
  3. 实时对比判断:每帧计算该区域的颜色特征,与基准对比,若出现明显且持续的颜色突变,则判定有垃圾投入。

局限性与优化

  • 若垃圾颜色与投放口颜色接近,无法有效识别;工人手部误进入区域会触发误判;
  • 优化方案:结合背景减除或光流运动检测,只有当有运动物体进入该区域,且颜色变化符合预期时,才判定为垃圾投入,减少误判。

附:测试代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 13:01:12