C++ OpenCV检测并提取数字华容道棋盘

学更好的别人,

做更好的自己。

——《微卡智享》

94ee8929004108a65c2cccdd729b9906.png

本文长度为1999,预计阅读5分钟

前言

一直关注我的朋友应该知道前段时间使用OpenCV做了数字华容道的游戏及AI自动解题,相关文章《整活!我是如何用OpenCV做了数字华容道游戏!(附源码)》《趣玩算法--OpenCV华容道AI自动解题》,一直也想在现在的基础上再加些东西,就考虑到使用图像读取了棋盘,生成对应的棋局再自动AI解题。

像这样的图像识别,用深度学习的方法实现应该是最佳的,奈何自已也是刚开始自学,很多东西也不太了解,等入门后会更新相关的学习笔记,今天就先用OpenCV传统的方法处理。

3234af9bcddb4eaf3ab2a9c6d5d70fd6.png

Q1

如何实现图像读取数字华容道棋盘生成棋局?

虽然这是一个问题,不过要完成实现需要两个操作,就是定位棋盘和数字识别,那具体应该怎么实现呢?

 

1.定位并提取数字华容道棋盘(非深度学习方法),今天这篇就是来讲讲怎么实现提取数字华容道棋盘。

2.数字识别(OCR识别),以前文章中有在Android端调用过Tesseract,但PC端一直没装,最近也在看看有没有更合适的框架,所以这块还没定下,等弄好了我们继续做这步。

文中代码只显示核心的代码,文末会有源码的地址,想看源码的可以从地址中下载。

 

实现效果

62ef2e91554eb08f69a707a43b9569ba.gif

6bc177d3c640e4d205e1b331420e421b.png

48f2e0affaecbbdbaa269db635962836.png

e2011d50c324e6c8da0655ad62c8b4f5.png

4148e02df4ca3c50ce1a0cf506a75ab9.png

 

#实现思路
1图像预处理后进行边缘检测
2查找到最大的轮廓并且是4边形的轮廓
3将查找到的轮廓获取到最小旋转矩形进行透视变换
4提取出透视变换后的图像显示出来

 

代码实现

6f6541952f6ba6cfbc4f22ac14dabdbd.png

微卡智享

01

图像预处理后进行边缘检测

通常进行边缘检测时直接使用Canny边缘检测,因为检测速度也快,《C++ OpenCV使用大津法求自适应阈值》篇中也说过使用大津法求的自适应阈值,开始也是这样用的,后来发现为了检测的效果更好一些,这里采用了把图像R,G,B层分开边缘检测,然后再把三个分开的图像做与操作,最后出来的图像再做处理。

  vector<Mat> channels;
      Mat B_src, G_src, R_src, dstmat;
      split(src, channels);
      
      int minthreshold = 120, maxthreshold = 200;
    
      //B进行Canny
      //大津法求阈值
      CvUtils::GetMatMinMaxThreshold(channels[0], minthreshold, maxthreshold, 1);
      cout << "OTSUmin:" << minthreshold << "  OTSUmax:" << maxthreshold << endl;
      //Canny边缘提取
      Canny(channels[0], B_src, minthreshold, maxthreshold);


      //大津法求阈值
      CvUtils::GetMatMinMaxThreshold(channels[1], minthreshold, maxthreshold, 1);
      cout << "OTSUmin:" << minthreshold << "  OTSUmax:" << maxthreshold << endl;
      //Canny边缘提取
      Canny(channels[1], G_src, minthreshold, maxthreshold);


      //大津法求阈值
      CvUtils::GetMatMinMaxThreshold(channels[2], minthreshold, maxthreshold, 1);
      cout << "OTSUmin:" << minthreshold << "  OTSUmax:" << maxthreshold << endl;
      //Canny边缘提取
      Canny(channels[2], R_src, minthreshold, maxthreshold);
      
      bitwise_or(B_src, G_src, dstmat);
      bitwise_or(R_src, dstmat, dstmat);

70d83f3c056faf13e57558dbf74c3b13.png

上图中可以看到,中间三个分别是B,G,R三色分别通过Canny边缘求出的图,最右边的是将三个图像与操作后得到的轮廓图。

合并图像显示的代码

      Mat channelmat;
      resize(B_src, B_src, Size(0, 0), 0.4, 0.4);
      resize(G_src, G_src, Size(0, 0), 0.4, 0.4);
      resize(R_src, R_src, Size(0, 0), 0.4, 0.4);
      channelmat.push_back(B_src);
      channelmat.push_back(G_src);
      channelmat.push_back(R_src);
      CvUtils::SetShowWindow(channelmat, "channelmat", 600, 0);
      imshow("channelmat", channelmat);

02

查找到最大的轮廓并且是4边形的轮廓

图像的预处理边缘检测完了,就要开始查找图像中最大轮廓了,因为需要寻找数字华容道的棋盘,所以除了长最大面积外,还要考虑是四边形的轮廓,不是四边形的直接排除即可。找到符合条件的轮廓记录其轮廓编号,用于做下一步处理。

      vector<vector<Point>> contours;
      vector<Vec4i> hierarchy;
      findContours(dstmat, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);


      Mat dstcontour = Mat::zeros(dstmat.size(), CV_8SC3);
      Mat tmpcontour;
      dstcontour.copyTo(tmpcontour);


      //定义拟合后的多边形数组
      vector<vector<Point>> vtshulls(contours.size());


      for (int i = 0; i < contours.size(); ++i) {
        //判断轮廓形状,不是四边形的忽略掉
        double lensval = 0.01 * arcLength(contours[i], true);
        vector<Point> convexhull;
        approxPolyDP(Mat(contours[i]), convexhull, lensval, true);


        //拟合的多边形存放到定义的数组中
        vtshulls[i] = convexhull;


        //不是四边形的过滤掉
        if (convexhull.size() != 4) continue;


        //求出最小旋转矩形
        RotatedRect rRect = minAreaRect(contours[i]);
        //更新最小旋转矩形中面积最大的值
        if (rRect.size.height == 0) continue;
        
        if (rRect.size.area() > maxArea && rRect.size.area() > srcArea * 0.1
          && !CvUtils::CheckRectBorder(src, rRect)) {
          maxArea = rRect.size.area();
          maxAreaidx = i;
        }
      }

重点说明:

判断轮廓是否是四边形,首先通过计算轮廓的周长再乘0.01得到的值做为阈值,然后通过这个阈值对轮廓的点进行多边形拟合,拟合后的轮廓点个数来判断是不是四边形。

03

取出旋转矩形透视变换并提取

上一步找到符合条件的最大轮廓的编号后,我们单独对这个轮廓进行处理,处理的方式就是《C++ OpenCV透视变换改进---直线拟合的应用》篇中透视变换的改进-----采用直线拟合的方式。

      //找到符合条码的最大面积的轮廓进行处理
      if (maxAreaidx >= 0) {
        //获取最小旋转矩形
        RotatedRect rRect = minAreaRect(contours[maxAreaidx]);
        Point2f vertices[4];
        //重新排序矩形坐标点,按左上,右上,右下,左下顺序
        CvUtils::SortRotatedRectPoints(vertices, rRect);


        cout <<"Rect:" << vertices[0] << vertices[1] << vertices[2] << vertices[3] << endl;


        //根据获得的4个点画线
        for (int k = 0; k < 4; ++k) {
          line(dstcontour, vertices[k], vertices[(k + 1) % 4], Scalar(255, 0, 0));
        }


        //计算四边形的四点坐标
        Point2f rPoints[4];
        CvUtils::GetPointsFromRect(rPoints, vertices, vtshulls[maxAreaidx]);
        for (int k = 0; k < 4; ++k) {
          line(dstcontour, rPoints[k], rPoints[(k + 1) % 4], Scalar(255, 255, 255));
        }




        //采用离最小矩形四个点最近的重新设置范围,将所在区域的点做直线拟合再看看结果
        Point2f newPoints[4];
        CvUtils::GetPointsFromFitline(newPoints, rPoints, vertices);
        for (int k = 0; k < 4; ++k) {
          line(dstcontour, newPoints[k], newPoints[(k + 1) % 4], Scalar(255, 100, 255));
        }




        //根据最小矩形和多边形拟合的最大四个点计算透视变换矩阵    
        Point2f rectPoint[4];
        //计算旋转矩形的宽和高
        float rWidth = CvUtils::CalcPointDistance(vertices[0], vertices[1]);
        float rHeight = CvUtils::CalcPointDistance(vertices[1], vertices[2]);
        //计算透视变换的左上角起始点
        float left = dstcontour.cols;
        float top = dstcontour.rows;
        for (int i = 0; i < 4; ++i) {
          if (left > newPoints[i].x) left = newPoints[i].x;
          if (top > newPoints[i].y) top = newPoints[i].y;
        }


        rectPoint[0] = Point2f(left, top);
        rectPoint[1] = rectPoint[0] + Point2f(rWidth, 0);
        rectPoint[2] = rectPoint[1] + Point2f(0, rHeight);
        rectPoint[3] = rectPoint[0] + Point2f(0, rHeight);




        //计算透视变换矩阵    
        Mat warpmatrix = getPerspectiveTransform(rPoints, rectPoint);
        Mat resultimg;
        //透视变换
        warpPerspective(src, resultimg, warpmatrix, resultimg.size(), INTER_LINEAR);


        /*CvUtils::SetShowWindow(resultimg, "resultimg", 200, 20);
        imshow("resultimg", resultimg);*/


        //载取透视变换后的图像显示出来
        Rect cutrect = Rect(rectPoint[0], rectPoint[2]);
        Mat cutMat = resultimg(cutrect);
        
        CvUtils::SetShowWindow(cutMat, "cutMat", 600, 20);
        imshow("cutMat", cutMat);
      }

58e01e4dd469f7edbb7d7d245eaa4723.png

上图中根据最小外接矩形找到最近的点进行直接拟合,然后再做透视变换

75c75058b42a1a9950628c7c30945670.png

透视变换后的图像效果

0a07b3bdb635ecd0c1706e584a64abfc.png

最后在提取出透视变换后我们实际需要的部分

8c323a816aca36363bc116e4c3eac091.png

未检测成功的情况

提取的方法这样就说完了,从上面的动图中可以看到,不是所有的图像都提取出来,例如:

90ed1af2e6b0d21deeae04df56840de3.png

上面这张图就是背景太过复杂,边缘检测后找不到合适的轮廓
 

d591104e9af02a8b6d6db0c6af9c5f3c.png

上图中轮廓检测没问题,但是多边形拟合后得到的轮廓为5个点,

所以不认为是四边形

e907c044423c46cd57df286b87e4992d.png

行人这个肯定检测不出四边形

e301d83644ae4a5039c5a1e78cd73b1d.png

源码地址

https://github.com/Vaccae/OpenCVDemoCpp.git

GitHub上不去的朋友,可以击下方的原文链接跳转到码云的地址,关注【微卡智享】公众号,回复【源码】可以下载我的所有开源项目。

a3ecab2d7eaa3a48a95704f73d6e45f9.png

扫描二维码

获取更多精彩

微卡智享

3aa18fa4416869f42da063e05c3a72d5.png

「 往期文章 」

OpenCV旋转矩形RotatedRect的Points函数遇到的问题

C++ OpenCV使用大津法求自适应阈值

C++ OpenCV自适应阈值Canny边缘检测

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
OpenCV是一个开源的计算机视觉库,它可以用来处理图像和视频。在使用OpenCV进行移动检测时,一般需要以下步骤: 1. 读取视频:首先,需要使用OpenCV的函数读取视频文件或者调用摄像头来获取实时视频流。可以使用`cv2.VideoCapture()`函数来实现。 2. 提取帧:读取视频后,需要对每一帧图像进行处理。可以使用`capture.read()`函数来循环读取每一帧图像。 3. 转换为灰度图像:将每一帧的彩色图像转换为灰度图像,这样可以简化处理,并且加快计算速度。可以使用`cv2.cvtColor()`函数来实现。 4. 计算帧差:将连续帧之间的差异提取出来,可以使用帧差法或者光流法来计算。帧差法是一种简单有效的方法,通过计算当前帧和前一帧之间的差异来检测移动。 5. 过滤差异:对计算得到的帧差进行阈值化处理,将差异化的像素设置为白色,其他像素设置为黑色。可以使用`cv2.threshold()`函数来实现。 6. 膨胀和腐蚀:对二值化图像进行膨胀和腐蚀操作,可以去除掉小的噪点,并填充移动物体的空洞。可以使用`cv2.dilate()`和`cv2.erode()`函数来实现。 7. 轮廓检测:使用`cv2.findContours()`函数来检测移动物体的轮廓。可以设置阈值,来过滤掉面积较小或者边缘比较不规则的轮廓。 8. 绘制边界框:对于检测到的轮廓,可以使用`cv2.rectangle()`函数来绘制边界框,将移动物体框起来。 以上是使用OpenCV进行移动检测的基本步骤。根据具体的需求,还可以进行更复杂的处理,如运动目标跟踪、运动方向计算等。OpenCV的强大功能使得移动检测变得更加便捷和高效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vaccae

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值