zoukankan      html  css  js  c++  java
  • OpenCV-C++ 图像形态学操作应用-提取水平与垂直线

    通过自定义的结构元素实现结构元素对输入图像对一些对象敏感,对另外一些对象不敏感,这样就会让敏感的对象改变而不敏感的对象保留输出;

    常见的结构元素:矩形, 圆,直线,磁盘,钻石;

    理解形态学操作-膨胀, 腐蚀

    膨胀操作:

    • 利用结构元素,在图像上以滑动窗口的形式进行计算,提取出结构元素内最大值;

    腐蚀操作:

    • 利用结构元素,在图像上以互动窗口的形式进行计算,提取出结构元素内最小值;

    因此,重要的是结构元素的大小,形状,以及膨胀和腐蚀的不同的作用;

    目标问题

    如下图所示:

    有以下几个目标问题:

    1. 提取出图像中的水平线
    2. 提取出图像中的垂直线
    3. 提取图像中的一些字母;

    其实,上面这张图像还是有一点点难度的,下面处理后的效果还没有很理想;当然这篇文章重要的是为了说明形态学操作的应用,重在原理的理解;后续学到更多的知识,再回头改一改吧;

    另外,上面这幅图像是利用代码绘制出来的,代码在这篇文章的最后;当然,如果你的电脑上有绘图软件的画,也可以自己画一些线段和字符;上面这幅图像的特征是:

    1. 有水平线, 垂直线, 字符;
    2. 线的长度, 宽度不一致;
    3. 字符大小不一致;

    图像预处理

    上面提供的图像是一个彩色图像,为了方便后续处理,执行两步操作:

    1. 将彩色图像转换称灰度图像
    2. 将灰度图像转换称二值图像

    转换的结果如下图所示:

    首先,第一步将彩色图像转化称灰度图像:

    // 读取图像
    Mat src = imread("/home/chen/dataset/random_line.png");
    
    // 转换称灰度图像
    Mat srcGray;
    cvtColor(src, srcGray, COLOR_BGR2GRAY);
    

    其次,第二步将灰度图像转换称二值图像:

    Mat srcBinary;
    adaptiveThreshold(~srcGray, srcBinary, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 11, -2);
    

    其中,需要注意的有两点:

    第一点是关于adaptiveThreshold这个方法的使用, 使用方法参照:OpenCV-C++ 图像自适应阈值二值化处理adaptiveThreshold

    第二点是关于为什么要使用~srcGray,而不是直接使用srcGray,这是因为我们想让背景变成黑色,前景变成白色,并配合adativeThreshold的使用;

    提取水平线

    如上面二值图像所示,想要提取其中水平线部分,我们需要对图像执行开操作, 即先腐蚀,后膨胀;腐蚀的目的是为了先将除水平线之外的线条过滤掉;

    那么,如果过滤呢? 这就需要设计腐蚀操作的结构元素了;

    想一下假如,有一个横向的一维结构元素,在腐蚀操作的时候,是不是就将一些纵向的线段腐蚀掉了呢.(背景是黑色,前景是白色)

    代码如下:

    // 定义水平(横向)结构元素
    Mat hline = getStructuringElement(MORPH_RECT, Size(20, 1), Point(-1, -1));  // 水平线
    
    // 开操作: 先腐蚀,后膨胀
    Mat dst;
    erode(srcBinary, dst, hline);  // 腐蚀操作, 过滤掉非水平线
    dilate(dst, dst, hline);  // 膨胀操作
    

    你可能也注意到了,在想要提取水平线的同时,也将字母中的水平线提取出来了.

    可以通过增加水平结构元素的长度,即增加Size(20, 1), 影响如下:

    • 不再提取出字母中的水平线
    • 有可能会遗漏右下角较短的水平短线;
    • 会将中间部分,横线的断裂给连接上;(当然这一步也可以单独为膨胀操作单独设置水平结构元素来实现;)

    提取垂直线

    与提取水平线原理类似:

    如上面二值图像所示,想要提取其中垂直线部分,我们需要对图像执行开操作, 即先腐蚀,后膨胀;腐蚀的目的是为了先将除垂直线之外的线条过滤掉;

    那么,如果过滤呢? 这就需要设计腐蚀操作的结构元素了;

    想一下假如,有一个纵向的一维结构元素,在腐蚀操作的时候,是不是就将一些横向的线段腐蚀掉了呢.(背景是黑色,前景是白色)

    代码如下:

    // 垂直结构元素
    Mat vline = getStructuringElement(MORPH_RECT, Size(1, 50), Point(-1, -1));  // 垂直线
    
    // 开操作: 先腐蚀,后膨胀
    Mat dst;
    erode(srcBinary, dst, kernel);
    dilate(dst, dst, kernel);
    

    同样,也存在相同的问题,就是将字母中的垂直线提取出来,那么相应的解决办法也相同:

    1. 一方面调整结构元素的长度;
    2. 另一方面单独为腐蚀, 膨胀操作设置不同的结构元素;
    3. 甚至,在处理不同长度的线段时,使用不同的结构元素;

    提取字母

    尴尬了没有办法完成提取图像中的字符了,构造的图像难度有点高(对于目前初学的我解决不了了);

    代码如下:

    // 定义结构元素
    Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
    // 开操作: 先腐蚀,后膨胀
    Mat dst;
    erode(srcBinary, dst, kernel);
    dilate(dst, dst, kernel);
    

    其实,原因好像也能理解:主要是因为线段的宽度与字符宽度类似;

    或许,再思考一下,直接把水平线和垂直线过滤掉算了,当然重要的是参数的调整;

    (尝试着调整了一下,完成不了,shit)

    总结

    • 重点理解形态学操作膨胀, 腐蚀的原理;
    • 不同的场景下,需要的结构元素,长度,形状是不同的;
    • 需要从任务角度进行分析,需要使用哪些处理操作;
    • 有些问题,还没有很好的解决,待学习了更多的知识后,再回头解决吧;

    相关完整代码

    处理过程完整代码:

    #include <iostream>
    #include <opencv2/opencv.hpp>
    using namespace std;
    using namespace cv;
    
    int main(){
    
        // 读取图像
        Mat src = imread("/home/chen/dataset/random_line.png");
        if (! src.data){
            cout << "could not load image." << endl;
            return -1;
        }
        namedWindow("src", WINDOW_AUTOSIZE);
        imshow("src", src);
    
        // 转换成灰度图像
        Mat srcGray;
        cvtColor(src, srcGray, COLOR_BGR2GRAY);
        namedWindow("srcGray", WINDOW_AUTOSIZE);
        imshow("srcGray", srcGray);
        
        // // 转换为二值图像
        Mat srcBinary;
        adaptiveThreshold(~srcGray, srcBinary, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 11, -2);
        namedWindow("srcBinary", WINDOW_AUTOSIZE);
        imshow("srcBinary", srcBinary);
    
        // 水平结构元素
        Mat hline = getStructuringElement(MORPH_RECT, Size(20, 1), Point(-1, -1));  // 水平线
        // 垂直结构元素
        Mat vline = getStructuringElement(MORPH_RECT, Size(1, 50), Point(-1, -1));  // 垂直线
        // 提取字母
        Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
    
        // 开操作: 先腐蚀,后膨胀
        Mat dst;
        erode(srcBinary, dst, kernel);
        dilate(dst, dst, kernel);
    
        // 水平结构元素
        Mat hline2 = getStructuringElement(MORPH_RECT, Size(40, 1), Point(-1, -1));  // 水平线
        // 垂直结构元素
        Mat vline2 = getStructuringElement(MORPH_RECT, Size(1, 50), Point(-1, -1));  // 垂直线
    
        Mat temp1, temp2, dst_temp;
        erode(dst, temp1, hline2);
        dst_temp = dst - temp1;
        erode(dst, temp2, vline2);
        dst = dst_temp - temp2;
    
        imshow("temp1", temp1);
        imshow("temp2", temp2);
    
        namedWindow("dst", WINDOW_AUTOSIZE);
        imshow("dst", dst);
    
        // 显示图像
        waitKey(0);
    
        return 0;
    }
    

    绘制上面的目标问题图像:

    #include <iostream>
    #include <opencv2/opencv.hpp>
    
    using namespace std;
    using namespace cv;
    
    
    int main(){
        // 创建一张空白图像
        Mat src = Mat(Size(512, 512), CV_8UC3, Scalar(255, 255, 255));
    
        RNG rng(12345);
        // 随机生成一些水平线
        Point p1, p2;
        Scalar color;
        const int MAX_THICKNESS = 5;
        int thickness;
        const int NUM_HORIZONTAL_LINE = 3;
        for (int i = 0; i <= NUM_HORIZONTAL_LINE; i++){
            p1.x = rng.uniform(0, src.cols);
            p1.y = rng.uniform(MAX_THICKNESS, src.rows - MAX_THICKNESS);
            p2.x = rng.uniform(0, src.cols);
            p2.y = p1.y;
            color  = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
            thickness = rng.uniform(1, MAX_THICKNESS);
            line(src, p1, p2, color, thickness);
        }
    
        // 随机生成一些垂直线
        const int NUM_VERTICAL_LINE = 3;
        for (int i = 0; i <= NUM_VERTICAL_LINE; i++){
            p1.x = rng.uniform(MAX_THICKNESS, src.cols- MAX_THICKNESS);
            p1.y = rng.uniform(0, src.rows);
            p2.x = p1.x;
            p2.y = rng.uniform(0, src.rows);
            color  = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
            thickness = rng.uniform(1, MAX_THICKNESS);
            line(src, p1, p2, color, thickness);
        }
    
        // 在随机位置生成一些数字
        putText(src, "A", Point(90, 128), FONT_HERSHEY_COMPLEX, 2, Scalar(0, 0, 255), 3);
        putText(src, "B", Point(200, 128), FONT_HERSHEY_COMPLEX, 2.5, Scalar(0, 0, 255), 3);
        putText(src, "E", Point(320, 128), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 3);
    
        // 显示图像
        namedWindow("src", WINDOW_AUTOSIZE);
        imshow("src", src);
        waitKey(0);
    
        // 保存图像
        imwrite("/home/chen/dataset/random_line.png", src);
    
        return 0;
    }
    
  • 相关阅读:
    js-jquery-003-条形码-二维码【QR码】
    js-jquery-002-条形码-一维码
    js-jquery-001-条形码概述
    java-mybaits-00401-Mapper-输入输出
    tools-eclipse-002-常用插件
    java-mybaits-00301-SqlMapConfig
    java-mybaits-00203-DAO-mapper代理开发方法,多参数【推荐】
    java-mybaits-00202-DAO-原始DAO开发方法
    java-mybaits-00201-DAO-SqlSession使用范围
    java-mybaits-00103-入门程序原生的【查、增、删、改】
  • 原文地址:https://www.cnblogs.com/chenzhen0530/p/14642786.html
Copyright © 2011-2022 走看看