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

Part11.  绘制简单的图形

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

11.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;
}
a779d7e5bcfac88f5b5cc6ef079fb386.jpeg
绘制点和圆.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:圆心坐标点和半径值的小数点位数。

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

21.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;
}
d223749b8fe9dd069ae7e38b411c234c.jpeg
绘制直线.png

31.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;
}
74a6300a8a32fbb15b35725248a5db2c.jpeg
绘制矩形.png

41.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;
}
05d9454bb5ea9261364d8f4e0b417e91.jpeg
绘制椭圆.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 才会绘制完整的椭圆。

51.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;
}
8be97244227cb5eb80ae3af732b33e1d.jpeg
绘制多边形.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。

61.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;
}
eec31b4fa964a3b448646cb9c021722d.jpeg
添加文字.png

Part22. 轮廓入门和绘制轮廓

72.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;
}
88699634566ee0b58532d3210575d96b.jpeg
查找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()类型。

82.2 绘制轮廓

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

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

imshow("contours",image);

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

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

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

Part33.  总结

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

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

猜你喜欢

转载自blog.csdn.net/SLFq6OF5O7aH/article/details/134002044