C# OpenCV | 手把手教你用传统方法实现骰子识别

2022-02-08 17:00:57 浏览数 (1)

导读

本文给大家分享一个用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;
}

0 人点赞