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

OpenCV运动检测程序中cv2.findContours返回空值问题求助

问题排查与修复方案

核心问题:OpenCV版本差异导致findContours返回值错误

cv2.findContours的返回值在不同OpenCV版本中存在差异:

  • OpenCV 3.x 返回:(image, contours, hierarchy)
  • OpenCV 4.x+ 返回:(contours, hierarchy)

你的代码中使用[1]获取返回值,在OpenCV4.x中实际拿到的是**层级信息(hierarchy)**而非轮廓(contours),这直接导致后续遍历轮廓时无有效数据。

修复方式:
用imutils库提供的兼容函数自动适配版本,或者直接针对OpenCV4.x取第一个返回值:

# 兼容OpenCV3和4的写法
cnts = cv2.findContours(dilate_image.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)

或者确定使用OpenCV4.x+时,直接写:

cnts = cv2.findContours(dilate_image.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]

其他影响检测的问题修复

  1. 帧尺寸不匹配
    你在初始化first_frame后才对frame做resize,但greyscale_image仍保持原始摄像头帧尺寸,导致frame_delta尺寸和绘制矩形的frame尺寸不一致,即使检测到轮廓也无法正确显示。

修复:先resize帧再处理灰度图:

while True:
    ret, frame = video_capture.read()
    if not ret:
        break
    text = 'Unoccupied'
    
    frame = imutils.resize(frame, width=500)  # 先统一帧尺寸
    greyscale_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 后续模糊处理基于resize后的灰度图
  1. 阈值设置过高
    设置的阈值100过高,普通环境下大部分亮度变化无法触发二值化,导致无有效轮廓。建议调低至25-30:
thresh = cv2.threshold(frame_delta, 25, 255, cv2.THRESH_BINARY)[1]
  1. 增加帧读取有效性判断
    添加ret判断,避免摄像头读取失败导致代码崩溃:
ret, frame = video_capture.read()
if not ret:
    print("无法读取摄像头帧")
    break

完整修复后的代码

import cv2
import time
import datetime
import imutils


def motion_detection():
    video_capture = cv2.VideoCapture(0, cv2.CAP_DSHOW)
    time.sleep(2)

    first_frame = None

    while True:
        ret, frame = video_capture.read()
        if not ret:
            print("摄像头读取失败")
            break
        text = 'Unoccupied'

        # 先统一帧尺寸
        frame = imutils.resize(frame, width=500)
        greyscale_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        gaussian_frame = cv2.GaussianBlur(greyscale_frame, (21, 21), 0)
        greyscale_image = cv2.blur(gaussian_frame, (5, 5))

        if first_frame is None:
            first_frame = greyscale_image
        else:
            frame_delta = cv2.absdiff(first_frame, greyscale_image)
            # 适配普通环境的阈值
            thresh = cv2.threshold(frame_delta, 25, 255, cv2.THRESH_BINARY)[1]
            dilate_image = cv2.dilate(thresh, None, iterations=2)

            # 兼容版本的轮廓获取
            cnts = cv2.findContours(dilate_image.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
            cnts = imutils.grab_contours(cnts)

            for c in cnts:
                if cv2.contourArea(c) > 800:
                    (x, y, w, h) = cv2.boundingRect(c)
                    cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
                    text = 'Occupied'

        # 绘制状态文本与时间戳
        cv2.putText(frame, '{+} Room Status: %s' % text, (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
        cv2.putText(frame, datetime.datetime.now().strftime('%A %d %B %Y %I:%M:%S%p'),
                    (10, frame.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 255), 1)

        cv2.imshow('Security Feed', frame)
        if first_frame is not None:
            cv2.imshow('Threshold(foreground mask)', dilate_image)
            cv2.imshow('Frame_delta', frame_delta)

        key = cv2.waitKey(1) & 0xFF
        if key == ord('q'):
            break

    video_capture.release()
    cv2.destroyAllWindows()


if __name__ == '__main__':
    motion_detection()

内容的提问来源于stack exchange,提问作者Toby

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 06:20:37