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

如何使用OpenCV删除图像小孤岛 保留面积大于平均值的目标

OpenCV 过滤运动检测小面积孤岛区域实现

需求与已完成步骤

开发目标为删除背景减除运动检测结果中的较小孤岛噪声区域,目前已完成处理流程:

  • 使用背景减除法提取前景,分离运动目标
  • 通过腐蚀(Erosion)、膨胀(Dilation)形态学操作填充前景区域内部孔洞
  • 遍历检测到的所有目标轮廓,计算轮廓平均面积
  • 待实现逻辑:筛选保留面积大于平均面积的有效目标,移除小面积噪声

原有实现代码如下:

from __future__ import print_function
import cv2 as cv
import argparse
import numpy as np

array = [None] * 100
parser = argparse.ArgumentParser(description='')
parser.add_argument('--input', type=str, help='Path to a video or a sequence of image.', default='vtest.avi')
# parser.add_argument('--algo', type=str, help='Background subtraction method (KNN, MOG2).', default='MOG2')
parser.add_argument('--algo', type=str, help='Background subtraction method (KNN, MOG2).', default='KNN')
args = parser.parse_args()

# create Background Subtractor objects
if args.algo == 'MOG2':
    backSub = cv.createBackgroundSubtractorMOG2()
else:
    backSub = cv.createBackgroundSubtractorKNN()

capture = cv.VideoCapture("test.mp4")
if not capture.isOpened():
    print('Unable to open: ' + args.input)
    exit(0)

while True:
    ret, frame = capture.read()
    if frame is None:
        break

    # update the background model
    fgMask = backSub.apply(frame)
    # Erosion and Dilation
    kernel = np.ones((3, 3), np.int8)
    erosion = cv.erode(fgMask, kernel, iterations=1)
    dilation = cv.dilate(erosion, kernel, iterations=2)
    ## [apply]
    # contours, hierarchy = cv2.findContours(dilation, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
    contours, hierarchy = cv.findContours(dilation, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
    print("Number of Contours = " + str(len(contours)))
    # Find max contours
    # contour = max(contours, key=len)
    '''contourImg = cv.drawContours(frame, contour, -1, (0, 255, 0), 3)
    cv.imshow("Contours", contourImg)'''

    Contour_Cnts = 0
    Total_Area_Of_all_Contour = 0
    Average_Area_Of_Contour = 0
    for cnt in contours:
        area = cv.contourArea(cnt)
        Total_Area_Of_all_Contour = Total_Area_Of_all_Contour + area
        Contour_Cnts = Contour_Cnts + 1
    Average_Area_Of_Contour = Total_Area_Of_all_Contour // Contour_Cnts
    print("Number of contours", Contour_Cnts)

    Contour_Cnts = 0
    index = 0
    for cnt in contours:
        Contour_Cnts = Contour_Cnts + 1
        area = cv.contourArea(cnt)
        if area > Average_Area_Of_Contour:
            index = int(index) + 1
            # array[index] = contours[Contour_Cnts]
    # cv.drawContours(frame, array, -1, (0,0,255), 2)

    cv.imshow('Frame', frame)
    cv.imshow('FG Mask', fgMask)
    cv.imshow('Dilation', dilation)

capture.release()
cv.destroyAllWindows()

原有代码问题点

  • 轮廓存储逻辑错误:预定义固定长度的空数组存储有效轮廓,遍历索引和轮廓下标不匹配,很容易出现空值、轮廓错位问题
  • 平均面积计算逻辑有缺陷:没有过滤像素级的极小无效噪点轮廓,大量噪点会拉低平均面积阈值,导致过滤效果达不到预期
  • 缺少窗口响应逻辑:cv.imshow 之后没有加 cv.waitKey() 逻辑,运行时窗口会直接卡死,无法正常显示处理结果
  • 筛选后的轮廓没有实际绘制、也没有生成对应掩码,无法验证过滤效果

修正后代码

核心修改点:

  1. 用动态空列表存储符合面积要求的有效轮廓,不需要预定义固定长度数组
  2. 计算平均面积前先过滤面积极小(小于5像素)的噪点轮廓,避免阈值被拉低
  3. 补上键盘事件响应逻辑,保证窗口正常显示
  4. 筛选完成后直接在帧上绘制有效轮廓,同时生成过滤后的前景掩码方便对比效果
  5. 增加除零判断,避免某一帧无有效轮廓时程序崩溃
  6. 修正形态学操作内核的数据类型为np.uint8,避免隐式类型转换问题
from __future__ import print_function
import cv2 as cv
import argparse
import numpy as np

parser = argparse.ArgumentParser(description='')
parser.add_argument('--input', type=str, help='Path to a video or a sequence of image.', default='vtest.avi')
parser.add_argument('--algo', type=str, help='Background subtraction method (KNN, MOG2).', default='KNN')
args = parser.parse_args()

# 初始化背景减除器
if args.algo == 'MOG2':
    backSub = cv.createBackgroundSubtractorMOG2()
else:
    backSub = cv.createBackgroundSubtractorKNN()

capture = cv.VideoCapture("test.mp4")
if not capture.isOpened():
    print('Unable to open: ' + args.input)
    exit(0)

while True:
    ret, frame = capture.read()
    if frame is None:
        break

    # 提取前景掩码
    fgMask = backSub.apply(frame)
    # 形态学操作
    kernel = np.ones((3, 3), np.uint8)
    erosion = cv.erode(fgMask, kernel, iterations=1)
    dilation = cv.dilate(erosion, kernel, iterations=2)
    
    # 查找轮廓
    contours, hierarchy = cv.findContours(dilation, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
    print(f"检测到原始轮廓数量: {len(contours)}")

    # 第一步:计算有效轮廓的平均面积,先过滤小于5像素的噪点
    valid_contours_for_calc = []
    total_area = 0
    for cnt in contours:
        area = cv.contourArea(cnt)
        if area > 5: # 过滤像素级噪点
            valid_contours_for_calc.append(cnt)
            total_area += area
    # 避免无有效轮廓时除零报错
    avg_area = total_area // len(valid_contours_for_calc) if len(valid_contours_for_calc) > 0 else 0
    print(f"有效轮廓平均面积: {avg_area}")

    # 第二步:筛选保留面积大于平均面积的目标
    keep_contours = []
    for cnt in valid_contours_for_calc:
        area = cv.contourArea(cnt)
        if area > avg_area:
            keep_contours.append(cnt)
    
    # 生成过滤后的前景掩码
    filtered_mask = np.zeros_like(dilation)
    cv.drawContours(filtered_mask, keep_contours, -1, 255, -1)
    # 在原始帧上绘制保留的目标轮廓
    cv.drawContours(frame, keep_contours, -1, (0,0,255), 2)

    # 显示结果
    cv.imshow('Frame', frame)
    cv.imshow('FG Mask', fgMask)
    cv.imshow('Dilation', dilation)
    cv.imshow('Filtered Mask', filtered_mask)

    # 键盘响应,按q或ESC退出
    keyboard = cv.waitKey(30)
    if keyboard == ord('q') or keyboard == 27:
        break

capture.release()
cv.destroyAllWindows()

注意:如果实际场景里真实小目标比较多,可以把面积判断的阈值从大于平均面积调整为大于平均面积*0.8这类带系数的值,避免误删真实目标。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 06:21:14