写在前面的那些有的没的

学习OpenCV有一段时间了,虽然已经正式成为拉环小哥整整一个月,但是仍然想着学习自己喜欢的。说实话,想着大家都在往自己喜欢的方向奔,我不愿成为无所事事的人。

可能还是MSP的氛围好吧,想起昕羽姐辞掉微软的工作,一心想当一名插画师,去年这个时候还写明信片鼓励我。大家都这么优秀,说真的,不想拖大家后腿。

车辆段是挺好的一个地方,确实没想到会分回来。既然来了那就先这样呗,最起码我要先把自己的生活安顿下来,最起码要让生活走上正轨。

现在的我没有什么突出的能力,没有什么钱,没有什么过硬的技术。这些都只能慢慢来,说到底,还是我自己太菜了。

好了,不说这些有的没的了。

现在拿工资了,终于有钱买实验设备跟学习教程了!很开心!

言归正传,说说这两天学的Open CV切边。

什么是OpenCV切边?

七月份刚毕业的时候,在淘宝上花了15块买了份Open CV的盗版视频。

clipboard.png

下载下来发现是51CTO的收费视频,想着这么贵讲的应该还不错,然后就学习了下。

这个老师叫贾志刚,我喜欢叫他沙雕老师,因为,讲的实在是太沙雕了。在第一部分的课里,净在讲骚话。从高数到语文,从历史到政治,不仅教你背古诗还教你撩妹!说骚话张口就来,一讲到硬核的部分就emmm...真是服气。听不懂也没办法只能在网上搜一下相关的文章,看看原理,补一补高数。

抄抄老师的代码,运行一下,总结一下,就这样混过去了。

直到这两天,开始做小案例。

小案例就是把歪着的图片矫正,并且切除多余部分。
看,就是下面这个dogB 王源

clipboard.png

然后要矫正。
这个沙雕老师真是坑的一匹,代码写的只有他的图片能用,原理也讲不通,水的要死。

clipboard.png

原理是这样的:

Canny检测边缘->比较最大的轮廓->确定最大轮廓->按照轮廓画图->矫正切边

从代码可以看出来:

代码我有相当一部分看的是这篇文章:
https://blog.csdn.net/weixin_...
int main(int,void*)
{
//...imread..xxxx
Check_Skew();  
}

void Check_Skew(); 
{
    Mat canny_output;          
    cvtColor(src_img, gray_img, COLOR_BGR2GRAY);         //将原图转化为灰度图
    Canny(gray_img, canny_output, threshold_value, threshold_value * 2, 3, false);      // canny边缘检测
 
    vector<vector<Point>> contours;
    vector<Vec4i> hireachy;
    findContours(canny_output, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));    // 找到所有轮廓
    Mat drawImg = Mat::zeros(src_img.size(), CV_8UC3);   
    float max_width = 0;       // 定义最大宽度
    float max_height = 0;      // 定义最大高度
    double degree = 0;         // 定义旋转角度
    for (auto t = 0; t < contours.size(); ++t)            // 遍历每一个轮廓   
    { 
        RotatedRect minRect = minAreaRect(contours[t]);        // 找到每一个轮廓的最小外包旋转矩形,RotatedRect里面包含了中心坐标、尺寸以及旋转角度等信息   
        degree = abs(minRect.angle);         
        if (degree > 0) 
        {
            max_width = max(max_width, minRect.size.width);      
            max_height = max(max_height, minRect.size.height);
        }
    }
    RNG rng(12345);        
    for (auto t = 0; t < contours.size();++t) 
    {
        RotatedRect minRect = minAreaRect(contours[t]);    
        if (max_width == minRect.size.width && max_height == minRect.size.height)
        {
            degree = minRect.angle;   // 保存目标轮廓的角度
            Point2f pts[4];
            minRect.points(pts);
            Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));  //产生随机颜色
            for (int i = 0; i < 4; ++i)
            {
                line(drawImg, pts[i], pts[(i + 1) % 4], color, 2, 8, 0);
            }
        }
    }
    
    imshow("找到的矩形轮廓", drawImg);
    Point2f center(src_img.cols / 2, src_img.rows / 2);
    Mat rotm = getRotationMatrix2D(center, degree, 1.0);    //获取仿射变换矩阵
    Mat dst;
    warpAffine(src_img, dst, rotm, src_img.size(), INTER_LINEAR, 0, Scalar(255, 255, 255));    // 进行图像旋转操作
    imwrite("123.png", dst);      //将校正后的图像保存下来
    imshow("Correct Image", dst);

}

这些代码看起来比较长,我分几部分。

这些是找轮廓的:

    Mat canny_output;          
    cvtColor(src_img, gray_img, COLOR_BGR2GRAY);         //将原图转化为灰度图
    Canny(gray_img, canny_output, threshold_value, threshold_value * 2, 3, false);      // canny边缘检测
 
    vector<vector<Point>> contours;
    vector<Vec4i> hireachy;
    findContours(canny_output, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));    // 找到所有轮廓

这些是找最大的轮廓并记录它的高度宽度,以用于后面绘制它

    Mat drawImg = Mat::zeros(src_img.size(), CV_8UC3);   
    float max_width = 0;       // 定义最大宽度
    float max_height = 0;      // 定义最大高度
    double degree = 0;         // 定义旋转角度
    for (auto t = 0; t < contours.size(); ++t)            // 遍历每一个轮廓   
    { 
        RotatedRect minRect = minAreaRect(contours[t]);        // 找到每一个轮廓的最小外包旋转矩形,RotatedRect里面包含了中心坐标、尺寸以及旋转角度等信息   
        degree = abs(minRect.angle);         
        if (degree > 0) 
        {
            max_width = max(max_width, minRect.size.width);      
            max_height = max(max_height, minRect.size.height);
        }
    }

这些是挨个比较,看你们这一堆轮廓中哪个是我刚刚找到的最大轮廓,并且绘制出来。
(这地方就是沙雕老师水平不行的地方,代码写得稀烂,明明能一次做完的事儿非要干第二次,有点蠢)

    RNG rng(12345);        
    for (auto t = 0; t < contours.size();++t) 
    {
        RotatedRect minRect = minAreaRect(contours[t]);    
        if (max_width == minRect.size.width && max_height == minRect.size.height)
        {
            degree = minRect.angle;   // 保存目标轮廓的角度
            Point2f pts[4];
            minRect.points(pts);
            Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));  //产生随机颜色
            for (int i = 0; i < 4; ++i)
            {
                line(drawImg, pts[i], pts[(i + 1) % 4], color, 2, 8, 0);
            }
        }
    }

剩下的只有一行比较关键:

warpAffine(src_img, dst, rotm, src_img.size(), INTER_LINEAR, 0, Scalar(255, 255, 255)); // 进行图像旋转操作

这是旋转图像的API,关键在于旋转之后,你要按着这个沙雕老师抄就会发现图片的空余地方是白色的。你需要修改的是INTER_LINEAR后面边界填充参数,就是问你想用什么东西填充空白。沙雕老师写了个0,我这是BORDER_REPLICATE看个人情况吧,不知道咋用可以看看这个

https://blog.csdn.net/qq_2494...

clipboard.png

矫正完了就成这样了。

值得注意的是,我并不是按照上面这样写的。我的代码加了中值滤波,为了消除噪声。还有很头疼的阴影。

edianBlur(src, mBlur,11);

(11的模糊力度特别大,边缘特别容易识别。)

矫正后

接下来就是切去周围多余的边了,然而坑爹的就在这儿了。

矫正之后反而还识别不了了。What The Fuck?你原图都识别出来了,矫正还识别不出来了?

我仔细找了下原因:

矫正后,

if (max_width == minRect.size.width && max_height == minRect.size.height)

该条件无法成立,

clipboard.png

按道理来说我们应该选绿框,那怎么才能挑出绿框呢?

那就记录找出满足任意max条件的面积最大的,或者你你看max_Width不能小于多少,把红框过滤掉。

这样就解决了最头疼的问题。

clipboard.png

大功告成~

PS:期间找不到边缘的时候我曾怀疑过是不是我边缘不够亮,还用PS修了一下。。妈个鸡想想都觉得丢人。

最后,切边总算是快要结束了~

附切边代码:


void findROI(int, void*)
{
    
    printf("**************当前阈值:%d******************************\n", threshold_value);
    //cvtColor(src_img, gray_img, COLOR_BGR2GRAY);      //将原图转化为灰度图
    Mat canny_output;
    Mat mBlur;
    medianBlur(src, mBlur, 11);
    Canny(mBlur, canny_output, threshold_value, threshold_value * 2, 3, false);                // canny边缘检测
    imshow("canny_output", canny_output);
    vector<vector<Point>> contours;
    vector<Vec4i> hireachy;
    findContours(canny_output, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));    // 调用API,找到轮廓

    // 筛选contours中的轮廓,我们需要最大的那个轮廓
    float max_width = 0;       // 定义最大宽度
    float max_height = 0;      // 定义最大高度
    double degree = 0;         // 定义旋转角度
    //这个for是为了找最大的框
    for (auto t = 0; t < contours.size(); ++t)            // 遍历每一个轮廓   
    {
        RotatedRect minRect = minAreaRect(contours[t]);        // 找到每一个轮廓的最小外包旋转矩形,RotatedRect里面包含了中心坐标、尺寸以及旋转角度等信息   
        degree = abs(minRect.angle);
        max_width = max(max_width, minRect.size.width);
        max_height = max(max_height, minRect.size.height);
    }

    RNG rng(12345);                            //定义一个随机数产生器,用来产生不同颜色的矩形框
    Mat drawImage = Mat::zeros(src_img.size(), CV_8UC3);
    Rect bbox;
    for (auto t = 0; t < contours.size(); ++t)        // 遍历每一个轮廓
    {
        RotatedRect minRect = minAreaRect(contours[t]);        // 找到每一个轮廓的最小外包旋转矩形,RotatedRect里面包含了中心坐标、尺寸以及旋转角度等信息
        
        if ((minRect.size.width == max_width || minRect.size.height == max_height) && minRect.size.width > 620)   //筛选最小外包旋转矩形
        {
            printf("current angle : %f\n", degree);
            Mat vertices;       // 定义一个4行2列的单通道float类型的Mat,用来存储旋转矩形的四个顶点
            boxPoints(minRect, vertices);    // 计算旋转矩形的四个顶点坐标
            bbox = boundingRect(vertices);   //找到输入点集的最小外包直立矩形,返回Rect类型
            cout << "最小外包矩形:" << bbox << endl;
            Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));   //产生随机颜色
            for (int i = 0; i < 4; i++)             // 遍历每个旋转矩形的四个顶点坐标
            {
                // 在相邻的顶点之间绘制直线
                Mat p1 = vertices.row(i); // 使用成员函数row(i)和col(j)得到矩阵的第i行或者第j列,返回值仍然是一个单通道的Mat类型
                int j = (i + 1) % 4;
                Mat p2 = vertices.row(j);
                Point p1_point = Point(p1.at<float>(0, 0), p1.at<float>(0, 1)); //将Mat类型的顶点坐标转换为Point类型
                Point p2_point = Point(p2.at<float>(0, 0), p2.at<float>(0, 1));
                line(src, p1_point, p2_point, color, 2, 8, 0);    // 根据得到的四个顶点,通过连接四个顶点,将最小旋转矩形绘制出来
            }
        }
    }
    imshow(output_win, src);

    if (bbox.width > 0 && bbox.height > 0)
    {
        Mat roiImg = src_img(bbox);        //从原图中截取兴趣区域
        namedWindow(roi_win, CV_WINDOW_AUTOSIZE);
        imshow(roi_win, roiImg);
    }

    return;
}

下个小案例见!

有啥不懂的可以问我~也可以单独分模块测试。
Sangyu.Li@outlook.com


Mortal
6 声望2 粉丝