[OpenCV+VS2015]表计读数识别(一):表计位置检测(表计歪的咋办!?)

[OpenCV+VS2015]表计读数识别(一):表计位置检测

本文是基于传统视频图像处理办法检测表计读数,作者资历尚浅,如有不足之处,欢迎指正,谢谢!

目录

  • [OpenCV+VS2015]表计读数识别(一):表计位置检测
    • 1 位置检测的思路
    • 2 位置提取
      • 2.1 kmeans颜色聚类
      • 2.2 表盘轮廓提取
    • 3 结果
    • 4 代码

1 位置检测的思路

当时拿到手有如下的几个思路想法:
a、表计都是圆的,可以用hough变换圆形检测,再用mask掩膜提取,就可以完成位置的确定
b、表盘的颜色有一定的分布,比如外圈是黑色原框,内部是白色表盘底。可以先用聚类的方式让颜色聚类分开,便于区域分割。然后设定一些RGB判据进行二值化,就可以得出表盘轮廓从而提取。
c、模板匹配:这里分为直接模板匹配surf特征点匹配找到对应匹配点,前者是不具有旋转拉伸不变性。

然后我拿到图,裂开
[OpenCV+VS2015]表计读数识别(一):表计位置检测(表计歪的咋办!?)_第1张图片
这嘛玩意歪成这样,好吧,我直接颜色聚类简单点处理算了。
所以我直接方案b走起,其他方法我也想过,可以操作但是有点麻烦,而且本人太菜!

2 位置提取

2.1 kmeans颜色聚类

keamns简介和代码,我之前写过,这里建个传送门

这里的核心思想就是表盘颜色接近白色和周围颜色有明显不同,所以kmeans聚类之后,使用二值化判断的方法把表轮廓的部分选择出来
[OpenCV+VS2015]表计读数识别(一):表计位置检测(表计歪的咋办!?)_第2张图片
这个图就是我二值化的结果,表盘部分很明显是内部白色外部黑色的圆形轮廓

2.2 表盘轮廓提取

这个时候就可以选择
1、可以hough变换找圆形或者拟合椭圆形去寻找
2、可以直接findcontour找到轮廓,然后用矩形框选,但是框选的时候应该添加一些条件,比如:

  • 表计的长方形框长宽比接近1:1;
  • 表计位置不会出出现在图片的边缘位置;
  • 表计不会过于狭窄;
  • 表计框选内部的颜色分量应该是白色居多;
    等等可以按照自己的需要添加条件

3 结果

最后截取效果
[OpenCV+VS2015]表计读数识别(一):表计位置检测(表计歪的咋办!?)_第3张图片
表盘就出来啦

4 代码

这是主要部分的代码

void Gauge_Detection(Mat& src, Mat& dst)
{
	Mat binary_image;
	Image trans_in, trans_out;

	MattoImage(src, trans_in);
	RGB_kmeans(trans_in, trans_out);
	ImagetoMat(trans_out, dst);

	//namedWindow("【kmeans聚类图像】", 0);
	//resizeWindow("【kmeans聚类图像】", 640, 480);
	//imshow("【kmeans聚类图像】",dst);

	Binary(dst, binary_image);
	namedWindow("【二值化图像】", 0);
	resizeWindow("【二值化图像】", 640, 480);
	imshow("【二值化图像】", binary_image);
	DrawFire(src, binary_image);

	//namedWindow("【kmeans聚类图像】", 0);
	//resizeWindow("【kmeans聚类图像】", 640, 480);
	//imshow("【kmeans聚类图像】", dst);
	//namedWindow("【二值化图像】", 0);
	//resizeWindow("【二值化图像】", 640, 480);
	//imshow("【二值化图像】", binary_image);
}
/*将导入的mat型变换到image*/
void MattoImage(Mat &src, Image &dst) {
	for (int i = 0; i < src.rows; i++) {
		Row row;
		for (int j = 0; j < src.cols; j++) {
			auto temp = src.at<Vec3b>(i, j);
			row.emplace_back(Color(temp[2], temp[1], temp[0]));
		}
		dst.push_back(row);
	}
}

void RGB_kmeans(Image &src, Image &dst)
{
	int i, j, s, it, t, pos;
	double diff;
	double mindiff = _mindiff;
	int Maxitr = _Maxitr;

	assert(!src.empty());
	for (i = 1; i < src.size(); i++) {
		assert(src[i].size() == src[0].size());
	}

	int rows = src.size();
	int cols = src[0].size();

	Mat cluster(src.size(), src[0].size(), CV_8UC1);
	dst.clear();
	//给定初始质心
	double temp = src.size() / k;
	if (temp < 1 || temp<0)
	{
		printf("the input 'k' error.please try again!");
	}

	int interval = (src.size() - src.size() % k) / k;
	int krow[k] = { 0 };
	int kcol[k] = { 0 };//用于存放初始化随机k个质心点位置

	int kr[k];
	int kg[k];
	int kb[k];
	double fact_kr[k];
	double fact_kg[k];//用于存放k个质心点位置
	double fact_kb[k];
	int pre_kr[k];
	int pre_kg[k];//用于存放k个质心点位置坐标
	int pre_kb[k];

	for (i = 0; i < k; i++)
	{
		krow[i] = i*interval; //选择了图片中心线上的k个点
		kcol[i] = (int)(src.size() / 2);
		kr[i] = src[krow[i]][kcol[i]][0];
		kg[i] = src[krow[i]][kcol[i]][1];
		kb[i] = src[krow[i]][kcol[i]][2];
		//if (i == 2) { printf("%d %d %d\n", kr[i], kg[i], kb[i]); }
	}
	//遍历图像中所有像素点,计算每个像素点和质心点的RGB距离,和哪个最近归到哪一类
	double distR, distG, distB, cur_dist, min_dist = 196000.0;
	double sumr, sumg, sumb, num;
	for (it = 0; it < Maxitr; it++) {
		printf("\n iter has ready!! The iter time is %d  \n", (it + 1));
		for (i = 0; i < rows; i++) {
			for (j = 0; j < cols; j++) {
				min_dist = 196000;
				for (s = 0; s < k; s++) {
					/*	printf("caculate distance!!\n");*/
					distR = pow(src[i][j][0] - kr[s], 2);
					distG = pow(src[i][j][1] - kg[s], 2);
					distB = pow(src[i][j][2] - kb[s], 2);
					cur_dist = distR + distG + distB;                         //计算颜色距离
					if (cur_dist < min_dist)
					{
						//说明对于当前点来说该点更靠近
						min_dist = cur_dist;
						cluster.at<uchar>(i, j) = s;    //对于这个像素点更靠近第s+1个聚类
					}
				}
			}
		}//完成聚类表 需要重新计算质心RGB!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!位置
		printf("完成聚类表 需要重新计算质心RGB位置!!\n");
		/*waitKey(0);*/
		for (s = 0; s < k; s++) {
			sumr = sumg = sumb = num = 0.0;
			for (i = 0; i < rows; i++) {
				for (j = 0; j < cols; j++) {
					if (cluster.at<uchar>(i, j) == s) {
						sumr += src[i][j][0];
						sumg += src[i][j][1];
						sumb += src[i][j][2];
						num++;
					}
				}
			}
			pre_kr[s] = kr[s];
			pre_kg[s] = kg[s];
			pre_kb[s] = kb[s];
			if (num == 0) {
				fact_kr[s] = 0.0;
				fact_kg[s] = 0.0;
				fact_kb[s] = 0.0;
			}
			else
			{
                fact_kr[s] = sumr / num;
			    fact_kg[s] = sumg / num;
			    fact_kb[s] = sumb / num;
			}
			kr[s] = int(fact_kr[s]);
			kg[s] = int(fact_kg[s]);
			kb[s] = int(fact_kb[s]);
		}
		//退出循环条件
		//waitKey(0);
		t = 0;
		for (s = 0; s < k; s++) {
			diff = pow((fact_kr[s] - pre_kr[s]), 2) + pow((fact_kg[s] - pre_kg[s]), 2) + pow((fact_kb[s] - pre_kb[s]), 2);
			if (diff <= mindiff) {
				t++;
			}
		}
		printf("cluster has renew!!\n");
		printf("t = %d , s = %d!!\n", t, s);
		for (i = 0; i < k; i++)
		{
			printf("( %d , %d , %d )\t ", kr[i], kg[i], kb[i]);
		}
		printf("\n");
		printf("\n");
		/*waitKey(0);*/
		if (t == k)
		{
			printf("kmeans has done,cluster complete!\n");
			break;
		}
	}
	printf("has gotto real dot!!\n");
	/*waitKey(0);*/
	//此时已经求得k个真正的聚类点,需要更新新的聚类表cluster[][];
	for (i = 0; i < rows; i++) {
		for (j = 0; j < cols; j++) {
			min_dist = 196000;
			for (s = 0; s < k; s++) {
				distR = pow(src[i][j][0] - kr[s], 2);
				distG = pow(src[i][j][1] - kg[s], 2);
				distB = pow(src[i][j][2] - kb[s], 2);
				cur_dist = distR + distG + distB;
				if (cur_dist < min_dist)
				{
					//说明对于当前点来说该点更靠近
					min_dist = cur_dist;
					cluster.at<uchar>(i, j) = s;    //对于这个像素点更靠近第s+1个聚类
				}
			}
		}
	}
	printf("此时已经求得k个真正的聚类点,需要更新新的聚类表cluster[][]完成!!\n");

	for (i = 0; i < rows; i++) {
		Row dst_row;
		for (j = 0; j < cols; j++) {

			Color cur_color = src[i][j];
			pos = cluster.at<uchar>(i, j);
			cur_color[0] = kr[pos];
			cur_color[1] = kg[pos];
			cur_color[2] = kb[pos];
			if (pos == 2) {
				//cur_color[0] = 250;
				//cur_color[1] = 0;
				//cur_color[2] = 0;
				//printf("bbbbbb!");
			}
			dst_row.push_back(cur_color);
			//printf("3\n");
			//waitKey(0);
		}
		dst.push_back(dst_row);
	}
}

void ImagetoMat(Image &src, Mat &dst)
{
	for (unsigned long i = 0; i < src.size(); i++)
	{
		for (unsigned long j = 0; j < src[0].size(); j++)
		{
			dst.at<Vec3b>(i, j)[0] = src[i][j][2];
			dst.at<Vec3b>(i, j)[1] = src[i][j][1];
			dst.at<Vec3b>(i, j)[2] = src[i][j][0];
		}
	}  // convert Image to Mat
}

void Binary(Mat& src, Mat& dst) {
	int i, j, r, g, b, black = 0, white = 0;
	dst = Mat::zeros(src.size(), CV_8UC1);

	Mat mid;

	Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	erode(src, mid, element);

	for (i = 0; i < mid.rows; i++) {
		for (j = 0; j < mid.cols; j++) {
			r = mid.at<Vec3b>(i, j)[2];
			g = mid.at<Vec3b>(i, j)[1];
			b = mid.at<Vec3b>(i, j)[0];

			if gauge_criterion   //((g - b)>30) &&
			{
				dst.at<uchar>(i, j) = 255;
			white++;
			}
			else
			{
				dst.at<uchar>(i, j) = 0;
				black++;
			}
		}
	}
	//for (i = 0; i < src.rows; i++) {
	//	for (j = 0; j < src.cols; j++) {
	//		r = src.at(i, j)[2];
	//		g = src.at(i, j)[1];
	//		b = src.at(i, j)[0];

	//		if gauge_criterion   //((g - b)>30) &&
	//		{
	//			dst.at(i, j) = 255;
	//			white++;
	//		}
	//		else
	//		{
	//			dst.at(i, j) = 0;
	//			black++;
	//		}
	//	}
	//}
}

void DrawFire(Mat &inputImg, Mat foreImg)
{
	vector<vector<Point>> contours_set;//保存轮廓提取后的点集及拓扑关系  

	findContours(foreImg, contours_set, RETR_EXTERNAL, CHAIN_APPROX_NONE);

	Mat result0;
	Scalar holeColor;
	Scalar externalColor;

	vector<vector<Point>>::iterator iter = contours_set.begin();
	for (; iter != contours_set.end(); )   //迭代器循环
	{
		Rect rect = boundingRect(*iter);

		/*	float radius;
		Point2f center;
		minEnclosingCircle(*iter, center, radius);*/
		if draw_criterion
		{
			gauge_number ++;
			rectangle(inputImg, rect, Scalar(0, 0, 255), 5); 
			printf("\n %d  %d ( %d %d) ", rect.height, rect.width,rect.x, rect.y);
			if (gauge_number == 1) { lefttop1.x = rect.x; lefttop1.y = rect.y; }
			if (gauge_number == 2) { lefttop2.x = rect.x; lefttop2.y = rect.y; }
			++iter;
			Gauge_position.push_back(rect.x);
			Gauge_position.push_back(rect.y);
			Gauge_position.push_back(rect.width);
			Gauge_position.push_back(rect.height);
			Gauge_Image.push_back(Gauge_position);
			Gauge_position.clear();
		}
		else
		{
			iter = contours_set.erase(iter);
		}
	}
	//namedWindow("【表计位置检测】", 0);
	//resizeWindow("【表计位置检测】", 640, 480);
	//imshow("【表计位置检测】", inputImg);

}

void Image_Capture(Mat& src, Mat& gauge_image_1,Mat& gauge_image_2) 
{
	vector<int> read;
	int image_x, image_y, image_width, image_height;

	if (gauge_number == 1)
    {
	    read = Gauge_Image[0];
	    image_x = read[0];
	    image_y = read[1];
	    image_width = read[2];
	    image_height = read[3];

	    printf("输出表计图像...\n %d %d %d %d",image_x,image_y,image_width,image_height);
		Rect rect(image_x, image_y, image_width, image_height);
	    gauge_image_1 = src(rect);

		imshow("【表计1】", gauge_image_1);
	}
	if (gauge_number == 2)
	{
		read = Gauge_Image[0];
		image_x = read[0];
		image_y = read[1];
		image_width = read[2];
		image_height = read[3];
		Rect rect1(image_x, image_y, image_width, image_height);
		gauge_image_1 = src(rect1);

		read = Gauge_Image[1];
		image_x = read[0];
		image_y = read[1];
		image_width = read[2];
		image_height = read[3];
		Rect rect2(image_x, image_y, image_width, image_height);
		gauge_image_2 = src(rect2);

		imshow("【表计1】", gauge_image_1);
	}
	
 //   vector::iterator iter = read.begin();
	//for (; iter < read.end();iter++)   //迭代器不可以越界,<而不是<=
	//{
	//	printf("  %d  ", *iter);
	//}

}
 

欢迎指正,后续会补上全部代码于GitHub上

你可能感兴趣的:(opencv,c++)