学习OpenCV3:在空白图片上画虚线


一、背景

  在OpenCV中,可以画圆、线、矩形、椭圆和多边形,但并不能画出虚线,现希望通过OpenCV已有的函数画出由点或线组成的虚线。

cv::circle() // 画一个简单圆
cv::clipLine() // 判断一条直线是否在给定的矩形内
cv::ellipse() // 画一个椭圆,可以倾斜,或者只有部分圆弧
cv::ellipse2Poly() // 计算一个近似椭圆的多边形
cv::fillConvexPoly() // 画一个填充的简单多边形
cv::fillPoly() // 画一个填充的任意多边形
cv::line() // 画一条简单直线
cv::rectangle() // 画一个简单矩形
cv::polyLines() // 画多重折线

二、实现

注意事项:
1、OpenCV虽然没有画点的函数,但可通过cv::circle()实现,只要半径够短,线够厚,一个实心圆可看成一个点。
2、创建空白图片时,其图片类型应选择CV_32FC3CV_64FC3。存储像素的位数越多,画出的斜线越直。
3、两个点画出一条线,故使用g_firstg_last两个Point来存储所画的线。画线主要分为水平线、垂直线和倾斜线三种情况。倾斜线表示方法如下,设置每个点或线的间隔,已知 x x x即可求出 y y y,由此可确定每个点或每条线的坐标。
y − y 1 y 2 − y 1 = x − x 1 x 2 − x 1 ⇒ y = ( y 2 − y 1 ) ( x 2 − x 1 ) ∗ ( x − x 1 ) + y 1 \frac{y-y_1}{y_2-y_1} = \frac{x-x_1}{x_2-x_1} \Rightarrow y = \frac{(y2-y1)}{(x2-x1)}*(x-x1)+y1 y2y1yy1=x2x1xx1y=(x2x1)(y2y1)(xx1)+y1

操作方法:
1、运行程序,在空白图片上按下鼠标左键并移动,鼠标左键释放时即可画出一条虚线。鼠标右键点击可擦掉已画出的虚线。
2、程序包含2种模式,模式1画出由点组成的虚线,模式2画出由线组成的虚线。鼠标默认使用模式1,可通过键盘输入12选择不同模式。键盘输入除12的键会自动退出程序。

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

Point2f g_first, g_last; // 线的起点和终点

// 画由点组成的虚线
void draw_dotted_line1(Mat img, Point2f p1, Point2f p2, Scalar color, int thickness)
{
    float n = 15; //虚点间隔
    float w = p2.x - p1.x, h = p2.y - p1.y;
    float l = sqrtf(w * w + h * h);
    int m = l / n;
    n = l / m; // 矫正虚点间隔,使虚点数为整数

    circle(img, p1, 1, color, thickness); // 画起点
    circle(img, p2, 1, color, thickness); // 画终点
    // 画中间点
    if (p1.y == p2.y) // 水平线:y = m
    {
        float x1 = min(p1.x, p2.x);
        float x2 = max(p1.x, p2.x);
        for (float x = x1 + n; x < x2; x = x + n)
            circle(img, Point2f(x, p1.y), 1, color, thickness);
    }
    else if (p1.x == p2.x) // 垂直线, x = m
    {
        float y1 = min(p1.y, p2.y);
        float y2 = max(p1.y, p2.y);
        for (float y = y1 + n; y < y2; y = y + n)
            circle(img, Point2f(p1.x, y), 1, color, thickness);
    }
    else // 倾斜线,与x轴、y轴都不垂直或平行
    {
        // 直线方程的两点式:(y-y1)/(y2-y1)=(x-x1)/(x2-x1) -> y = (y2-y1)*(x-x1)/(x2-x1)+y1
        float m = n * abs(w) / l;
        float k = h / w;
        float x1 = min(p1.x, p2.x);
        float x2 = max(p1.x, p2.x);
        for (float x = x1 + m; x < x2; x = x + m)
            circle(img, Point2f(x, k * (x - p1.x) + p1.y), 1, color, thickness);
    }
}

// 画由线组成的虚线
void draw_dotted_line2(Mat img, Point2f p1, Point2f p2, Scalar color, int thickness)
{
    float n = 15; //线长度
    float w = p2.x - p1.x, h = p2.y - p1.y;
    float l = sqrtf(w * w + h * h);
    // 矫正线长度,使线个数为奇数
    int m = l / n;
    m = m % 2 ? m : m + 1;
    n = l / m;

    circle(img, p1, 1, color, thickness); // 画起点
    circle(img, p2, 1, color, thickness); // 画终点
    // 画中间点
    if (p1.y == p2.y) //水平线:y = m
    {
        float x1 = min(p1.x, p2.x);
        float x2 = max(p1.x, p2.x);
        for (float x = x1, n1 = 2 * n; x < x2; x = x + n1)
            line(img, Point2f(x, p1.y), Point2f(x + n, p1.y), color, thickness);
    }
    else if (p1.x == p2.x) //垂直线, x = m
    {
        float y1 = min(p1.y, p2.y);
        float y2 = max(p1.y, p2.y);
        for (float y = y1, n1 = 2 * n; y < y2; y = y + n1)
            line(img, Point2f(p1.x, y), Point2f(p1.x, y + n), color, thickness);
    }
    else // 倾斜线,与x轴、y轴都不垂直或平行
    {
        // 直线方程的两点式:(y-y1)/(y2-y1)=(x-x1)/(x2-x1) -> y = (y2-y1)*(x-x1)/(x2-x1)+y1
        float n1 = n * abs(w) / l;
        float k = h / w;
        float x1 = min(p1.x, p2.x);
        float x2 = max(p1.x, p2.x);
        for (float x = x1, n2 = 2 * n1; x < x2; x = x + n2)
        {
            Point p3 = Point2f(x, k * (x - p1.x) + p1.y);
            Point p4 = Point2f(x + n1, k * (x + n1 - p1.x) + p1.y);
            line(img, p3, p4, color, thickness);
        }
    }
}

// 矫正坐标
void correct(Point2f &p, const Point2f &p1, const Point2f &p2)
{
    if (p.x < p1.x)
        p.x = p1.x;
    else if (p.x > p2.x)
        p.x = p2.x;
    if (p.y < p1.y)
        p.y = p1.y;
    else if (p.y > p2.y)
        p.y = p2.y;
}

// 鼠标回调函数
void mouse_callback(int event, int x, int y, int flags, void *param)
{
    static size_t i = 0; // 鼠标操作步骤的标识
    switch (event)
    {
    case cv::EVENT_LBUTTONDOWN: // 鼠标左键点击
        if (i == 0)
        {
            g_first = Point2f(x, y);        // 保存线的起点
            g_last = Point2f(x + 1, y + 1); // 保存线的终点
            i = 1;
        }
        break;
    case cv::EVENT_MOUSEMOVE: // 鼠标移动
        if (i == 1)
            g_last = Point2f(x, y); // 刷新线的终点
        break;
    case cv::EVENT_LBUTTONUP: // 鼠标左键释放
        if (i == 1)
            i = 2; // 不再画线
        break;
    case cv::EVENT_RBUTTONDOWN: // 鼠标右键点击,清除内容
        i = 0;
        g_first = Point2f(0, 0);
        g_last = Point2f(0, 0);
        break;
    default:
        break;
    }
}

int main()
{
    const int w = 800, h = 600;
    const string window_name = "image";
    Mat image_original = cv::Mat(h, w, CV_32FC3, cv::Scalar(255, 255, 255)); // 选择CV_32FC3,画出的斜线更直
    cv::imshow(window_name, image_original);
    cv::setMouseCallback(window_name, mouse_callback); // 鼠标函数
    char flag = '1';
    while (true)
    {
        Mat img = image_original.clone(); // 克隆图片,方便在同一张图片上多次画线
        correct(g_last, Point2f(0, 0), Point2f(img.cols, img.rows));
        if (flag == '1')
            draw_dotted_line1(img, g_first, g_last, Scalar(0, 255, 0), 2); // 模式1画由点组成的虚线
        else
            draw_dotted_line2(img, g_first, g_last, Scalar(0, 255, 0), 2); // 模式2画由线组成的虚线
        imshow(window_name, img);

        char c = waitKey(1); // 获取键盘输入的字符
        if (c == '1' || c == '2')
            flag = c;
        else if (c > 0) // 若不按'1'或'2',按其它字符键会自动退出。
            break;
    }
    return 0;
}

运行结果:

猜你喜欢

转载自blog.csdn.net/qq_34801642/article/details/106799817
今日推荐