一、背景
??在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_32FC3或CV_64FC3。存儲(chǔ)像素的位數(shù)越多,畫(huà)出的斜線越直。
3、兩個(gè)點(diǎn)畫(huà)出一條線,故使用g_first和g_last兩個(gè)Point來(lái)存儲(chǔ)所畫(huà)的線。畫(huà)線主要分為水平線、垂直線和傾斜線三種情況。傾斜線表示方法如下,設(shè)置每個(gè)點(diǎn)或線的間隔,已知即可求出
,由此可確定每個(gè)點(diǎn)或每條線的坐標(biāo)。
操作方法:
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ò)鍵盤輸入1或2選擇不同模式。鍵盤輸入除1或2的鍵會(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é)果:

