学习OpenCV3:画带箭头的直线

    技术2024-01-01  96


    一、问题

      现有一条直线 p 1 p 2 p_1p_2 p1p2,给定箭头角度angle,长度length,颜色color和厚度thickness,要求在 p 2 p_2 p2画出箭头。

    二、分析:

      已知角度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);

      已知直线 p 1 p 2 p_1p_2 p1p2 p 3 p 4 p_3p_4 p3p4 p 1 p 2 p_1p_2 p1p2垂直,可计算出 p 3 p 4 p_3p_4 p3p4相对 x x x轴的角度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; }

    操作方法:   鼠标点击直线起点或终点并按住移动,可改变直线。在键盘按任意的键可退出程序。

    运行结果:

    Processed: 0.014, SQL: 9