【视觉项目】【day4】8.24号实验记录(消除瓶子内部“边缘”)

245 阅读9分钟

思路分析以及代码

思路1:使用findContours函数,设置轮廓为最外部RETR_EXTERNAL,结果发现结果仍然是所有轮廓。
思路2:先二值化,然后进行闭操作,然后canny,得到的轮廓确实比之前少很多,但是有个缺点:瓶子的边缘由于二值化的影响失真了。

原图直接canny得到的图二值化后canny图

思路3:先二值化,然后进行闭操作,然后填充较小的黑色区域,然后再进行腐蚀操作,得到了比原本瓶子要小一圈的mask,然后对canny后的图进行去轮廓操作(mask区域为黑),这样或许就可以了。

结果:确实好很多,但是有两张图片的没有结果,观察:有一个是因为孔洞填充选取的点有问题,还有一个 debug发现也是孔洞填充出现了问题。暂时现将大棕瓶区别对待。因为他的大津二值化结果有点特殊。
验证结果,发现效果确实比直接模板匹配canny后的好,但是仍然会有误判,发现误判主要出现在将大一点的瓶子的某部分识别成小瓶子,所以需要从瓶子像素多少来进行限制,规则是:不能识别成像素比模板瓶子像素少的类别。
所要做的:总结出模板图中瓶子像素值,得到每张测试图的瓶子像素(这里使用mask2的像素个数需要用连通域来进行甄别是否为,比实际瓶子要小)
空洞填充函数
输入:待处理图像的二值图
参数:背景颜色(黑还是白) 漫水填充的起始点(填充的是背景,一般选择(0,0))
输出:填充后的二值图

void My_hole_filling(Mat& srcImage, Mat& dstImage,int color,Point &startPoint)
{
	// Floodfill from point (0, 0) 以点(0,0)为种子点,进行漫水填充
	/*int x = startPoint.x;
	int y = startPoint.y;*/
	//srcImage.at<char>(x, y)
	if ( color== 255)		//背景为白
	{
		srcImage = ~srcImage;
	}
	Mat im_floodfill = srcImage.clone();
	floodFill(im_floodfill, startPoint, Scalar(255));
	//255
	// Invert floodfilled image 反转图像
	Mat im_floodfill_inv;
	bitwise_not(im_floodfill, im_floodfill_inv);

	// Combine the two images to get the foreground. 获得前景
	dstImage = (srcImage | im_floodfill_inv);
	dstImage = ~dstImage;
}

获取瓶子外轮廓的函数
输入:原图灰度图 canny阈值 输出:轮廓图 mask2图

void get_external_Contours_function(Mat& srcImage, Mat& dstImage,Mat& dstmask, int canny_thred)
{
	//模糊化降噪
	blur(srcImage, srcImage, Size(5, 5));
	Mat mask;
	//大津二值化
	threshold(srcImage, mask, 100, 255, THRESH_OTSU);
	//闭操作
	int Abs_offset = 2;
	Mat element = getStructuringElement(MORPH_ELLIPSE, Size(Abs_offset * 2 + 1, Abs_offset * 2 + 1), Point(Abs_offset, Abs_offset));	//返回的是内核矩阵
	morphologyEx(mask, mask, MORPH_CLOSE, element);
	//孔洞填充
	Point startpoint = Point(40,40);
	My_hole_filling(mask, mask, 0, startpoint);
	//将mask缩小一圈
	Mat mask2;
	Mat element_erode = getStructuringElement(MORPH_ELLIPSE, Size(Abs_offset * 2 + 1, Abs_offset * 2 + 1), Point(Abs_offset, Abs_offset));	//返回的是内核矩阵
	morphologyEx(mask, mask2, MORPH_DILATE, element_erode);
	dstmask = mask2;
	//mask2就是我们的掩膜
	//对模糊后的灰度图进行canny检测
	Canny(srcImage, dstImage, canny_thred, canny_thred * 2, 3);
	//将在mask2内的所有为白的像素置为黑
	int height = dstImage.rows;
	int width = dstImage.cols;
	for (int j = 0; j < height; j++)
	{
		for (int i = 0; i < width; i++)
		{
			if (mask2.at<uchar>(j, i) == 0 && dstImage.at<uchar>(j, i) == 255)
			{
				dstImage.at<uchar>(j, i) = 0;
			}
		}
	}
}

连通域测试代码,找出mask2中面积最大的连通域的像素个数

int main()
{
	cv::Mat srcMat = imread("D:\\opencv_picture_test\\视觉项目resize后的图片夹\\测试图片夹\\测试图\\10+麻点.jpg",1);
	//cv::Mat srcMat = imread("D:\\opencv_picture_test\\视觉项目resize后的图片夹\\测试图片夹\\测试图\\大棕瓶.jpg", 1);
	//cv::Mat srcMat = imread("D:\\opencv_picture_test\\视觉项目resize后的图片夹\\测试图片夹\\均衡化前的测试图\\方肩+肩薄.jpg", 1);
	Mat dstMat;
	Mat mask2;
	int thred = 40;
	//转换成灰度
	cvtColor(srcMat, dstMat, COLOR_BGR2GRAY);
	get_external_Contours_function(dstMat, dstMat, mask2, thred);
	//观察连通域个数,同时选出最大的那个连通域,之前对mask2进行反色
	mask2 = 255 - mask2;
	Mat lableMat;
	Mat statsMat;
	Mat centerMat;
	int nComp = cv::connectedComponentsWithStats(mask2,
	lableMat,
	statsMat,
	centerMat,
	8,
	CV_32S);
	//找出连通域像素个数最多的那个,然后记录下像素个数
	int max_pixels = 0;
	int max_pixels_label = 0;
	if (nComp == 1) max_pixels = statsMat.at<int>(1, 4);
	else
	{
		//找到像素点最多的连通域标记
		vector<int > pixels_nums;
		for (int i = 1; i < nComp; i++)
		{
			pixels_nums.push_back(statsMat.at<int>(i, 4));	//将连通域面积入vector
		}
		//找到最大的值并且返回它在vector的位置
		auto maxPosition = max_element(pixels_nums.begin(), pixels_nums.end());
		max_pixels = *maxPosition;
		max_pixels_label = maxPosition - pixels_nums.begin();
	}
	cout <<"连通域个数(算上背景)="<< nComp << endl;
	cout << "max_pixels = " << max_pixels << endl;
	cout << endl;
	imshow("die_on_chip", dstMat);
	waitKey(0);
	return 0;
}

结果:

获取地址成功
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\10+波纹.jpg
0 max_pixels = 14347
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\10+麻点.jpg
1 max_pixels = 13617
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\10+气泡+瓶口破裂.jpg
2 max_pixels = 13368
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\10.jpg
3 max_pixels = 14335
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\10_2.jpg
4 max_pixels = 12677
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\11.jpg
5 max_pixels = 11718
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\12.jpg
6 max_pixels = 12413
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\13+炸口.jpg
7 max_pixels = 8002
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\14.jpg
8 max_pixels = 8870
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\2+料纹.jpg
9 max_pixels = 18144
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\3.jpg
10 max_pixels = 16658
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\3_2.jpg
11 max_pixels = 15836
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\4+厚底.jpg
12 max_pixels = 17636
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\4+厚底2.jpg
13 max_pixels = 16468
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\4+炸肩.jpg
14 max_pixels = 15504
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\5+脖夹料.jpg
15 max_pixels = 19443
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\5+肩薄.jpg
16 max_pixels = 18623
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\5+气泡.jpg
17 max_pixels = 19209
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\5+炸口.jpg
18 max_pixels = 20063
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\5+皱纹气泡.jpg
19 max_pixels = 19552
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\6.jpg
20 max_pixels = 14913
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\6_2.jpg
21 max_pixels = 15616
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\6_3.jpg
22 max_pixels = 15653
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\7+厚底.jpg
23 max_pixels = 15158
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\7+厚底2.jpg
24 max_pixels = 13383
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\8.jpg
25 max_pixels = 14950
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\8_2.jpg
26 max_pixels = 15271
D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图\9.jpg
27 max_pixels = 13192

统计每个瓶子的像素个数(取最小的再减去2000作为标准)

判断序列像素个数
1暂不统计
218144
315836
415504
518623
614913
713383
814950
913192
10(A)12677
11(B)11718
12©12413
13(D)8002
14(E)8870

实现思路:获取测试图后,得到其mask2像素个数。与pixels_num_criterion[ ]中的元素相比较。
记录下满足(test_mask_pxiels>=pixels_num_criterion[i]-pixels_num_sub)的序号i,放入新的vector:prepare_template_num然后用序号属于其中的模板去匹配。
代码好像有点问题,存个档,明天检查一下:

//将测试图转换成与模板图相匹配的函数
//输入:测试图 canny算子阈值 输出:外轮廓图 
//返回值:该测试图mask2中的瓶子像素个数
int test_covertTo_Outer_contour(Mat& srcImg, Mat& dstImg, int thred)
{
	//这里我们批量处理
	Mat mask2;
	//int thred = 40;
	//转换成灰度
	cvtColor(srcImg, dstImg, COLOR_BGR2GRAY);
	get_external_Contours_function(dstImg, dstImg, mask2, thred);
	//观察连通域个数,同时选出最大的那个连通域,之前对mask2进行反色
	mask2 = 255 - mask2;
	Mat lableMat;
	Mat statsMat;
	Mat centerMat;
	int nComp = cv::connectedComponentsWithStats(mask2,
		lableMat,
		statsMat,
		centerMat,
		8,
		CV_32S);
	//找出连通域像素个数最多的那个,然后记录下像素个数
	int max_pixels = 0;
	int max_pixels_label = 0;
	if (nComp == 1) max_pixels = statsMat.at<int>(1, 4);
	else
	{
		//找到像素点最多的连通域标记
		vector<int > pixels_nums;
		//0是背景
		for (int i = 1; i < nComp; i++)
		{
			pixels_nums.push_back(statsMat.at<int>(i, 4));	//将连通域面积入vector
		}
		//找到最大的值并且返回它在vector的位置,然后还需要+1才是在连通域label中的位置
		auto maxPosition = max_element(pixels_nums.begin(), pixels_nums.end());
		max_pixels = *(maxPosition);
		max_pixels_label = (maxPosition - pixels_nums.begin() + 1);
	}
	return max_pixels;
}
int main()
{
	//改变控制台字体颜色
	system("color 02");
	//******************************************【0】获取测试文件夹路径和模板文件夹路径********************************************************//
	//获取测试文件夹路径和模板文件夹路径
	cv::String path_test = "D:/opencv_picture_test/视觉项目resize后的图片夹/测试图片夹/测试图/";        
	cv::String path_template = "D:/opencv_picture_test/视觉项目resize后的图片夹/模板图片夹/template外轮廓/";    
	cout << "获取地址成功" << endl;
	//******************************************【1】加载模板图像********************************************************//
	//创建模板vector
	vector<Mat>tempMat;
	//插入模板元素
	Mat srcImage;
	std::vector<cv::String> temp_filenames;
	cv::glob(path_template, temp_filenames);                 //opencv里面用来读取指定路径下文件名的一个很好用的函数
	for (int i = 0; i < temp_filenames.size(); i++)
	{
		srcImage = cv::imread(temp_filenames[i],0);
		tempMat.push_back(srcImage);
		cout << temp_filenames[i] << endl;
	}
	//获取模板数目
	int tempMat_Nums = tempMat.size();
	//******************************************【2】加载测试图像********************************************************//
	//创建测试vector
	vector<Mat>testMat;
	//插入测试元素
	std::vector<cv::String> test_filenames;
	cv::glob(path_test, test_filenames);                 //opencv里面用来读取指定路径下文件名的一个很好用的函数
	for (int i = 0; i < test_filenames.size(); i++)
	{
		srcImage = cv::imread(test_filenames[i]);
		testMat.push_back(srcImage);
		//cout << test_filenames[i] << endl;
	}
	//获取测试图数目
	int testMat_Nums = testMat.size();
	//******************************************【3】对每张测试图进行模板匹配********************************************************//
	for (int j = 0;j < testMat_Nums;j++)
	{
		cout <<"第"<< j <<"张测试图片的测试"<< endl;
		Mat resultMat;
		Mat CompareMat;
		Mat dispMat;
		//将测试图转换成与模板图相匹配的类型
		int test_mask_pxiels = 0;
		test_mask_pxiels=test_covertTo_Outer_contour(testMat[j], CompareMat, 40);
		cout << "test_mask_pxiels" << test_mask_pxiels << endl;
		int match_method = TM_CCORR_NORMED;		//经过试错发现此参数较好。
		//用每个模板去匹配测试图,并且找出每次结果的最佳匹配值,将值存入vector中
		vector<double>goodval;
		vector<Point>goodlock;
		int matchnum = 0;
		Point matchLoc;
		vector<int>prepare_template_num;
		cout << "可能的模板序号" << endl;
		for (int i = 0;i < 14;i++)
		{
			if (test_mask_pxiels >= (pixels_num_criterion[i] - pixels_num_sub))
			{
				//将符合规则的模板序号导入vector中
				prepare_template_num.push_back(i);
				cout << i <<" ";
			}
		}
		cout << endl;
		for (int x = 0;x < prepare_template_num.size();x++)
		{
			cout << prepare_template_num[x] << " ";
		}
		cout << endl;
		for (int i = 0;i < tempMat_Nums;i++)
		{
			//采用模板与目标图像像素与各自图像的平均值计算dot product,正值越大匹配度越高,负值越大图像的区别越大,但如果图像没有明显的特征(即图像中的像素值与平均值接近)则返回值越接近0;
			matchTemplate(CompareMat, tempMat[i], resultMat, match_method);
			//不归一化,因为不同模板归一化后的最佳值皆为1,无法比较
			//normalize(resultMat, resultMat, 0, 1, NORM_MINMAX, -1, Mat());	//归一化
			double minVal; double maxVal; Point minLoc; Point maxLoc;	//定义最大值最小值以及它们的位置变量
			minMaxLoc(resultMat, &minVal, &maxVal, &minLoc, &maxLoc, Mat());	//从结果矩阵中找到匹配度最大以及最小的值并且确定其位置
			//对于方法SQDIFF和SQDIFF_NORMED两种方法来讲,越小的值就有着更高的匹配结果
			//而其余的方法则是数值越大匹配效果越好
			if (match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED)
			{
				//将不符合像素数目规则的模板的可能性置1
				vector<int>::iterator result = find(prepare_template_num.begin(), prepare_template_num.end(), i); //查找该模板是否食欲符合规则的模板
				if (result == prepare_template_num.end()) //没找到
				{
					minVal = 1;
				}
				goodlock.push_back(minLoc);
				goodval.push_back(minVal);
			}
			else
			{
				//将不符合像素数目规则的模板的可能性置0
				vector<int>::iterator result = find(prepare_template_num.begin(), prepare_template_num.end(), i); //查找该模板是否食欲符合规则的模板
				if (result == prepare_template_num.end()) //没找到
				{
					maxVal = 0;
				}
				goodlock.push_back(maxLoc);
				goodval.push_back(maxVal);
			}
			show_probability(i, maxVal);
			//cout << i << "  " << maxVal << endl;
		}
		//找到goodval中最佳的一组
		if (match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED)
		{
			auto goodPosition = min_element(goodval.begin(), goodval.end());
			matchnum = distance(begin(goodval), goodPosition);
		}
		else
		{
			auto goodPosition = max_element(goodval.begin(), goodval.end());
			matchnum = distance(begin(goodval), goodPosition);
		}
		show_text(matchnum, test_filenames[j]);
		matchLoc = goodlock[matchnum];
		testMat[j].copyTo(dispMat);
		//以最佳匹配点为中心绘制与模板相同大小的框
		rectangle(dispMat, matchLoc, Point(matchLoc.x + tempMat[matchnum].cols, matchLoc.y + tempMat[matchnum].rows), Scalar::all(255), 2, 8, 0);
		namedWindow("testMat", WINDOW_NORMAL);//WINDOW_NORMAL允许用户自由伸缩
		imshow("testMat", dispMat);
		waitKey(30);
	}
	return 0;
}