OpenCV学习笔记:绘制几何图形

780 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

本文是我之前在微信公众号上的一篇文章记录。原链接为:# OpenCV学习笔记:绘制几何图形

本篇文章,将学习如何在图像上使用OpenCV函数绘制几何图形,如画线、圆、矩形、椭圆等,另外还学习在图像中增加文本信息。主要学习函数line()、circle()、rectangle()、ellipse()、putText()等函数的使用。

直线

函数原型:

void line(InputOutputArray img, Point pt1, Point pt2, const Scalar& color,
          int thickness = 1, int lineType = LINE_8, int shift = 0);
  • img:图像.
  • pt1:线条起点.
  • pt2:线条终点.
  • color:线条颜色.
  • thickness:线条宽度.
  • lineType:线型
#include <iostream>

#include "opencv2/opencv.hpp"

using namespace std;

void testDrawLine() {
    std::string img_path = "car.png";
    cv::Mat image = cv::imread(img_path, cv::IMREAD_COLOR);

    cv::Point start(100, 50);
    cv::Point end(400, 50);
    cv::line(image, start, end, cv::Scalar(0, 255, 0), 5, cv::LINE_8);

    cv::Point start1(100, 200);
    cv::Point end1(400, 240);
    cv::line(image, start1, end1, cv::Scalar(0, 0, 255), 10, cv::LINE_4);

    cv::namedWindow("draw line", cv::WINDOW_AUTOSIZE);
    cv::imshow("draw line", image);

    cv::waitKey(0);
}

int main() {
    testDrawLine();
    return 0;
}

image-20210626161225053

圆形

函数原型:

void cv::circle(InputOutputArray img, Point center, int radius,
                const Scalar &color, int thickness = 1, int lineType = LINE_8,
                int shift = 0)
  • img:为源图像
  • center:为画圆的圆心坐标
  • radius:为圆的半径
  • color:为设定圆的颜色,规则根据B(蓝)G(绿)R(红)
  • thickness: 如果是正数,表示组成圆的线条的粗细程度。如果是负数,则绘制一个实心圆
  • line_type: 线条的类型。默认是8
  • shift: 圆心坐标点和半径值的小数点位数
void testDrawCircle() {
    std::string img_path = "car.png";
    cv::Mat image = cv::imread(img_path, cv::IMREAD_COLOR);

    // unfilled circle - draws a circle.
    cv::Point centerCircle1(250, 100);
    int radiusCircle = 50;
    cv::Scalar colorCircle1(0, 255, 0);  // (B, G, R)
    int thicknessCircle1 = 5;

    cv::circle(image, centerCircle1, radiusCircle, colorCircle1,
               thicknessCircle1);

    // filled circle - draws a circle.
    cv::Point centerCircle2(400, 100);
    cv::Scalar colorCircle2(255, 0, 0);  // (B, G, R)

    // FILLED = -1
    cv::circle(image, centerCircle2, radiusCircle, colorCircle2, cv::FILLED);

    cv::namedWindow("draw circle", cv::WINDOW_AUTOSIZE);
    cv::imshow("draw circle", image);

    cv::waitKey(0);
}

输出:

image-20210626155638226

椭圆

函数原型:

void ellipse(Mat& img, Point center, Size axes, double angle, double startAngle,
             double endAngle, const Scalar& color, int thickness = 1,
             int lineType = 8, int shift = 0)
  • img:图像。
  • center:椭圆圆心坐标。
  • axes:主轴的长度:横轴长与纵轴长。
  • angle:偏转的角度。
  • start_angle:圆弧起始角的角度。
  • end_angle:圆弧终结角的角度。
  • color:线条的颜色。
  • thickness:线条的粗细程度。
  • line_type:线条的类型。
  • shift:圆心坐标点和数轴的精度。
void testDrawEllipse() {
    std::string img_path = "car.png";
    cv::Mat image = cv::imread(img_path, cv::IMREAD_COLOR);

    int thickness = 3;
    double angle = 30;  // 椭圆旋转角度
    // 第三个参数Size中的两个参数分别是横轴长、纵轴长。
    // 同理,thickness若是小于0,表示实心
    cv::ellipse(image, cv::Point(100, 100), cv::Size(90, 60), angle, 0, 360,
                cv::Scalar(255, 0, 0), thickness, cv::LINE_8);

    double angle1 = 0;  // 椭圆旋转角度
    cv::ellipse(image, cv::Point(300, 100), cv::Size(90, 60), angle1, 0, 300,
                cv::Scalar(0, 255, 0), cv::FILLED, cv::LINE_8);

    cv::namedWindow("draw Ellipse", cv::WINDOW_AUTOSIZE);
    cv::imshow("draw Ellipse", image);

    cv::waitKey(0);
}

输出:

image-20210626170231198

矩形

函数原型:

// 根据两个对角顶点画矩形
void rectangle(InputOutputArray img, Point pt1, Point pt2, const Scalar &color,
               int thickness = 1, int lineType = LINE_8, int shift = 0);
// 根据左上角点和长宽画矩形
void rectangle(InputOutputArray img, Rect rec, const Scalar &color,
               int thickness = 1, int lineType = LINE_8, int shift = 0);
  • img:图像。
  • rec:表征矩形的位置和长宽, (pt1 和 pt2是矩形对角的两个点)。
  • color:线条颜色 (RGB) 或亮度(灰度图像 )(grayscale image)。
  • thickness:组成矩形的线条的粗细程度。取负值时(如CV_FILLED)函数绘制填充了色彩的矩形。
  • line_type:线条的类型。见cvLine的描述
  • shift:坐标点的小数点位数。
void testDrawRectangle() {
    std::string img_path = "car.png";
    cv::Mat image = cv::imread(img_path, cv::IMREAD_COLOR);

    // unfilled
    cv::Point p1(50, 50), p2(200, 100);
    cv::Scalar colorRectangle1(0, 255, 0);
    cv::rectangle(image, p1, p2, colorRectangle1, 3);

    // filled
    cv::Point p3(250, 50), p4(400, 100);
    cv::Scalar colorRectangle2(255, 0, 0);
    cv::rectangle(image, p3, p4, colorRectangle2, cv::FILLED);

    // draw with Rect
    cv::Rect r(100, 150, 300, 50);
    cv::Scalar colorRectangle3(0, 255, 0);
    cv::rectangle(image, r, colorRectangle3, cv::FILLED);

    cv::namedWindow("draw rectangle", cv::WINDOW_AUTOSIZE);
    cv::imshow("draw rectangle", image);

    cv::waitKey(0);
}

输出:

image-20210626175041433

多边形

填充多边形

函数原型:

void fillPoly(Mat& img, const Point** pts, const int* npts, int ncontours,
              const Scalar& color, int lineType = 8, int shift = 0,
              Point offset = Point());
  • img:在img表示的图像上绘制
  • pts:是指向多边形数组的指针,必须是const修饰的
  • npts:是多边形顶点个数的数组名
  • ncontours:是绘制多边形的个数
void testDrawFillpoly() {
    std::string img_path = "car.png";
    cv::Mat image = cv::imread(img_path, cv::IMREAD_COLOR);
    // 创建一个二维数组,每一行存储一个多边形的点,总共存储了两个多边形的点
    cv::Point p[2][5] = {
        {cv::Point(50, 50), cv::Point(150, 50), cv::Point(180, 90),
         cv::Point(10, 190), cv::Point(200, 267)},
        {cv::Point(360, 60), cv::Point(420, 70), cv::Point(480, 120),
         cv::Point(370, 200), cv::Point(300, 90)}};
    // 这个指针数组存储每一个多边形的定点所在的一维数组的地址
    const cv::Point *pp[] = {p[0], p[1]};
    // 这个数组存储每一个多边形的定点个数
    int n[] = {5, 5};
    // 绘制两个个填充多边形
    cv::fillPoly(image, pp, n, 2, cv::Scalar(0, 255, 0));
    cv::namedWindow("draw fillPoly", cv::WINDOW_AUTOSIZE);
    cv::imshow("draw fillPoly", image);
    cv::waitKey(0);
}

输出:

image-20210626214406040

非填充多边形

函数原型:

void polylines(Mat& img, const Point** pts, const int* npts, int ncontours,
               bool isClosed, const Scalar& color, int thickness = 1,
               int lineType = 8, int shift = 0);
  • img:在img表示的图像上绘制
  • pts:是指向多边形数组的指针,必须是const修饰的
  • npts:是多边形顶点个数的数组名
  • ncontours:绘制多边形的个数
  • isClosed:表示多边形是否闭合,1表示闭合,0表示不闭合
  • color:是填充的颜色
void testDrawPolylines() {
    std::string img_path = "car.png";
    cv::Mat image = cv::imread(img_path, cv::IMREAD_COLOR);
    // 创建一个二维数组,每一行存储一个多边形的点,总共存储了两个多边形的点
    cv::Point p[2][5] = {
        {cv::Point(50, 50), cv::Point(150, 50), cv::Point(180, 90),
         cv::Point(10, 190), cv::Point(200, 267)},
        {cv::Point(360, 60), cv::Point(420, 70), cv::Point(480, 120),
         cv::Point(370, 200), cv::Point(300, 90)}};
    // 这个指针数组存储每一个多边形的定点所在的一维数组的地址
    const cv::Point *pp[] = {p[0], p[1]};
    // 这个数组存储每一个多边形的定点个数
    int n[] = {5, 5};
    // 绘制两个个填充多边形
    cv::polylines(image, pp, n, 2, false, cv::Scalar(0, 255, 0), 3);
    cv::namedWindow("draw polylines", cv::WINDOW_AUTOSIZE);
    cv::imshow("draw polylines", image);
    cv::waitKey(0);
}

非闭合输出:

image-20210626214746585

闭合输出:

image-20210626214823173

文字

函数原型:

void cv::putText(cv::Mat& img,        // 待绘制的图像
                 const string& text,  // 待绘制的文字
                 cv::Point origin,    // 文本框的左下角
                 int fontFace,        // 字体 (如cv::FONT_HERSHEY_PLAIN)
                 double fontScale,    // 尺寸因子,值越大文字越大
                 cv::Scalar color,    // 线条的颜色(RGB)
                 int thickness = 1,   // 线条宽度
                 int lineType = 8,  // 线型(4邻域或8邻域,默认8邻域)
                 bool bottomLeftOrigin = false  // true='origin at lower left'
);

这个函数是OpenCV的一个主要文字绘制方法,它可以简单地在图像上绘制一些文字,由text指定地文字将在以左上角为原点地文字框中以color指定地颜色绘制出来,除非bottomLeftOrigin标志设置为真,这种情况以左下角为原点,使用的字体由fontFace参数决定,支持的字体如下表格所示:

标识符描述
FONT_HERSHEY_SIMPLEX = 0!< normal size sans-serif font 普通大小无衬线字体
FONT_HERSHEY_PLAIN = 1!< small size sans-serif font 小号无衬线字体
FONT_HERSHEY_DUPLEX = 2!< normal size sans-serif font (more complex than FONT_HERSHEY_SIMPLEX) 普通大小无衬线字体
FONT_HERSHEY_COMPLEX = 3!< normal size serif font 普通大小无衬线字体比 FONT_HERSHEY_DUPLEX 更复杂
FONT_HERSHEY_TRIPLEX = 4!< normal size serif font (more complex than FONT_HERSHEY_COMPLEX 普通大小无衬字体,比 FONT_HERSHEY_COMPLEX 更复杂
FONT_HERSHEY_COMPLEX_SMALL = 5!< smaller version of FONT_HERSHEY_COMPLEX-小号版本的 FONT_HERSHEY_COMPLEX
FONT_HERSHEY_SCRIPT_SIMPLEX = 6!< hand-writing style font 手写字体
FONT_HERSHEY_SCRIPT_COMPLEX = 7!< more complex variant of FONT_HERSHEY_SCRIPT_SIMPLEX
FONT_ITALIC = 16!< flag for italic font

另外,在实际绘制文字之前,还可以使用cv::getTextSize()接口先获取待绘制文本框的大小,以方便放置文本框。具体调用形式如下:

cv::Size cv::getTextSize(const string& text, cv::Point origin, int fontFace,
                         double fontScale, int thickness, int* baseLine);
  • const string& text: 输入的文本文字
  • int fontFace: 文字字体类型
  • double fontScale: 字体缩放系数
  • int thickness: 字体笔画线宽
  • CV_OUT int* baseLine: 文字最底部y坐标

其中前四个参数都好理解:text为文本,fontFace为文本的字体类型,fontScale为文本大小的倍数(以字体库中的大小为基准而放大的倍数),thickness为文本的粗细。最后一个参数baseLine是指距离文本最低点对应的y坐标,乍听起来很乱,具体如图所示img

我们对于四线本应该很熟悉,在小学英语时会经常用这种本子写英文,借助其中的三条线可以帮助我们了解该函数。函数返回的Size中的height其实是图中两个红线的距离,而baseLine即下面红线与蓝线直接的距离。

程序如下:

void testDrawText() {
    std::string img_path = "mm.jpeg";
    cv::Mat src = cv::imread(img_path, cv::IMREAD_COLOR);
    cv::Mat image;
    cv::resize(src, image, cv::Size(src.cols / 2, src.rows / 2));
    const std::string text = "Hello World!";
    cv::Scalar color(0, 255, 0);
    int fontFace = cv::FONT_HERSHEY_SIMPLEX;
    cv::putText(image, text, cv::Point2i(10, 50), fontFace, 2, color, 1, 8,
                false);
    fontFace = cv::FONT_HERSHEY_PLAIN;
    cv::putText(image, text, cv::Point2i(10, 100), fontFace, 2, color, 1, 8,
                false);
    fontFace = cv::FONT_HERSHEY_DUPLEX;
    cv::putText(image, text, cv::Point2i(10, 150), fontFace, 2, color, 1, 8,
                false);
    fontFace = cv::FONT_HERSHEY_COMPLEX;
    cv::putText(image, text, cv::Point2i(10, 200), fontFace, 2, color, 1, 8,
                false);
    fontFace = cv::FONT_HERSHEY_TRIPLEX;
    // 左下角为原点,字体倒立显示
    cv::putText(image, text, cv::Point2i(10, 250), fontFace, 2, color, 1, 8,
                true);
    cv::Scalar color2(255, 0, 0);
    fontFace = cv::FONT_HERSHEY_COMPLEX_SMALL;
    cv::putText(image, text, cv::Point2i(10, 500), fontFace, 2, color2, 1, 8,
                false);
    fontFace = cv::FONT_HERSHEY_SCRIPT_SIMPLEX;
    cv::putText(image, text, cv::Point2i(10, 560), fontFace, 2, color2, 1, 8,
                false);
    fontFace = cv::FONT_HERSHEY_SCRIPT_COMPLEX;
    cv::putText(image, text, cv::Point2i(10, 620), fontFace, 2, color2, 1, 8,
                false);
    fontFace = cv::FONT_ITALIC | cv::FONT_HERSHEY_PLAIN;
    // 左下角为原点,字体倒立显示
    cv::putText(image, text, cv::Point2i(10, 680), fontFace, 2, color2, 1, 8,
                true);
    fontFace = cv::FONT_HERSHEY_SIMPLEX;  // 字体样式
    double fontScale = 2.0;  // 字体大小	
    int thickness = 3;//字体线条粗细
    int baseline = 0;
    cv::Size textSize = cv::getTextSize(text, fontFace, fontScale, thickness,
                                        &baseline);  // 居中显示
    cv::Scalar color3(0, 0, 255);
    cv::Point2i orgin(image.cols / 2 - textSize.width / 2,
                      image.rows / 2 - textSize.height / 2);
    cv::putText(image, text, orgin, fontFace, fontScale, color3, thickness, 8,
                false);
    cv::namedWindow("draw text", cv::WINDOW_AUTOSIZE);
    cv::imshow("draw text", image);
    cv::waitKey(0);
}

输出:

image-20210627073158677