美文网首页OpenCV 学习笔记
OpenCV 笔记(3):基本图形的绘制

OpenCV 笔记(3):基本图形的绘制

作者: fengzhizi715 | 来源:发表于2023-10-22 13:48 被阅读0次

    1. 绘制简单的图形

    绘图功能是 OpenCV 最基础的功能,OpenCV 提供了基础的绘制函数,用于帮助我们绘制一些基本的图形。通过这些函数的组合,我们也可以做一些高级的应用。

    1.1 绘制点和圆

    OpenCV 的绘制函数相对简单,而且很多参数很类似,所以介绍第一个函数时会详细地介绍各个参数的含义,后面就不做特别详细的介绍了。

    我们先来看点和圆的绘制:

    #include <opencv2/core.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/imgproc.hpp>
    
    using namespace std;
    using namespace cv;
    
    int main(int argc,char *argv[])
    {
        Mat image = Mat::zeros(Size(800, 800), CV_8UC3);
        image.setTo(255);// 设置屏幕为白色
    
        Point p1(100, 100);
        Point p2(200, 200);
        Point p3(300, 300);
        Point p4(400, 400);
        Point p5(500, 500);
        Point p6(600, 600);
        Point p7(700, 700);
    
        circle(image, p1, 4, Scalar(0, 0, 255), -1);  // 画半径为4的圆(画点)
    
        circle(image, p2, 60, Scalar(255, 0, 0), 2);  // 画半径为60的圆
    
        circle(image, p3, 60, Scalar(0, 255, 0), -1);
    
        circle(image, p4, 60, Scalar(255, 255, 0), 5);
    
        circle(image, p5, 60, Scalar(255, 0, 255), -1);
    
        circle(image, p6, 60, Scalar(0, 255, 255), 2);
    
        circle(image, p7, 60, Scalar(0, 0, 0), -1);
    
        imshow("src", image);
    
        waitKey(0);
        return 0;
    }
    
    绘制点和圆.png

    我们主要使用 circle() 函数来绘制点和圆。

    CV_EXPORTS_W void circle(InputOutputArray img, Point center, int radius,
                           const Scalar& color, int thickness = 1,
                           int lineType = LINE_8, int shift = 0);
    

    其各个参数的含义:

    第一个参数 img:输入的源图像。
    第二个参数 center:圆心的坐标。
    第三个参数 radius:圆的半径。
    第四个参数 color:圆形的颜色。
    第五个参数 thickness:如果是正数,表示组成圆的线条的粗细程度。如果是负数,表示圆被填充。
    第六个参数 lineType:线条的类型。OpenCV 提供了三种类型的线条,它们都是 LineTypes 枚举类型。

    • LINE_4 :4,表示四连接线。
    • LINE_8 :8,表示八连接线。
    • LINE_AA :16,表示抗锯齿线。使用它会产生更好的绘图质量,图像看起来会非常平滑,但是绘制速度较慢。

    第七个参数 shift:圆心坐标点和半径值的小数点位数。

    这里很多的参数,在本节后续的函数中都会用到。

    1.2 绘制直线

    直线跟圆的区别是,直线需要2个点来确定位置。下面是绘制直线的例子:

    #include <opencv2/core.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/imgproc.hpp>
    
    using namespace std;
    using namespace cv;
    
    int main(int argc,char *argv[])
    {
        Mat image = Mat::zeros(Size(800, 800), CV_8UC3);
        image.setTo(255);// 设置屏幕为白色
    
        Point p1(100, 100);
        Point p2(700, 700);
        Point p3(700, 100);
        Point p4(100, 700);
    
        line(image, p1, p2, Scalar(0, 0, 255), 2);
        line(image, p3, p4, Scalar(255, 0, 0), 2);
    
        imshow("src", image);
    
        waitKey(0);
        return 0;
    }
    
    绘制直线.png

    1.3 绘制矩形

    矩形有两种绘制方式,一种是定义好矩形的左上角点位置和矩形长宽,然后在图像上绘制出来;另一种是通过确定矩形的左上角点右下角点来确定矩形的位置,然后在图像上绘制出来。

    #include <opencv2/core.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/imgproc.hpp>
    
    using namespace std;
    using namespace cv;
    
    int main(int argc,char *argv[])
    {
        Mat image = Mat::zeros(Size(800, 800), CV_8UC3);
        image.setTo(255);// 设置屏幕为白色
    
        Rect rect(150, 150, 120, 200);
        rectangle(image, rect, Scalar(0, 0, 255), 4);
    
        rectangle(image,Point(200,400),      //两个对角点
                  Point(600,600),
                  Scalar(255,0,0),
                  -);
    
        imshow("src", image);
    
        waitKey(0);
        return 0;
    }
    
    绘制矩形.png

    1.4 绘制椭圆

    椭圆的绘制稍微复杂一点,除了椭圆的中心位置以外,还需要确定椭圆的旋转角度、横轴长、纵轴长,这样才能绘制出椭圆。

    #include <opencv2/core.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/imgproc.hpp>
    
    using namespace std;
    using namespace cv;
    
    int main(int argc,char *argv[])
    {
        Mat image = Mat::zeros(Size(800, 800), CV_8UC3);
        image.setTo(255);// 设置屏幕为白色
    
        Point p1(100, 100);
        Point p2(200, 200);
        Point p3(300, 300);
        Point p4(400, 400);
        Point p5(600, 600);
    
        ellipse(image,p1,Size(60, 30),30,0,360,Scalar(255, 255, 0),4);
    
        ellipse(image,p2,Size(30, 60),0,0,360,Scalar(255, 0, 0),-1);
    
        ellipse(image,p3,Size(60, 30),120,0,360,Scalar(0, 255, 0),4);
    
        ellipse(image,p4,Size(100, 100),0,0,360,Scalar(0, 0, 255),-1);
    
        ellipse(image,p5,Size(120, 60),0,0,360,Scalar(255, 0, 255),4);
    
        imshow("src", image);
    
        waitKey(0);
        return 0;
    }
    
    绘制椭圆.png

    绘制椭圆的 ellipse() 函数的定义:

    CV_EXPORTS_W void ellipse(InputOutputArray img, Point center, Size axes,
                            double angle, double startAngle, double endAngle,
                            const Scalar& color, int thickness = 1,
                            int lineType = LINE_8, int shift = 0);
    

    其中,
    第三个参数 axes: Size 的两个参数分别是横轴的长度、纵轴的长度。当横轴和纵轴相等时,那就表示是圆形。
    第四个参数 angle:椭圆旋转角度。
    第五个参数 startAngle:从主轴顺时针方向测量的椭圆弧的起点。
    第六个参数 endAngle:从主轴顺时针方向测量的椭圆弧的终点。当 startAngle 和 endAngle 的值为 0、360 才会绘制完整的椭圆。

    1.5 绘制多边形

    多面体相对于椭圆更加复杂一些,多面体的绘制本身也有两种函数可以实现。

    polylines() 函数根据点集绘制多条相连的线段用以组成多面体,fillPoly() 函数绘制具有填充效果的多面体。因此, thickness 参数是否为负数无法对多面体的填充起作用。

    #include <vector>
    #include <opencv2/core.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/imgproc.hpp>
    
    using namespace std;
    using namespace cv;
    
    int main(int argc,char *argv[])
    {
        Mat image = Mat::zeros(Size(800, 800), CV_8UC3);
        image.setTo(255);// 设置屏幕为白色
    
        Point p1(100, 100);
        Point p2(350, 100);
        Point p3(450, 280);
        Point p4(320, 450);
        Point p5(100, 400);
    
        std::vector<Point> pts;
        pts.push_back(p1);
        pts.push_back(p2);
        pts.push_back(p3);
        pts.push_back(p4);
        pts.push_back(p5);
    
        polylines(image, pts, true, Scalar(255, 0, 255), 4);
    
        Point p6(500, 500);
        Point p7(720, 650);
        Point p8(650, 780);
        Point p9(550, 700);
        Point p10(300, 700);
    
        pts.clear();
        pts.push_back(p6);
        pts.push_back(p7);
        pts.push_back(p8);
        pts.push_back(p9);
        pts.push_back(p10);
    
        fillPoly(image, pts, Scalar(0, 255, 255));
    
        imshow("src", image);
    
        waitKey(0);
        return 0;
    }
    
    绘制多边形.png

    简单介绍一下 polylines() 函数,另一个 fillPoly() 函数很类似。

    CV_EXPORTS_W void polylines(InputOutputArray img, InputArrayOfArrays pts,
                                bool isClosed, const Scalar& color,
                                int thickness = 1, int lineType = LINE_8, int shift = 0 );
    

    第二个参数 pts: 输入多边形的点的集合。
    第三个参数 isClosed:是否把绘制的多条线段首尾相连,如果要绘制成多边形这个参数很重要,需要设置成 true。

    1.6 图像中添加文字

    OpenCV 提供了在原图上添加文字的 putText() 函数,支持字体、字号的设置。

    #include <iostream>
    #include <opencv2/core.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/imgproc.hpp>
    
    using namespace std;
    using namespace cv;
    
    int main(int argc,char *argv[])
    {
        Mat image = Mat::zeros(Size(800, 800), CV_8UC3);
        image.setTo(255);// 设置屏幕为白色
    
        string text = "Hello OpenCV!";
    
        putText(image, text, Point(0,100), FONT_HERSHEY_PLAIN, 2, cv::Scalar(0, 0, 255), 2);
        putText(image, text, Point(100,200), FONT_HERSHEY_PLAIN, 4, cv::Scalar(255, 0, 255), 4);
    
        //设置绘制文本的相关参数
        int fontFace = cv::FONT_HERSHEY_SIMPLEX;
        double fontScale = 2;
        int thickness = 8;
        int baseline;
    
        // 通过 getTextSize() 函数先获取待绘制文本的大小
        Size textSize = getTextSize(text, fontFace, fontScale, thickness, &baseline);
    
        // 计算出文本绘制到图片居中的位置
        Point point;
        point.x = image.cols / 2 - textSize.width / 2;
        point.y = image.rows / 2 + textSize.height / 2;
    
        putText(image, text, point, fontFace, fontScale, cv::Scalar(255, 255, 0), thickness);
    
        imshow("src", image);
    
        waitKey(0);
        return 0;
    }
    
    添加文字.png

    2. 轮廓入门和绘制轮廓

    2.1 轮廓入门

    轮廓机器视觉的常用概念。它是由一系列相连的点组成的曲线,具有相同的颜色或灰度。轮廓常用于形状分析、物体检测、识别等任务。

    一般情况下,为了得到精准的轮廓需要先对图像进行二值化处理,例如使用阈值分割或者 Canny 边缘检测等方式得到二值图像。然后,对二值图像进行轮廓发现和轮廓分析。

    轮廓发现是利用 findContours() 函数检测图像中的对象边界,将每一个轮廓以点向量方式存储。因此,可以得到一个图像的拓扑信息,包含了一个轮廓的后一个轮廓、前一个轮廓、父轮廓和内嵌轮廓的索引编号。

    在获取图像轮廓之后,我们就可以通过轮廓的属性(例如:轮廓的面积、质心、周长、几何矩、中心矩等等)来分析和筛选轮廓。轮廓具有很多属性和性质,我们会在后面的文章详细地介绍更多的内容,本文只是作为简单的入门介绍。

    下面的例子,展示了获取手机的轮廓图,并获取其最小外接矩形以及截取 roi:

    #include <iostream>
    #include <vector>
    #include <opencv2/core.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/imgproc.hpp>
    
    using namespace std;
    using namespace cv;
    
    bool ascendSort(vector<Point> a,vector<Point> b)
    {
        return contourArea(a) > contourArea(b);
    }
    
    int main(int argc,char *argv[])
    {
        string fileName = ...;
        Mat image = imread(fileName);
        if (image.empty()) {
            return -1;
        }
    
        imshow("src",image);
    
        Mat gray;
        cvtColor(image,gray,COLOR_BGR2GRAY);
        Mat thresh;
        threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
    
        // 定义变量轮廓
        vector<vector<Point>> contours;
        vector<Vec4i> hierarchy;
    
        findContours(thresh, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
        sort(contours.begin(), contours.end(), ascendSort);//ascending sort
    
        RotatedRect rrt = minAreaRect(contours[0]);
        Rect bbox = rrt.boundingRect();
    
        Mat roi;
        try {
            roi = image(bbox);
        } catch (...) {
            return -1;
        }
    
        imshow("roi",roi);
    
        waitKey(0);
    
        return 0;
    }
    
    查找ROI.png

    上述代码,首先将原图转换成灰度图像,再进行阈值分割变成二值图像。然后,对轮廓进行查找并按照轮廓面积的大小进行排序。最后,对最大的轮廓获取其最小外接矩形以及截取这个最小外接矩形作为 roi,并将其展示。

    在这段代码中,有些函数的作用和解释会在以后的小节中详细介绍。本文只详细解释如何进行轮廓发现和查找,主要使用的是 findContours() 函数。它包含很多参数,我们有必要简介绍一下各个参数的含义。

    第一个参数 image: 输入的源图像。一个 CV_8UC1 的单通道图像。
    第二个参数 contours: 输出轮廓图像。每个轮廓都存储为点向量 std::vector<cv::Point>,由多个轮廓组成输出的全部轮廓 std::vector<std::vector<cv::Point>>。
    第三个参数 hierarchy:输出各个轮廓的继承关系。是 std::vector<cv::Vec4i> 类型的向量,长度跟 contours 的长度一致,每个元素和 contours 的元素对应,包含了有关图像拓扑的信息。
    第四个参数 mode:轮廓检测的模式。包括以下四种:

    • RETR_EXTERNAL:只检测外轮廓,忽略轮廓内部的洞。
    • RETR_LIST:检测所有的轮廓,但不建立继承(包含)关系。
    • RETR_TREE:检测所有的轮廓,并且建立所有的继承(包含)关系。
    • RETR_CCOMP:检测所有轮廓,但是仅仅建立两层包含关系。
    • RETR_FLOODFILL:洪水填充法。采用这种模式时,输入的源图像也可以是 32 位的整型图像(CV_32SC1)。

    第五个参数 method:每个轮廓的编码信息。包括以下四种:

    • CHAIN_APPROX_NONE:把轮廓上所有的点存储。
    • CHAIN_APPROX_SIMPLE:只存储轮廓上的拐点。
    • CHAIN_APPROX_TC89_L1:使用 teh-Chinl chain 近似算法。
    • CHAIN_APPROX_TC89_KCOS 使用 teh-Chinl chain 近似算法。

    第六个参数 offset:每个轮廓点移动的偏移量。表示所有的轮廓信息相对于原始图像的偏移量,它是一个可选参数,cv::Point()类型。

    2.2 绘制轮廓

    在上述的代码找到了轮廓之后,绘制轮廓就变得很简单了。我们使用 drawContours() 函数就可以绘制轮廓。

    drawContours(image,contours,0,Scalar(0,0,255),8);
    
    imshow("contours",image);
    

    再结合 imshow() 函数,可以直接在原图上展示绘制出来的手机轮廓。


    绘制轮廓.png

    drawContours() 函数的参数就不一一解释了,我们只解释2个参数的含义。
    第二个参数 contours: 输入全部的轮廓图像。
    第三个参数 contourIdx:轮廓索引号,从 0 开始。-1 表示绘制所有轮廓。

    通过这个函数,我们学会了绘制轮廓。在调试代码的时候,我经常会在原图上绘制一下查找到的相关轮廓,看看查找的内容是否准确。

    3. 总结

    本文主要分成两个部分。第一部分介绍了 OpenCV 基本的绘制函数以及使用,它们的使用比较简单只要明白每个函数中各个参数的含义即可。如果将这些函数组合起来使用,也可以做一些相对高级的应用。

    第二部分介绍了轮廓的入门知识,主要是轮廓发现和轮廓绘制。轮廓是图像处理的核心内容之一,它包含了很多重要的信息和性质,我们会在后面的文章中重点学习。

    相关文章

      网友评论

        本文标题:OpenCV 笔记(3):基本图形的绘制

        本文链接:https://www.haomeiwen.com/subject/uqoridtx.html