學(xué)習(xí)OpenCV3:在空白圖片上畫(huà)虛線


一、背景

??在OpenCV中,可以畫(huà)圓、線、矩形、橢圓和多邊形,但并不能畫(huà)出虛線,現(xiàn)希望通過(guò)OpenCV已有的函數(shù)畫(huà)出由點(diǎn)或線組成的虛線。

cv::circle() // 畫(huà)一個(gè)簡(jiǎn)單圓
cv::clipLine() // 判斷一條直線是否在給定的矩形內(nèi)
cv::ellipse() // 畫(huà)一個(gè)橢圓,可以傾斜,或者只有部分圓弧
cv::ellipse2Poly() // 計(jì)算一個(gè)近似橢圓的多邊形
cv::fillConvexPoly() // 畫(huà)一個(gè)填充的簡(jiǎn)單多邊形
cv::fillPoly() // 畫(huà)一個(gè)填充的任意多邊形
cv::line() // 畫(huà)一條簡(jiǎn)單直線
cv::rectangle() // 畫(huà)一個(gè)簡(jiǎn)單矩形
cv::polyLines() // 畫(huà)多重折線

二、實(shí)現(xiàn)

注意事項(xiàng):
1、OpenCV雖然沒(méi)有畫(huà)點(diǎn)的函數(shù),但可通過(guò)cv::circle()實(shí)現(xiàn),只要半徑夠短,線夠厚,一個(gè)實(shí)心圓可看成一個(gè)點(diǎn)。
2、創(chuàng)建空白圖片時(shí),其圖片類型應(yīng)選擇CV_32FC3CV_64FC3。存儲(chǔ)像素的位數(shù)越多,畫(huà)出的斜線越直。
3、兩個(gè)點(diǎn)畫(huà)出一條線,故使用g_firstg_last兩個(gè)Point來(lái)存儲(chǔ)所畫(huà)的線。畫(huà)線主要分為水平線、垂直線和傾斜線三種情況。傾斜線表示方法如下,設(shè)置每個(gè)點(diǎn)或線的間隔,已知x即可求出y,由此可確定每個(gè)點(diǎn)或每條線的坐標(biāo)。
\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

操作方法:
1、運(yùn)行程序,在空白圖片上按下鼠標(biāo)左鍵并移動(dòng),鼠標(biāo)左鍵釋放時(shí)即可畫(huà)出一條虛線。鼠標(biāo)右鍵點(diǎn)擊可擦掉已畫(huà)出的虛線。
2、程序包含2種模式,模式1畫(huà)出由點(diǎn)組成的虛線,模式2畫(huà)出由線組成的虛線。鼠標(biāo)默認(rèn)使用模式1,可通過(guò)鍵盤輸入12選擇不同模式。鍵盤輸入除12的鍵會(huì)自動(dòng)退出程序。

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

Point2f g_first, g_last; // 線的起點(diǎn)和終點(diǎn)

// 畫(huà)由點(diǎn)組成的虛線
void draw_dotted_line1(Mat img, Point2f p1, Point2f p2, Scalar color, int thickness)
{
    float n = 15; //虛點(diǎn)間隔
    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; // 矯正虛點(diǎn)間隔,使虛點(diǎn)數(shù)為整數(shù)

    circle(img, p1, 1, color, thickness); // 畫(huà)起點(diǎn)
    circle(img, p2, 1, color, thickness); // 畫(huà)終點(diǎn)
    // 畫(huà)中間點(diǎn)
    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軸都不垂直或平行
    {
        // 直線方程的兩點(diǎn)式:(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);
    }
}

// 畫(huà)由線組成的虛線
void draw_dotted_line2(Mat img, Point2f p1, Point2f p2, Scalar color, int thickness)
{
    float n = 15; //線長(zhǎng)度
    float w = p2.x - p1.x, h = p2.y - p1.y;
    float l = sqrtf(w * w + h * h);
    // 矯正線長(zhǎng)度,使線個(gè)數(shù)為奇數(shù)
    int m = l / n;
    m = m % 2 ? m : m + 1;
    n = l / m;

    circle(img, p1, 1, color, thickness); // 畫(huà)起點(diǎn)
    circle(img, p2, 1, color, thickness); // 畫(huà)終點(diǎn)
    // 畫(huà)中間點(diǎn)
    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軸都不垂直或平行
    {
        // 直線方程的兩點(diǎn)式:(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);
        }
    }
}

// 矯正坐標(biāo)
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;
}

// 鼠標(biāo)回調(diào)函數(shù)
void mouse_callback(int event, int x, int y, int flags, void *param)
{
    static size_t i = 0; // 鼠標(biāo)操作步驟的標(biāo)識(shí)
    switch (event)
    {
    case cv::EVENT_LBUTTONDOWN: // 鼠標(biāo)左鍵點(diǎn)擊
        if (i == 0)
        {
            g_first = Point2f(x, y);        // 保存線的起點(diǎn)
            g_last = Point2f(x + 1, y + 1); // 保存線的終點(diǎn)
            i = 1;
        }
        break;
    case cv::EVENT_MOUSEMOVE: // 鼠標(biāo)移動(dòng)
        if (i == 1)
            g_last = Point2f(x, y); // 刷新線的終點(diǎn)
        break;
    case cv::EVENT_LBUTTONUP: // 鼠標(biāo)左鍵釋放
        if (i == 1)
            i = 2; // 不再畫(huà)線
        break;
    case cv::EVENT_RBUTTONDOWN: // 鼠標(biāo)右鍵點(diǎn)擊,清除內(nèi)容
        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,畫(huà)出的斜線更直
    cv::imshow(window_name, image_original);
    cv::setMouseCallback(window_name, mouse_callback); // 鼠標(biāo)函數(shù)
    char flag = '1';
    while (true)
    {
        Mat img = image_original.clone(); // 克隆圖片,方便在同一張圖片上多次畫(huà)線
        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畫(huà)由點(diǎn)組成的虛線
        else
            draw_dotted_line2(img, g_first, g_last, Scalar(0, 255, 0), 2); // 模式2畫(huà)由線組成的虛線
        imshow(window_name, img);

        char c = waitKey(1); // 獲取鍵盤輸入的字符
        if (c == '1' || c == '2')
            flag = c;
        else if (c > 0) // 若不按'1'或'2',按其它字符鍵會(huì)自動(dòng)退出。
            break;
    }
    return 0;
}

運(yùn)行結(jié)果:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容