目录

  • 1、前言
  • 2、基本绘图函数
  • 3、原子图绘制
  • 4、多边形绘制+最小外接矩形
  • 5、鼠标绘图+最小外接矩形


1、前言

图像处理中经常用到基本图形的绘制,比如直线、圆、矩形,在上一文中在直方图绘制中使用了OpenCV的line()函数来绘制直方图,不仅如此,基本图形在很多大型项目中也会频频使用,比如物体识别中,就需要绘制矩形来框选物体所在区域作为候选区,方便后续特征识别处理,本文通过介绍OpenCV基本绘图函数,如line()、Rectangle()等,来完成原子图绘制,另外拓展两个小demo

  • 绘制任意多边形并求最小外接矩形
  • 鼠标控制任意图形绘制并求最小外接矩形

通过本文学习可以熟悉OpenCV基本图形绘制、图形轮廓最小外接矩形、鼠标响应与回调函数。

2、基本绘图函数

OpenCV绘图函数

作用

line()

直线绘制

rectangle()

矩形绘制

ellipse()

椭圆绘制

circle()

圆形绘制

filllines()

多边形绘制

fillPoly()

填充多边形绘制

OpenCV中直线绘制的函数是line,其函数原型如下:

void line(Mat& img,  //需绘制线段的图像
		  Point pt1,  //直线起点
		  Point pt2,  //直线终点
		  const Scalar& color,  //直线的颜色,Scalar类定义
		  int thickness=1,  //线宽,默认为1
		  int lineType=8,  //线型,可以取值8、4和CV_AA,分别代表8邻接连接线,4邻接连接线和反锯齿连接线。
		  int shift=0  //坐标小数点位数,不用管
		  )

【注】绘制线段图像img,不能设为const Mat型,因为绘制过程需要修改img像素值,之前因为这个总是找不到error;线型lineType不是表示工程上的虚线、点划线等,而是像素点连接方式,一般取8-邻域连接方式,具体可参考相关Blog。

OpenCV中矩形绘制的函数是rectangle,其函数原型如下:

void rectangle(InputOutputArray img,  //需绘制矩形的图像
			   Point pt1,  //矩形的一个顶点
			   Point pt2,  //矩形的另一个顶点
			   const Scalar &color,  //矩形线条颜色
			   int thickness = 1,  //线宽,默认为1
			   int lineType = 8,   //线型,默认8-邻域
			   int shift = 0  //坐标小数点位数,不用管
			   )
//重载rectangle定义
void rectangle(Mat &img,  //需绘制矩形的图像
			   Rect rec,  //Rect类定义的矩形
			   const Scalar &color,  //矩形线条颜色
			   int thickness = 1,  //线宽,默认为1
			   int lineType = 8,  //线型,默认8-邻域
			   int shift = 0  //坐标小数点位数,不用管
			   )

【注】笔者在coding过程中发现rectangle原型有两种定义方式,一是通过两个顶点定义矩形的形状,二是通过Rect类定义一个矩形,对于之前定义过的rectangle,可以直接调用快速绘制。

OpenCV中椭圆绘制的函数是ellipse,其函数原型如下:

void ellipse(Mat&img,   //需绘制椭圆的图像
			 Point center,  //椭圆中心点
			 Size axes,  //轴的长度,Size类型
			 double angle,  //椭圆倾斜的角度,0度时放正
			 double startAngle,  //椭圆圆弧起始角度
			 double endAngle,   //椭圆圆弧终止角度
			 const Scalar&color,  //线的颜色
			 intthickness=1,  //线宽,默认为1
			 int lineType=8,  //线型,默认8-邻域
			 intshift=0  //坐标小数点位数,不用管
			 )
//重载ellipse定义
void ellipse(Mat& img,  //需绘制椭圆的图像
			 const RotatedRect& box,  //椭圆外接矩形
			 const Scalar& color,  //线的颜色
			 int thickness=1,  //线宽,默认为1
			 intlineType=8  //线型,默认8-邻域
			 )

【注】在ellipse()函数重载定义中,可以通过椭圆外接矩形的方式绘制唯一一个椭圆,因为一个椭圆只有一个最小外接矩形,这个在后面的demo中也有涉及

OpenCV中圆形绘制的函数是circle,其函数原型如下:

void circle(InputOutputArray img,  //需绘制圆形的图像
			cv::Point center,  //圆形中心点
			int radius,  //半径
			const Scalar &color,  //线的颜色
			int thickness = 1,  //线宽,默认为1
			int lineType = 8,   //线型,默认8-邻域
			int shift = 0  //坐标小数点位数,不用管
			)

【注】线宽thickness定义为-1时,表示圆形内部被填充且填充颜色与线的颜色一致。

OpenCV中多边形绘制的函数是filllines,其函数原型如下:

void polylines(InputOutputArray img,  //需绘制多边形的图像
			   InputArrayOfArrays pts,  //多边形顶点向量
			   bool isClosed,   //多边形是否闭合
			   const Scalar &color,  //线的颜色
			   int thickness = 1,  //线宽,默认为1
			   int lineType = 8,   //线型,默认8-邻域
			   int shift = 0  //坐标小数点位数,不用管
			   )

//重载polylines定义
void polylines(Mat &img,   //需绘制多边形的图像
			   const Point *const *pts,  //多边形顶点集,Point数组表示
			   const int *npts,  //多边形顶点数目
			   int ncontours,   //多边形边数
			   bool isClosed,   //多边形是否闭合
			   const Scalar &color,  //线的颜色
			   int thickness = 1,  //线宽,默认为1
			   int lineType = 8,   //线型,默认8-邻域
			   int shift = 0  //坐标小数点位数,不用管
			   )

一般用vector<vector>类型的二维点阵表示多边形顶点向量,它涵盖了多边形顶点、边和数量。另外当特别要求多边形填充时,会用到另一个多边形函数fillPoly,虽然和polylines差不多,但在这里还是写出来,也方便以后查询。其函数原型如下:

void fillPoly(InputOutputArray img,  //需绘制和填充多边形的图像
			  InputArrayOfArrays pts,  //多边形顶点向量
			  const cv::Scalar &color,  //多边形填充颜色
			  int lineType = 8,  //线型,默认8-邻域
			  int shift = 0,   //坐标小数点位数,不用管
			  Point offset = cv::Point() //平移点
			  )
			  
//重载fillPoly定义
void fillPoly(Mat &img,  //需绘制和填充多边形的图像
			  const Point **pts,  //多边形顶点集,Point数组表示
			  const int *npts,  //多边形顶点数目
			  int ncontours,   //多边形边数
			  const cv::Scalar &color,  //多边形填充颜色
			  int lineType = 8,  //线型,默认8-邻域
			  int shift = 0,  //坐标小数点位数,不用管
			  Point offset = cv::Point()  //平移点
			  )

  绘制实心目标时,一般将thickness设为-1即可,但是线颜色和填充颜色一致,多边形填充的填充颜色可任意设置。

3、原子图绘制

参考《OpenCV3编程入门》,以绘制原子图模型为例,展示一下OpenCV中基本绘图函数的使用,代码如下:

#include<opencv2/opencv.hpp>
#define WIDTH 600  //宏定义显示窗口大小
using namespace cv;

void Draw_Ellipse(const Mat& image,const double angle){
    int thickness=2;
    int lineType=8;

    ellipse(image,Point(WIDTH/2,WIDTH/2),Size(WIDTH/4,WIDTH/16),angle,0,360,Scalar(255,0,0),thickness,lineType);
}

void Draw_centerCircle(const Mat& image,Point center){
    int thickness=-1;
    int lineType=8;

    circle(image,center,WIDTH/32,Scalar(0,0,255),thickness,lineType);
    
}

void Draw_line(const Mat& image,Point start,Point end){
    int thickness=2;
    int lineType=8;

    line(image,start,end,Scalar(0,255,0),thickness,lineType);
}

int main()
{
    //1、基本图像绘制
    //2、多变形最小外接矩形
    //3、鼠标控制基本图形绘制+求外接矩形
    Mat atomImage=Mat::zeros(WIDTH,WIDTH,CV_8UC3);  //绘制空白Mat画板
    
    //绘制中心线
    Draw_line(atomImage,Point(WIDTH/2+WIDTH*3/8,WIDTH/2),Point(WIDTH/2-WIDTH*3/8,WIDTH/2));
    Draw_line(atomImage,Point(WIDTH/2,WIDTH/2+WIDTH*3/8),Point(WIDTH/2,WIDTH/2-WIDTH*3/8));
    
    //绘制椭圆
    Draw_Ellipse(atomImage,0);
    Draw_Ellipse(atomImage,45);
    Draw_Ellipse(atomImage,90);
    Draw_Ellipse(atomImage,135);
   
    //绘制圆心
    Draw_centerCircle(atomImage,Point(WIDTH/2,WIDTH/2));

	imshow("AtomImage",atomImage);
    waitKey();
    return 0;
}

效果如下:

python opencv对矩形图像进行裁剪 opencv画矩形函数_OpenCV

4、多边形绘制+最小外接矩形

#include<iostream>
#include<vector>
#include<opencv2/opencv.hpp>
#define WIDTH 600  //宏定义显示窗口大小
using namespace cv;
using namespace std;

void Draw_polygon(Mat& image){
    int lineType=8;
    //创建多边形点: 可自行创建
    Point point[1][5]; //二维数组表示顶点集
    point[0][0]=Point(115,220);
    point[0][1]=Point(460,125);
    point[0][2]=Point(566,350);
    point[0][3]=Point(435,365);
    point[0][4]=Point(310,510);
    
    const Point* ppt=point[0];  //point[0]地址作为顶点集首地址
    int npt=5; //顶点个数
    polylines(image, &ppt, &npt, 1, 1, Scalar(0,0255),1,8,0);   //绘制多边形不填充
    //fillPoly(image,&ppt,&npt,1,Scalar(255,0,0),lineType);   //绘制多边形填充
}

int main()
{
	//绘制多边形 + 外接矩形
    Mat polyImage=Mat::zeros(WIDTH,WIDTH,CV_8UC3);  //绘制空白Mat画板
    Draw_polygon(polyImage);
    Mat poly_gray;
    cvtColor(polyImage,poly_gray,CV_RGB2GRAY); //灰度处理

    vector<vector<Point>>contours; //轮廓(二维点集)
    vector<Vec4i> hierarchy;  
    vector<RotatedRect> rect;  //rect最小旋转矩形
    
    findContours(poly_gray, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);  //查找轮廓
    for (int i = 0; i < contours.size(); i++)  //绘制最小外接矩形
    {
        rect.push_back(minAreaRect(contours[i]));
        Point2f vertices[4];      //定义矩形的4个顶点
        rect[i].points(vertices);   //计算矩形的4个顶点
        for (int i = 0; i < 4; i++)
            line(polyImage, vertices[i], vertices[(i + 1) % 4], Scalar(255, 255, 255),1);
        cout <<"width is:"<<rect[i].size.width << endl;
        cout << "height is:" << rect[i].size.height << endl;
    }

	imshow("polyImage",polyImage);
    waitKey();
    return 0;
}

因为vector<RotatedRect>类型可以自动寻找最小外接矩形,而多边形的最小外接矩形只有一个,所以可以表示出来。效果如下:

python opencv对矩形图像进行裁剪 opencv画矩形函数_邻域_02

5、鼠标绘图+最小外接矩形

OpenCV中进行鼠标操作主要用到setMouseCallback这个函数,函数原型如下:

void setMouseCallback(const String& winname,  //鼠标响应窗口名
					  MouseCallback onMouse,  //鼠标响应函数,通过回调函数处理鼠标事件
					  void* userdata = 0  //用户自定义的参数
					  );

主要操作在回调函数onMouse中设置:

void on_Mouse(int event,  //表示鼠标事件类型的常量
			  int x,   //鼠标指针在图像坐标系的横坐标
			  int y,   //鼠标指针在图像坐标系的纵坐标
			  int flags,  //鼠标事件标志的常量
			  void* param //用户可自定义的参数
			  );

鼠标事件Event的类型(字母和数字完全等价):

#define CV_EVENT_MOUSEMOVE 0  //滑动
#define CV_EVENT_LBUTTONDOWN 1  //左键点击
#define CV_EVENT_RBUTTONDOWN 2  //右键点击
#define CV_EVENT_MBUTTONDOWN 3  //中键点击
#define CV_EVENT_LBUTTONUP 4   //左键放开
#define CV_EVENT_RBUTTONUP 5   //右键放开
#define CV_EVENT_MBUTTONUP 6   //中键放开
#define CV_EVENT_LBUTTONDBLCLK 7  //左键双击
#define CV_EVENT_RBUTTONDBLCLK 8  //右键双击
#define CV_EVENT_MBUTTONDBLCLK 9  //中键双击

这里的鼠标响应函数onMouse和混动条(Trackbar)回调函数一样
需要进行的操作全在回调函数onMouse里面进行。

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

由于回调函数在main函数体外定义,所以回调函数内部用到的变量需全局定义,当参数量不大的时候可以设置一些外部全局变量,但是这就使得程序内存开销增大,可以通过用户自定义结构体或类来实现体内传参调用,就避免了额外定义全局变量。

代码如下:

#include<iostream>
#include<vector>
#include<opencv2/opencv.hpp>
#define WIDTH 600  //宏定义显示窗口大小
using namespace cv;
using namespace std;

void Draw_circumRect(Mat& src,Mat& grayImage){
    vector<vector<Point>>contours;
    vector<Vec4i> hierarchy;
    vector<RotatedRect>rect;
    //【5】查找轮廓
    findContours(grayImage, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    for (int i = 0; i < contours.size(); i++)
    {
        rect.push_back(minAreaRect(contours[i]));
        Point2f vertices[4];      //定义矩形的4个顶点
        rect[i].points(vertices);   //计算矩形的4个顶点
        for (int i = 0; i < 4; i++)
            line(src, vertices[i], vertices[(i + 1) % 4], Scalar(255, 255, 255),1);
        cout <<"width的值:"<<rect[i].size.width << endl;
        cout << "height的值:" << rect[i].size.height << endl;//其实只有一个外接矩形
    }
}

int point_num=0;
Point P0,P1,P2;
Mat img,newImg,grayImg;
void on_Mouse(int event,int x,int y,int flag,void* para){
    if (event == CV_EVENT_LBUTTONDOWN){
        P0=Point(x,y);
        P1=Point(x,y); //鼠标按下作为起始点
    }
    else if ((event == CV_EVENT_MOUSEMOVE) && (flag==1))//鼠标按下并且光标移动
    {
        P2=Point(x,y);

        //从起点连线
        line(img,P1,P2,Scalar(255,255,255),3,8);

        P1=P2;
        point_num++;
    }
    else if (event == 4){
        line(img,P0,P1,Scalar(255,255,255),3,8);
        //point_num=0; //记录每次绘制Point数量

        img.copyTo(newImg);
        cvtColor(newImg,grayImg,CV_RGB2GRAY);
        Draw_circumRect(newImg,grayImg);
        imshow("circum_Rect",newImg);

    }
}

int main()
{
	int key=0;
    img=Mat::zeros(WIDTH,WIDTH,CV_8UC3);
    namedWindow("mouse_Graph"); //鼠标响应窗口
    setMouseCallback("mouse_Graph",on_Mouse); //鼠标回调函数
    while(1){
        imshow("mouse_Graph", img);
        key=waitKey(30);
        if (key == 27){   //按下ESC退出整个程序,保存视频文件到磁盘
			break;
		}
    }
    
    waitKey();
    return 0;
}

效果如下:

python opencv对矩形图像进行裁剪 opencv画矩形函数_#define_03


  当然也可以自己加载一幅图像,灰度化后进行阈值分割,得到物体边缘,最后用一个最小矩形框框出物体或ROI区域,其实上面的过程就是物体识别过程中得到候选框的过程,这只适用物体之间无交叉的情况,对于多物体检测可以参考R-CNN、Fast R-CNN等,它们在获取候选框的时候还是有很多经典的方法的,比如划窗法、RPN等。后面再更新~