OpenCV C++案例实战二十三《网孔检测》

OpenCV C++案例实战二十三《网孔检测》


前言

前段时间,有位粉丝私信我,给我发了一张图片,如下图所示:
请添加图片描述
在这里贴出他的原话。
在这里插入图片描述
从他给的图片分析,该图存在遮挡,所以不能简单的二值化,然后提取图像轮廓去寻找结果。所以,我就想如何去掉这些遮挡物(即图像修复)。从图像可知,该遮挡物是黄色的线,所以,我就想可否使用hsv色彩空间提取出黄色,然后得到二值掩模图像,最后对原图进行修复。接下来,就一起看看是如何一步步实现的吧。

一、HSV通道转换

通过hsv通道转换,可以提取出图像中的黄色分量。

    //hsv颜色通道转换,提取图像中黄色线部分,生成掩膜图像
    Mat hsv;
    cvtColor(src, hsv, COLOR_BGR2HSV);
Mat mask<span class="token punctuation">;</span>
<span class="token function">inRange</span><span class="token punctuation">(</span>hsv<span class="token punctuation">,</span> <span class="token function">Scalar</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">Scalar</span><span class="token punctuation">(</span><span class="token number">40</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">)</span><span class="token punctuation">,</span> mask<span class="token punctuation">)</span><span class="token punctuation">;</span>

    结果如图所示:
    请添加图片描述

    二、图像修复

    关于图像修复的相关知识可以参考我之前的博文。这里就不细说了。
    OpenCV C++案例实战十四《图像修复》
    OpenCV C++案例实战十七《图像去水印》

    我们拿到上面的mask掩模图像,需要对其进行膨胀处理,使修复区域范围扩大。

        //将生成的掩膜mask膨胀一下,使掩膜区域放大
        Mat kernel = getStructuringElement(MORPH_RECT, Size(9, 9));
        dilate(mask, mask, kernel);
    
     
     

      请添加图片描述
      接下来,需要对图像进行修复。这里我提供两种修复方法,一种是OpenCV提供的inpaint函数,一种是我自己写的。

      2.1 OpenCV函数实现

          //使用OpenCV自带的inpaint函数进行图像修复,得到目标图像
          Mat inpaintImg;
          inpaint(src, mask, inpaintImg, 1, INPAINT_NS);
      
       
       

        请添加图片描述
        效果如图所示。

        2.2 MyFunction

        通过修改图像像素达到图像修复的效果。具体请看源码注释。

            //自己写的算法,修改图像像素,完成图像修复
            Mat canvas = Mat::zeros(src.size(), src.type());
            int r = 1;//像素查找范围--表示在该像素点上下几行像素进行查找
            for (int i = r; i < src.rows- r; i++)
            {
                for (int j = 0; j < src.cols; j++)
                {        
                    if (mask.at<uchar>(i, j) != 255)
                    {   
                        //对于非掩膜区域,直接将原像素进行像素赋值
                        for (int c = 0; c < 3; c++)
                        {
                            canvas.at<Vec3b>(i, j)[c] = src.at<Vec3b>(i, j)[c];
                        }          
                    }
                    else
                    {
                        //找到距离该掩膜像素点最近的非掩膜区域像素进行赋值
                        Point res = find_Nearest_Point(mask, i, j, r);
                        for (int c = 0; c < 3; c++)
                        {
                            canvas.at<Vec3b>(i, j)[c] = src.at<Vec3b>(res.x, res.y)[c];
                        }
                    }
                }
            }
        
         
         

          请添加图片描述
          效果如何所示

          三、轮廓提取

          接下来我们只需要对修复之后的图像进行轮廓提取就可以了。

              //将修复之后的目标图像进行图像预处理,提取轮廓
              Mat gray;
              cvtColor(canvas, gray, COLOR_BGR2GRAY);
          

          Mat gaussian;
          GaussianBlur(gray, gaussian, Size(3, 3), 0);

          Mat thresh;
          threshold(gaussian, thresh, 30, 255, THRESH_BINARY_INV);

          Mat kernel1 = getStructuringElement(MORPH_RECT, Size(3, 3));
          morphologyEx(thresh, thresh, MORPH_OPEN, kernel);

          //namedWindow(“thresh”, WINDOW_NORMAL);
          //imshow(“thresh”, thresh);

          //轮廓提取
          vector<vector<Point>>contours;
          findContours(thresh, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
          //经过面积,外接矩形特征筛选出目标区域
          vector<vector<Point>>EffectiveConts;
          for (int i = 0; i < contours.size(); i++)
          {
          double area = contourArea(contours[i]);

          if (area>100)
          {
          Rect rect = boundingRect(contours[i]);

          if (double(rect.height) > 30 && double(rect.width) > 30)
          {
          EffectiveConts.push_back(contours[i]);
          }
          }
          }

            四、效果显示

                for (int i = 0; i < EffectiveConts.size(); i++)
                {
                    //计算轮廓矩
                    Moments Mo = moments(EffectiveConts[i]);
                    //计算质心--即插孔坐标
                    Point center = Point(Mo.m10 / Mo.m00, Mo.m01 / Mo.m00);
                    //效果绘制
                    Rect rect = boundingRect(EffectiveConts[i]);
                    rectangle(src, rect, Scalar(0, 255, 0), 5);
                    circle(src, center, 3, Scalar(0, 0, 255), -1);
                }
            
             
             

              请添加图片描述
              如图为该案例最终效果。

              五、源码

              #include<opencv2/opencv.hpp>
              #include <iostream>
              #include<opencv2/photo.hpp>
              using namespace std;
              using namespace cv;
              

              double EuDis(Point pt1, Point pt2)
              {
              return sqrt(pow(pt1.x - pt2.x, 2) + pow(pt1.y - pt2.y, 2));
              }

              Point find_Nearest_Point(Mat mask , int currentrow, int currentcol, int r)
              {
              double mindis = 100000.0;
              Point res(0,0);

              //查找该像素点上下r行像素,找到最接近该像素的非掩膜区域像素
              for (int i = currentrow - r; i < currentrow + r; i++)
              {
              for (int j = 0; j < mask.cols; j++)
              {
              if (mask.at<uchar>(i, j) != 255)
              {
              //Point(currentrow, currentcol) 表示当前需要赋值的掩膜像素点
              double dis = EuDis(Point(currentrow, currentcol), Point(i, j));
              if (dis < mindis)
              {
              mindis = dis;
              res = Point(i, j); //目标像素点
              }
              }
              }
              }

              return res;
              }

              int main()
              {
              Mat src = imread(“test.jpg”);
              if (src.empty())
              {
              cout << “No Image!” << endl;
              system(“pause”);
              return -1;
              }

              //hsv颜色通道转换,提取图像中黄色线部分,生成掩膜图像
              Mat hsv;
              cvtColor(src, hsv, COLOR_BGR2HSV);

              Mat mask;
              inRange(hsv, Scalar(10, 50, 255), Scalar(40, 255, 255), mask);

              //将生成的掩膜mask膨胀一下,使掩膜区域放大
              Mat kernel = getStructuringElement(MORPH_RECT, Size(9, 9));
              dilate(mask, mask, kernel);

              //使用OpenCV自带的inpaint函数进行图像修复,得到目标图像
              //Mat inpaintImg;
              //inpaint(src, mask, inpaintImg, 1, INPAINT_NS);
              //namedWindow(“inpaintImg”, WINDOW_NORMAL);
              //imshow(“inpaintImg”, inpaintImg);

              //自己写的算法,修改图像像素,完成图像修复
              Mat canvas = Mat::zeros(src.size(), src.type());
              int r = 1;//像素查找范围–表示在该像素点上下几行像素进行查找
              for (int i = r; i < src.rows- r; i++)
              {
              for (int j = 0; j < src.cols; j++)
              {
              if (mask.at<uchar>(i, j) != 255)
              {
              //对于非掩膜区域,直接将原像素进行像素赋值
              for (int c = 0; c < 3; c++)
              {
              canvas.at<Vec3b>(i, j)[c] = src.at<Vec3b>(i, j)[c];
              }
              }
              else
              {
              //找到距离该掩膜像素点最近的非掩膜区域像素进行赋值
              Point res = find_Nearest_Point(mask, i, j, r);
              for (int c = 0; c < 3; c++)
              {
              canvas.at<Vec3b>(i, j)[c] = src.at<Vec3b>(res.x, res.y)[c];
              }
              }
              }
              }
              //namedWindow(“canvas”, WINDOW_NORMAL);
              //imshow(“canvas”, canvas);

              //将修复之后的目标图像进行图像预处理,提取轮廓
              Mat gray;
              cvtColor(canvas, gray, COLOR_BGR2GRAY);

              Mat gaussian;
              GaussianBlur(gray, gaussian, Size(3, 3), 0);

              Mat thresh;
              threshold(gaussian, thresh, 30, 255, THRESH_BINARY_INV);

              Mat kernel1 = getStructuringElement(MORPH_RECT, Size(3, 3));
              morphologyEx(thresh, thresh, MORPH_OPEN, kernel);

              //namedWindow(“thresh”, WINDOW_NORMAL);
              //imshow(“thresh”, thresh);

              //轮廓提取
              vector<vector<Point>>contours;
              findContours(thresh, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
              //经过面积,外接矩形特征筛选出目标区域
              vector<vector<Point>>EffectiveConts;
              for (int i = 0; i < contours.size(); i++)
              {
              double area = contourArea(contours[i]);

              if (area>100)
              {
              Rect rect = boundingRect(contours[i]);

              if (double(rect.height) > 30 && double(rect.width) > 30)
              {
              EffectiveConts.push_back(contours[i]);
              }
              }
              }

              for (int i = 0; i < EffectiveConts.size(); i++)
              {
              //计算轮廓矩
              Moments Mo = moments(EffectiveConts[i]);
              //计算质心–即插孔坐标
              Point center = Point(Mo.m10 / Mo.m00, Mo.m01 / Mo.m00);
              //效果绘制
              Rect rect = boundingRect(EffectiveConts[i]);
              rectangle(src, rect, Scalar(0, 255, 0), 5);
              circle(src, center, 3, Scalar(0, 0, 255), -1);
              }

              namedWindow(“src”, WINDOW_NORMAL);
              imshow(“src”, src);
              waitKey(0);
              system(“pause”);
              return 0;
              }


                总结

                本文使用OpenCV C++实现网孔检测,主要操作有以下几点。
                1、hsv通道转换,提取出黄色分量,得到掩模图像。
                2、利用掩模图像对原图进行图像修复。
                3、通过轮廓提取定位网孔位置。
                以上就是我对该案例的构思以及实现方法,如果大家有更好的算法可以实现,欢迎交流学习。。。

                评论
                添加红包

                请填写红包祝福语或标题

                红包个数最小为10个

                红包金额最低5元

                当前余额3.43前往充值 >
                需支付:10.00
                成就一亿技术人!
                领取后你会自动成为博主和红包主的粉丝 规则
                hope_wisdom
                发出的红包
                实付
                使用余额支付
                点击重新获取
                扫码支付
                钱包余额 0

                抵扣说明:

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

                余额充值