本文是基于传统视频图像处理办法检测表计读数,作者资历尚浅,如有不足之处,欢迎指正,谢谢!
当时拿到手有如下的几个思路想法:
a、表计都是圆的,可以用hough变换圆形检测,再用mask掩膜提取,就可以完成位置的确定
b、表盘的颜色有一定的分布,比如外圈是黑色原框,内部是白色表盘底。可以先用聚类的方式让颜色聚类分开,便于区域分割。然后设定一些RGB判据进行二值化,就可以得出表盘轮廓从而提取。
c、模板匹配:这里分为直接模板匹配和surf特征点匹配找到对应匹配点,前者是不具有旋转拉伸不变性。
然后我拿到图,裂开
这嘛玩意歪成这样,好吧,我直接颜色聚类简单点处理算了。
所以我直接方案b走起,其他方法我也想过,可以操作但是有点麻烦,而且本人太菜!
keamns简介和代码,我之前写过,这里建个传送门
这里的核心思想就是表盘颜色接近白色和周围颜色有明显不同,所以kmeans聚类之后,使用二值化判断的方法把表轮廓的部分选择出来
这个图就是我二值化的结果,表盘部分很明显是内部白色外部黑色的圆形轮廓
这个时候就可以选择
1、可以hough变换找圆形或者拟合椭圆形去寻找
2、可以直接findcontour找到轮廓,然后用矩形框选,但是框选的时候应该添加一些条件,比如:
这是主要部分的代码
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上