C++:
cv::Mat removeLargerSmallerRegion(cv::Mat mask, int minAreaThreshold , int maxAreaThreshold )
{
// 查找轮廓
std::vector<std::vector<Point>> contours;
std::vector<cv::Vec4i> hierarchy;
findContours(mask, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
// 遍历所有轮廓,过滤掉面积小于阈值的轮廓
int minAreaThreshold = 20; // 设置最小面积阈值
int maxAreaThreshold = 1000
for (int i = 0; i < contours.size(); i++) {
int areasValue = contourArea(contours[i], false);
if (areasValue <= minAreaThreshold && areasValue >= maxAreaThreshold )
{ // 只保留大于设定阈值的区域
cv::Scalar color = cv::Scalar(0, 0, 0); // 区域涂黑
drawContours(mask, contours, i, color, CV_FILLED, 8, hierarchy, 0, cv::Point(0, 0));
}
}
}
findContours:检测物体轮廓
findContours(Mat image,
List<MatOfPoint> contours,
Mat hierarchy,
int mode,
int method,
Point offset)
参数介绍:
image:单通道图像矩阵,可以是灰度图,但更常用的是二值图像,此处使用的是,语义分割之后的mask结果图,像素值代表类别;
contours:为一个向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓;有多少轮廓,向量contours就有多少元素;
int model:定义轮廓的检索模式:
1)RETR_EXTERNAL:只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略;
2)RETR_LIST :检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓;
3)RETR_CCOMP: 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层;
4)RETR_TREE:检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
int method:定义轮廓的近似方法:
1)CHAIN_APPROX_NONE:保存物体边界上所有连续的轮廓点到contours向量内;
2)CHAIN_APPROX_SIMPLE:仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留;
3)CHAIN_APPROX_TC89_L1,CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法;
Point offset:Point偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,并且Point还可以是负值。
drawContours:用于绘制轮廓的函数,根据轮廓数据在图像上绘制轮廓线条或填充轮廓。
drawContours(Mat image,
List<MatOfPoint> contours,
int contourIdx,
Scalar color,
int thickness,
int lineType,
Mat hierarchy,
int maxLevel,
Point offset)
参数介绍:
第一个参数image表示目标图像,
第二个参数contours表示输入的轮廓组,每一组轮廓由点vector构成,
第三个参数contourIdx****指明画第几个轮廓,如果该参数为负值,则画全部轮廓,
第四个参数color为轮廓的颜色,
第五个参数thickness为轮廓的线宽,如果为负值或CV_FILLED表示填充轮廓内部,
第六个参数lineType为线型,
第七个参数为轮廓结构信息,
第八个参数为maxLevel
第九个参数为偏移量
contourArea:计算轮廓面积
这个函数可以计算给定轮廓的面积,轮廓是由一系列二维点组成的。
double contourArea(InputArray contour, bool oriented = false);
参数介绍:
InputArray contour:输入的点集,通常是图像轮廓的顶点坐标,可以是std::vector或Mat类型。
bool oriented:是一个面向区域的标识符,如果设置为true,函数会返回一个带符号的面积值,正负取决于轮廓的方向(顺时针或逆时针)。如果为false,则以绝对值返回面积值。
注意:
1、contourArea()求的是封闭曲线内面积(通过格林公式计算),几何面积求的是最小外接矩的面积,导致contourArea()求出来的面积和宽高面积不一样,且永远小于看宽高面积。
2、由于在计算面积的时候丢失了外围像素,因此contourArea()求出来的面积比真实面积要小。
python:
方法一:通过检测轮廓来去除
findContours()函数:
检测输入图像的轮廓,最后通过面积进行筛选
contours, hierarch = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)
参数:
img: 输入图像,最好也是二值图像;
返回值:
coutours:一个list,list中每个元素都是图像中的一个轮廓信息,用numpy中的ndarray表示,配合cv.contourArea() 函数可以直接得到轮廓面积,配合cv.drawContours()函数可以对某个区域进行操作,比如上述的直接让该区域像素值置为背景颜色0(这俩函数也不多做赘述了,一搜一大堆)
hierarchy:轮廓间的层次关系,为三维数组,形状为(1,n,4)。
方法二:检测连通区域
connectedComponentsWithStats()函数
通过这个函数检测到每个连通区域后,再对不同区域的面积进行筛选:
num_labels, labels, stats, centroids = cv.connectedComponentsWithStats(src, connectivity=8, ltype=None)
函数参数:
src:传入需要处理的图片,要求为二值图像,上面用例的图像已经是二值图像了,如果不是的话需要加一步二值化;
connectivity:可选值为4或8,也就是使用4连通还是8连通
返回值:
num_labels:所有连通域的数目
labels:这个返回值很关键,ltype参数默认为none的情况下,输出的labels是一个和原图一样大小的矩阵,原图中检测到的连通图的位置,对应的labels矩阵值为1,其余值为0;
stats:记录了每个连通区域的信息,是一个5列的矩阵,每一行对应一个连通区域,分别为连通区域外接矩形的x、y、width、height和面积,例如stats[0][4]就是第一个连通区域的面积
centroids:连通域的中心点
然后就可以通过stats对连通域进行筛选,再通过labels确定筛选出的区域坐标,在进行各种想进行操作,比如这个去除孤立点,直接让该区域像素值置为背景颜色0即可。
import cv2.cv2 as cv
import numpy as np
def Img1(src):
num_labels, labels, stats, centroids = cv.connectedComponentsWithStats(src, connectivity=8, ltype=None)
img = np.zeros((src.shape[0], src.shape[1]), np.uint8) #创建个全0的黑背景
for i in range(1, num_labels):
mask = labels == i #这一步是通过labels确定区域位置,让labels信息赋给mask数组,再用mask数组做img数组的索引
if stats[i][4] > 300: #300是面积 可以随便调
img[mask] = 255
#面积大于300的区域涂白留下,小于300的涂0抹去
else:
img[mask] = 0
return img
def Img2(img):
contours, hierarch = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)
area = []
for i in range(len(contours)):
area.append(cv.contourArea(contours[i])) #计算轮廓所占面积
if area[i] < 300: #轮廓面积,可以自己随便调
cv.drawContours(img,[contours[i]],0,0,-1) #该轮廓区域填0
continue
return img
src = cv.imread('01.png',0)
cv.imshow('input',src)
cv.waitKey(0)
src = Img1(src)
cv.imshow('output', src)
cv.waitKey()