导读
本文给大家分享一个用C# OpenCV传统方法实现骰子识别的小案例。
背景介绍
这个案例是2018年初我给别人做的一个小项目,当时还没有现在这么多可以即拿即用的目标检测网络(比如SSD/Yolo等),所以当时是用传统的图像处理方法实现的。后来我的TensorFlow视频教程中也拿它来做目标检测案例讲解过:
TensorFlow基础与应用实战高清视频教程
接下来我们介绍用传统图像处理方法实现骰子识别的详细步骤!
实现步骤
一、需求分析
1. 图片如上,骰子容器是透明的,骰子数量固定为3颗,骰子本体颜色为白色,点数颜色有黑色和红色;
2. 骰子可能会出现紧靠、轻微反光和倾斜的情况;
3. 在正常情况下(倾斜和反光不严重),准确识别每个骰子点数,单张图片识别不超过300ms。
二、实现步骤
测试图片:
【1】 通过HSV阈值提取骰子本体;
代码语言:javascript复制int h_min = 0, s_min = 0, v_min = 200;
int h_max = 180, s_max = 80, v_max = 255;
CvInvoke.CvtColor(img2, hsv_img, ColorConversion.Bgr2Hsv);//Bgr转Hsv
ScalarArray hsv_min = new ScalarArray(new MCvScalar(h_min, s_min, v_min));
ScalarArray hsv_max = new ScalarArray(new MCvScalar(h_max, s_max, v_max));
CvInvoke.InRange(hsv_img, hsv_min, hsv_max, mask);
CvInvoke.MedianBlur(mask, mask, 5);
CvInvoke.Imwrite("mask.jpg", mask);
【2】先通过找轮廓方法,填充孔洞,然后做距离变换 阈值处理进一步定位骰子本体;
代码语言:javascript复制VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
CvInvoke.FindContours(mask, contours, null, RetrType.External, ChainApproxMethod.ChainApproxNone);
CvInvoke.DrawContours(mask, contours, -1, new MCvScalar(255, 255, 255), -1);
Mat dist_transform = new Mat();
Mat sure_fg = new Mat();
CvInvoke.DistanceTransform(mask, dist_transform, null, DistType.L2, 5);
CvInvoke.ConvertScaleAbs(dist_transform, dist_transform, 1, 0); //32F转8U
CvInvoke.Imwrite("dist_transform.jpg", dist_transform);
double[] minValues, maxValues;
Point[] minLoc, maxLoc;
dist_transform.MinMax(out minValues, out maxValues, out minLoc, out maxLoc);
CvInvoke.Threshold(dist_transform, sure_fg, maxValues[0] * 0.7, 255, ThresholdType.Binary); //二值化
CvInvoke.Imwrite("sure_fg.jpg", sure_fg);
【3】找轮廓,按照固定大小截取ROI:
代码语言:javascript复制Mat zero_img = sure_fg.Clone();
VectorOfVectorOfPoint contours2 = new VectorOfVectorOfPoint();
CvInvoke.FindContours(zero_img, contours2, null, RetrType.External, ChainApproxMethod.ChainApproxNone);
for (int i = 0; i < contours2.Size; i )
{
RotatedRect rotatedRect = CvInvoke.MinAreaRect(contours2[i]);
int x0 = (int)rotatedRect.Center.X;
int y0 = (int)rotatedRect.Center.Y;
Mat ROI = new Mat(imgInput, new Rectangle(x0-150, y0-150, 300, 300)).Clone();
CvInvoke.Imshow("ROI", ROI);
//CvInvoke.WaitKey(0);
int num = DiceRecognize(ROI);
CvInvoke.Circle(resultImg, new Point(x0, y0), 150, new MCvScalar(0, 0, 255), 10);
CvInvoke.PutText(resultImg, num.ToString(), new Point(x0-150, y0-150), FontFace.HersheyComplexSmall, 6,
new MCvScalar(0, 255, 0), 10);
}
【4】对每个ROI图像分别识别点数,可以使用Blob检测来判断点数,也可以用模板匹配来判断,下面以模板匹配为例:
模板图:
识别结果与代码:
代码语言:javascript复制private int DiceRecognize(Mat imgROI)
{
int diceNum = 0;
int diceNum1 = 0, diceNum2 = 0, diceNum3 = 0, diceNum4 = 0;
Mat black_img = Mat.Zeros(imgROI.Rows, imgROI.Cols, DepthType.Cv8U, 1);
Mat black_img2 = Mat.Zeros(imgROI.Rows, imgROI.Cols, DepthType.Cv8U, 1);
Mat black_img3 = Mat.Zeros(imgROI.Rows, imgROI.Cols, DepthType.Cv8U, 1);
Mat black_img4 = Mat.Zeros(imgROI.Rows, imgROI.Cols, DepthType.Cv8U, 1);
int w1 = template1.Cols, h1 = template1.Rows;
int w2 = template2.Cols, h2 = template2.Rows;
int w3 = template3.Cols, h3 = template3.Rows;
int w4 = template4.Cols, h4 = template4.Rows;
int matchImg1_rows = imgROI.Rows - h1 1;
int matchImg1_cols = imgROI.Cols - w1 1;
int matchImg2_rows = imgROI.Rows - h2 1;
int matchImg2_cols = imgROI.Cols - w2 1;
int matchImg3_rows = imgROI.Rows - h3 1;
int matchImg3_cols = imgROI.Cols - w3 1;
int matchImg4_rows = imgROI.Rows - h4 1;
int matchImg4_cols = imgROI.Cols - w4 1;
double matchThres = 0.62;
Mat matchImg1 = new Mat(matchImg1_rows, matchImg1_cols, DepthType.Cv32F, 1);
Mat matchImg2 = new Mat(matchImg2_rows, matchImg2_cols, DepthType.Cv32F, 1);
Mat matchImg3= new Mat(matchImg3_rows, matchImg3_cols, DepthType.Cv32F, 1);
Mat matchImg4 = new Mat(matchImg4_rows, matchImg4_cols, DepthType.Cv32F, 1);
CvInvoke.MatchTemplate(imgROI, template1, matchImg1, TemplateMatchingType.CcoeffNormed);
CvInvoke.MatchTemplate(imgROI, template2, matchImg2, TemplateMatchingType.CcoeffNormed);
CvInvoke.MatchTemplate(imgROI, template3, matchImg3, TemplateMatchingType.CcoeffNormed);
CvInvoke.MatchTemplate(imgROI, template4, matchImg4, TemplateMatchingType.CcoeffNormed);
Image<Gray, Single> ImgMatch = matchImg1.ToImage<Gray, Single>();
Image<Gray, Single> ImgMatch2 = matchImg2.ToImage<Gray, Single>();
Image<Gray, Single> ImgMatch3 = matchImg3.ToImage<Gray, Single>();
Image<Gray, Single> ImgMatch4 = matchImg4.ToImage<Gray, Single>();
int tempW = 0, tempH = 0;
for (int i = 0; i < matchImg1_rows; i )
{
for (int j = 0; j < matchImg1_cols; j )
{
float matchValue = ImgMatch.Data[i, j, 0];
//if (matchValue >= matchThres && (Math.Abs(j - tempW) > 50) && (Math.Abs(i - tempH) > 50))
if (matchValue >= matchThres )
{
diceNum1 ;
tempW = j;
tempH = i;
CvInvoke.Circle(ImgMatch, new Point(j, i), 50, new MCvScalar(0), -1);
//CvInvoke.Imshow("change", ImgMatch);
}
}
}
for (int i = 0; i < matchImg2_rows; i )
{
for (int j = 0; j < matchImg2_cols; j )
{
float matchValue = ImgMatch2.Data[i, j, 0];
//if (matchValue >= matchThres && (Math.Abs(j - tempW) > 50) && (Math.Abs(i - tempH) > 50))
if (matchValue >= matchThres)
{
diceNum2 ;
tempW = j;
tempH = i;
CvInvoke.Circle(ImgMatch2, new Point(j, i), 50, new MCvScalar(0), -1);
//CvInvoke.Imshow("change", ImgMatch);
}
}
}
for (int i = 0; i < matchImg3_rows; i )
{
for (int j = 0; j < matchImg3_cols; j )
{
float matchValue = ImgMatch3.Data[i, j, 0];
//if (matchValue >= matchThres && (Math.Abs(j - tempW) > 50) && (Math.Abs(i - tempH) > 50))
if (matchValue >= matchThres)
{
diceNum3 ;
tempW = j;
tempH = i;
CvInvoke.Circle(ImgMatch3, new Point(j, i), 50, new MCvScalar(0), -1);
//CvInvoke.Imshow("change", ImgMatch);
}
}
}
for (int i = 0; i < matchImg4_rows; i )
{
for (int j = 0; j < matchImg4_cols; j )
{
float matchValue = ImgMatch4.Data[i, j, 0];
//if (matchValue >= matchThres && (Math.Abs(j - tempW) > 50) && (Math.Abs(i - tempH) > 50))
if (matchValue >= matchThres)
{
diceNum4 ;
tempW = j;
tempH = i;
CvInvoke.Circle(ImgMatch4, new Point(j, i), 50, new MCvScalar(0), -1);
//CvInvoke.Imshow("change", ImgMatch);
}
}
}
Console.WriteLine("------");
Console.WriteLine(diceNum1.ToString());
Console.WriteLine(diceNum2.ToString());
Console.WriteLine(diceNum3.ToString());
Console.WriteLine(diceNum4.ToString());
Console.WriteLine("------");
diceNum = Math.Max(Math.Max(diceNum1, diceNum2), Math.Max(diceNum3, diceNum4));
return diceNum;
}