如何使用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()逻辑,运行时窗口会直接卡死,无法正常显示处理结果 - 筛选后的轮廓没有实际绘制、也没有生成对应掩码,无法验证过滤效果
修正后代码
核心修改点:
- 用动态空列表存储符合面积要求的有效轮廓,不需要预定义固定长度数组
- 计算平均面积前先过滤面积极小(小于5像素)的噪点轮廓,避免阈值被拉低
- 补上键盘事件响应逻辑,保证窗口正常显示
- 筛选完成后直接在帧上绘制有效轮廓,同时生成过滤后的前景掩码方便对比效果
- 增加除零判断,避免某一帧无有效轮廓时程序崩溃
- 修正形态学操作内核的数据类型为
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
相关产品推荐
相关产品推荐

