一、问题
现有一条直线,给定箭头角度angle
,长度length
,颜色color
和厚度thickness
,要求在画出箭头。
二、分析:
已知角度angle
和长度length
,可求出l1
和l2
:
double l1 = length * cos(angle * CV_PI / 180), l2 = length * sin(angle * CV_PI / 180);
已知p1
,p2
和l1
,可求出p0
:
int i = (p2.x > p1.x) ? 1 : -1; // i,j代表p2、p3、p4相对于p0的正负
int j = (p2.y > p1.y) ? 1 : -1;
double a1 = abs(atan((p2.y - p1.y) / (p2.x - p1.x))); // 直线p1p2相对于x轴的角度,取正值
double w1 = l1 * cos(a1), h1 = l1 * sin(a1); // 用于计算p2相对于p0的宽高
Point2d p0(p2.x - w1 * i, p2.y - h1 * j);
已知直线,与垂直,可计算出相对轴的角度a2
:
double a2 = 90 * CV_PI / 180 - a1; // 直线p3p4相对于x轴的角度
已知p0
、l2
和a2
,可计算出p3
和p4
:
double w2 = l2 * cos(a2), h2 = l2 * sin(a2); // 用于计算p3和p4相对于p0的宽高
p3 = Point2d(p0.x - w2 * i, p0.y + h2 * j);
p4 = Point2d(p0.x + w2 * i, p0.y - h2 * j);
三、实现
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
using namespace cv;
Point2d g_p1(400, 300), g_p2(400, 500); // 直线
// 画箭头
// img: 所画的图片
// p1,p2: 直线的起点和终点
// angle: 箭头相对直线角度
// length: 箭头的长度
// color: 箭头的颜色
// thickness: 箭头的厚度
void draw_arrow(Mat img, const Point2d p1, const Point2d p2, const double angle, const double length, const Scalar color, const int thickness)
{
double l1 = length * cos(angle * CV_PI / 180), l2 = length * sin(angle * CV_PI / 180);
Point2d p3(0, 0), p4(0, 0);
int i = (p2.x > p1.x) ? 1 : -1; // i,j代表p2、p3、p4相对于p0的正负
int j = (p2.y > p1.y) ? 1 : -1;
double a1 = abs(atan((p2.y - p1.y) / (p2.x - p1.x))); // 直线p1p2相对于x轴的角度,取正值
double w1 = l1 * cos(a1), h1 = l1 * sin(a1); // 用于计算p2相对于p0的宽高
Point2d p0(p2.x - w1 * i, p2.y - h1 * j);
double a2 = 90 * CV_PI / 180 - a1; // 直线p3p4相对于x轴的角度
double w2 = l2 * cos(a2), h2 = l2 * sin(a2); // 用于计算p3和p4相对于p0的宽高
p3 = Point2d(p0.x - w2 * i, p0.y + h2 * j);
p4 = Point2d(p0.x + w2 * i, p0.y - h2 * j);
line(img, p2, p3, color, 2); //画箭头
line(img, p2, p4, color, 2);
}
// 鼠标回调函数
void mouse_callback(int event, int x, int y, int flags, void *param)
{
static Point2d p(0, 0), p1(0, 0), p2(0, 0);
static int n = -1;
switch (event)
{
case cv::EVENT_LBUTTONDOWN: // 鼠标左键点击
{
p1 = Point2d(x, y);
int w = 15, h = 15;
Rect r1(g_p1.x - w, g_p1.y - h, 2 * w, 2 * h);
Rect r2(g_p2.x - w, g_p2.y - h, 2 * w, 2 * h);
if (r1.contains(p1)) // 鼠标落在g_p1
{
n = 1;
p = g_p1;
}
else if (r2.contains(p1)) // 鼠标落在g_p2
{
n = 2;
p = g_p2;
}
break;
}
case cv::EVENT_MOUSEMOVE: // 鼠标移动
p2 = Point2d(x, y);
if (n == 1)
{
g_p1 = p + p2 - p1;
}
else if (n == 2)
{
g_p2 = p + p2 - p1;
}
break;
case cv::EVENT_LBUTTONUP: // 鼠标左键释放
p1 = Point2d(0, 0);
p2 = Point2d(0, 0);
n = -1;
break;
default:
break;
}
}
// 主函数
int main()
{
string window_name = "image";
namedWindow(window_name, WINDOW_AUTOSIZE);
int w = 800, h = 600;
Mat image_original = Mat(h, w, CV_8UC3, Scalar(255, 255, 255));
cv::setMouseCallback(window_name, mouse_callback); // 调用鼠标回调函数
while (true)
{
Mat img = image_original.clone(); // 拷贝空白图片,方便重复画图
line(img, g_p1, g_p2, Scalar(255, 0, 0), 2); // 画蓝线a
draw_arrow(img, g_p1, g_p2, 20, 20, Scalar(255, 0, 0), 2); // 画箭头
imshow(window_name, img);
if (waitKey(3) > 0)
break;
}
return 0;
}
操作方法:
鼠标点击直线起点或终点并按住移动,可改变直线。在键盘按任意的键可退出程序。
运行结果: