OpenCV之对象分割和检测(一)

一,预处理
1,噪声消除
如果不去除噪声,就会检测到比预期更多的对象,中值滤波器通常用于去除椒盐噪声。

Mat img_noise;
medianBlur(img, img_noise, 3);

中值模糊函数需要三个参数:
带有1、3或4通道的输入图像,当内核大于5时,图像深度只能是CV_8U
输出图像,与输入图像相同类型和深度,应用中值模糊算法后的结果图像
内核大小,孔径大于1的奇数
2,用光模式移除背景进行分割
如果采用基本阀值得到的图像伪影有很多白噪声,如果采用光模式和背景去除技术,可以获得一个非常好的结果。需要一张没有任何对象的场景图片,该图片从完全相同的位置并在拍摄其他图像的相同光照条件下拍摄,用简单的数学运算就可以去除这种光模式
减法
减法操作是最简单的方法。如果我们有光模式L和图像模式I,则得到的去除结果R是它们的差:R = L - I
除法
除法有点复杂,R = 255*( 1 - ( I / L ))
在这种情况下,将图像模式除以光模式,假设光模式是白色并且对象比背景更暗,则图像像素值总是等于或小于光像素值。I/L获得的结果在0~ 1之间。最后,将该除法的结果反转以获得相同的颜色方向范围,并将它乘以255以获得0~255范围内的值。

Mat removeLight(Mat img, Mat pattern, int method)
{
	Mat aux;
	//if method is normalization
	if (method == 1)
	{
		//Require change our image to 32 float for division
		Mat img32, pattern32;
		img.convertTo(img32, CV_32F);
		pattern.convertTo(pattern32, CV_32F);
		//Divide the image by the pattern
		aux = 1 - (img32 / pattern32);
		//Convert 8 bits format and scale
		aux.convertTo(aux, CV_8U, 255);
	}
	else
	{
		aux = pattern - img;
	}
	return aux;
}

除法需要32位浮点图像,以便分割图像,而不是将数字截断为整数。

convertTo(OutputArray dst, int rtype, double alpha, double beta)

O(x,y) = cast( alpha * I(x,y) + beta )
如果没有光/背景模式,使用过滤器创建一个可以使用的光/背景模式,为了估计背景图像,我们将使用应用于输入图像的大内核尺寸的模糊技术。

Mat calculateLightPattern(Mat img)
{
	Mat pattern;
	//Basic and effective way to calculate the light pattern from one image
	blur(img, pattern, Size(img.cols / 3, img.cols / 3));
	return pattern;
}

3,二值化
删除背景后,需要对图像进行二值化,以便进行分割。
Threshold如果像素值大于阀值,则将像素的值设置为最大值(255),如果像素的值低于阀值,则将其设置为最小值(0)

Mat img_thr;
if (method_light != 2)
	threshold(img_no_light, img_thr, 30, 255, THRESH_BINARY);
else
	threshold(img_no_light, img_thr, 140, 255, THRESH_BINARY_INV);

当移除光/背景时,采用30作为threshold值,因为应用了背景移除所有不感兴趣的区域都是黑色的,当不使用光移除方法时,采用中等的threshold值140,因为有白色背景。
二,分割输入图像
1,连通组件
连通组件算法是一种迭代算法,其目的是采用八个或四个连通像素来标记图像。如果两个像素具有相同的值并且是邻居,则把它们连接起来。

int connectedComponents(InputArray image, OutputArray labels, int connectivity = 8, int ltype = CV_32S);
int connectedComponentsWithStats(InputArray image, OutputArray labels, OutputArray stats, OutputArray centroids, int connectivity = 8, int ltype = CV_32S);

连通组件算法,返回一个整数,其中包含检测到的标签数,标签0代表背景,这两个函数的区别是返回的信息不同。
image输入图像,labels输出标签图像,connectivity采用的连通方法(4或8),ltype返回标签图像的类型,(CV32_S和CV16_U),默认情况CV32_S。stats是一个输出参数,它给出每个标签(包括背景)以下的统计值:
CC_STAT_LEFT:连通组件对象的最左侧x坐标
CC_STAT_TOP:连通组件对象的最顶部y坐标
CC_STAT_WIDTH:由其边界框定义的连通组件对象的宽度
CC_STAT_HEIGHT:由其边界框定义的连通组件对象的高度
CC_STAT_AREA:连通组件对象的像素数量(区域)
centroids指向每个标签的浮点类型,包括为另一个连通组件考虑的背景。

static Scalar randomColor(RNG& rng)
{
	int icolor = (unsigned)rng;
	return Scalar(icolor & 255, (icolor >> 8) & 255, (icolor >> 16) & 255);
}

void ConnectedComponents(Mat img)
{
	//Use connected components to divide our image in multiple connected component objects
	Mat labels;
	auto num_objects = connectedComponents(img, labels);
	//Check the number of objects detected
	if (num_objects < 2)
	{
		cout << "No objects detected" << endl;
		return;
	}
	else
	{
		cout << "Number of objects detected:" << num_objects - 1 << endl;
	}
	//Create output image coloring the objects
	Mat output = Mat::zeros(img.rows, img.cols, CV_8UC3);
	RNG rng(0xFFFFFFFF);
	for (auto i = 1; i < num_objects; i++)
	{
		Mat mask = labels == i;
		output.setTo(randomColor(rng), mask);
	}
	imshow("Result", output);
}

void ConnectedComponentsStats(Mat img)
{
	//Use connected components with stats
	Mat labels, stats, centroids;
	auto num_objects = connectedComponentsWithStats(img, labels, stats, centroids);
	//Check the number of objects detected
	if (num_objects < 2)
	{
		cout << "No objects detected" << endl;
		return;
	}
	else
	{
		cout << "Number of objects detected:" << num_objects - 1 << endl;
	}
	//Create output image coloring the objects and show area
	Mat output = Mat::zeros(img.rows, img.cols, CV_8UC3);
	RNG rng(0xFFFFFFFF);
	for (auto i = 1; i < num_objects; i++)
	{
		cout << "Object " << i << " with pos: " << centroids.at<Point2d>(i) << "with area " << stats.at<int>(i, CC_STAT_AREA) << endl;
		Mat mask = labels == i;
		output.setTo(randomColor(rng), mask);
		//draw text with area
		stringstream ss;
		ss << "area: " << stats.at<int>(i, CC_STAT_AREA);
		putText(output, ss.str(), centroids.at<Point2d>(i), FONT_HERSHEY_SIMPLEX, 0.4, Scalar(255, 255, 255));
	}
	imshow("Result", output);
}

2,查找轮廓

void findContours( InputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset = Point());

image输入图像,contours轮廓的输出,其中每个检测到的轮廓是点的向量,可选的输出向量,用于保存轮廓的层次结构。层次结构表示为四个索引的向量,它们是(下一个轮廓,前一个轮廓,第一个子轮廓,父轮廓)。检索轮廓的模式:
RETR_EXTERNAL 仅检索外部轮廓
RETR_LIST 在不建立层次结构的情况下检索所有轮廓
RETR_CCOMP检索具有两级层次结构(外部和孔)的所有轮廓,如果另一个对象位于一个孔内,则将其放在层次结构的顶部。
RETR_TREE 检索所有轮廓,在轮廓之间创建完整的层次结构
method使我们能用近似方法来检索轮廓的形状
CV_CHAIN_APPROX_NONE 不对轮廓应用任何近似方法并存储轮廓点
CV_CHAIN_APPROX_SIMPLE 压缩所有水平、垂直和对角线段,公存储起点和终点
CV_CHAIN_APPROX_TC89_L1和CV_CHAIN_APPROX_TC89_KCOS应用Telchin链式近似算法

void drawContours( InputOutputArray image, InputArrayOfArrays contours,
                          int contourIdx, const Scalar& color,
                          int thickness = 1, int lineType = LINE_8,
                          InputArray hierarchy = noArray(),
                          int maxLevel = INT_MAX, Point offset = Point() );

image要绘制轮廓的输出图像,contours轮廓的向量,表示要绘制轮廓的索引,如果是负数则绘制所有轮廓,color绘制轮廓的颜色,thickness如果为负,则使用所选颜色填充轮廓,lineType指定是否要使用消除锯齿方法或其他绘图方法进行绘制,hierarchy可选参数,仅仅在你想绘制某些轮廓时才需要,maxLevel可选参数,hierarchy参数可用时使用该参数,为0仅绘制指定轮廓,为1绘制当前轮廓和嵌套轮廓,为2绘制所有指定轮廓层次结构,offset用于移动轮廓的可选参数

void FindContoursBasic(Mat img)
{
	vector<vector<Point>> contours;
	findContours(img, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	Mat output = Mat::zeros(img.rows, img.cols, CV_8UC3);
	//Check the number of objects detected
	if (contours.size() == 0)
	{
		cout << "No objects detected" << endl;
		return;
	}
	else
		cout << "Number of objects detected: " << contours.size() << endl;
	RNG rng(0xFFFFFFFF);
	for (auto i = 0; i < contours.size(); i++)
	{
		drawContours(output, contours, i, randomColor(rng));
		imshow("Result", output);
	}
}

main调用

const char* keys =
{
	"{help h usage ? | | print this message}"
	"{@image || Image to process}"
	"{@lightPattern || Image light pattern to apply to image input}"
	"{lightMethod | 1 | Method to remove backgroun light, 0 differenec, 1 div, 2 no light removal' }"
	"{segMethod | 1 | Method to segment: 1 connected Components, 2 connectec components with stats, 3 find Contours }"
};

int main(int argc, const char** argv)
{
	CommandLineParser parser(argc, argv, keys);
	//If requires help show
	if (parser.has("help"))
	{
		parser.printMessage();
		return 0;
	}

	String img_file = parser.get<String>(0);
	String light_pattern_file = parser.get<String>(1);
	auto method_light = parser.get<int>("lightMethod");
	auto method_seg = parser.get<int>("segMethod");

	// Check if params are correctly parsed in his variables
	if (!parser.check())
	{
		parser.printErrors();
		return 0;
	}

	// Load image to process
	Mat img = imread(img_file, 0);
	if (img.data == NULL) {
		cout << "Error loading image " << img_file << endl;
		return 0;
	}

	// Remove noise
	Mat img_noise, img_box_smooth;
	medianBlur(img, img_noise, 3);
	blur(img, img_box_smooth, Size(3, 3));

	// Load image to process
	Mat light_pattern = imread(light_pattern_file, 0);
	if (light_pattern.data == NULL) {
		// Calculate light pattern
		light_pattern = calculateLightPattern(img_noise);
	}
	medianBlur(light_pattern, light_pattern, 3);

	//Apply the light pattern
	Mat img_no_light;
	img_noise.copyTo(img_no_light);
	if (method_light != 2) {
		img_no_light = removeLight(img_noise, light_pattern, method_light);
	}

	// Binarize image for segment
	Mat img_thr;
	if (method_light != 2) {
		threshold(img_no_light, img_thr, 30, 255, THRESH_BINARY);
	}
	else {
		threshold(img_no_light, img_thr, 140, 255, THRESH_BINARY_INV);
	}

	// Show images
	//imshow("Light pattern", light_pattern);
	//imshow("No Light", img_no_light);

	switch (method_seg) {
	case 1:
		ConnectedComponents(img_thr);
		break;
	case 2:
		ConnectedComponentsStats(img_thr);
		break;
	case 3:
		FindContoursBasic(img_thr);
		break;
	}

	waitKey(0);
	return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值