學(xué)習(xí)OpenCV3:判斷兩條直線相交闯估,并計(jì)算交點(diǎn)和夾角


一睹欲、問題

??已知兩條直線l_1(x_1,y_1,x_2,y_2)l_2(x_3,y_3,x_4,y_4)供炼,現(xiàn)希望判斷l_1l_2間是否相交。若相交窘疮,計(jì)算出兩條直線的交點(diǎn)和夾角袋哼。

二、分析

1闸衫、直線方程

\frac{y-y_1}{y_2-y_1} = \frac{x-x_1}{x_2-x_1} \Rightarrow \begin{cases} ax+by+c=0 \\a=-(y_2-y_1) \\ b = x_2-x_1 \\ c = (y_2-y_1)x_1 - (x_2-x_1) y_1 \\ k = \frac{-a}涛贯\end{cases}

??l_1的直線方程:
a_1x+b_1y+c_1=0

??l_2的直線方程:
a_2x+b_2y+c_2=0

??提示:ab不能同時(shí)為0。若ab同時(shí)為0蔚出,起點(diǎn)和終點(diǎn)重合弟翘,該直線實(shí)際上是一個(gè)點(diǎn)。

2骄酗、判斷相交

??當(dāng)l_1垂直于x軸稀余,l_2傾斜于x軸時(shí),l_1l_2相交:

    b1==0 && b2!=0

??當(dāng)l_1傾斜于x軸趋翻,l_2垂直于x軸時(shí)睛琳,l_1l_2相交:

    b1!=0 && b2==0

??當(dāng)l_1l_2都傾斜于x軸,且斜率不同時(shí)踏烙,l_1l_2相交:

    b1!=0 && b2!=0 && a1/b1!=a2/b2

3师骗、計(jì)算交點(diǎn)

\begin{cases} a_1x+b_1y+c_1=0 \\ a_2x+b_2y+c_2=0 \end{cases} \Rightarrow \begin{cases} x= \frac{b_1c_2-b_2c_1}{a_1b_2-a_2b_1} \\ y=\frac{a_1c_2-a_2c_1}{a_2b_1-a_1b_2} \end{cases}

4、計(jì)算夾角

??交點(diǎn)p_0(x_0,y_0)讨惩、l_1的終點(diǎn)p_2(x_2,y_2)l_2的終點(diǎn)p_4(x_4,y_4)組成一個(gè)三角形辟癌,則其各邊邊長為:

a = \sqrt{(x_4-x_2)^2+(y_4-y_2)^2}

b = \sqrt{(x_4-x_0)^2+(y_4-y_0)^2}

c = \sqrt{(x_2-x_0)^2+(y_2-y_0)^2}

??由余弦定理得:
a^2=b^2+c^2-2bc \cos \theta \Rightarrow \cos \theta = \frac{b^2+c^2-a^2}{2bc} \Rightarrow \theta = \arccos (\frac{b^2+c^2-a^2}{2bc})

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

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

Vec4d g_line1(200, 300, 600, 300), g_line2(400, 500, 400, 100); // 垂直的兩條線

// 判斷兩條線是否相交荐捻,若相交則求出交點(diǎn)和夾角
Vec4d lines_intersection(const Vec4d l1, const Vec4d l2)
{
    double x1 = l1[0], y1 = l1[1], x2 = l1[2], y2 = l1[3];
    double a1 = -(y2 - y1), b1 = x2 - x1, c1 = (y2 - y1) * x1 - (x2 - x1) * y1; // 一般式:a1x+b1y1+c1=0
    double x3 = l2[0], y3 = l2[1], x4 = l2[2], y4 = l2[3];
    double a2 = -(y4 - y3), b2 = x4 - x3, c2 = (y4 - y3) * x3 - (x4 - x3) * y3; // 一般式:a2x+b2y1+c2=0
    bool r = false;                                                             // 判斷結(jié)果
    double x0 = 0, y0 = 0;                                                      // 交點(diǎn)
    double angle = 0;                                                           // 夾角
    // 判斷相交
    if (b1 == 0 && b2 != 0) // l1垂直于x軸黍少,l2傾斜于x軸
        r = true;
    else if (b1 != 0 && b2 == 0) // l1傾斜于x軸,l2垂直于x軸
        r = true;
    else if (b1 != 0 && b2 != 0 && a1 / b1 != a2 / b2)
        r = true;
    if (r)
    {
        //計(jì)算交點(diǎn)
        x0 = (b1 * c2 - b2 * c1) / (a1 * b2 - a2 * b1);
        y0 = (a1 * c2 - a2 * c1) / (a2 * b1 - a1 * b2);
        // 計(jì)算夾角
        double a = sqrt(pow(x4 - x2, 2) + pow(y4 - y2, 2));
        double b = sqrt(pow(x4 - x0, 2) + pow(y4 - y0, 2));
        double c = sqrt(pow(x2 - x0, 2) + pow(y2 - y0, 2));
        angle = acos((b * b + c * c - a * a) / (2 * b * c)) * 180 / CV_PI;
    }
    return Vec4d(r, x0, y0, angle);
}

// 畫夾角
void draw_angle(Mat img, const Point2d p0, const Point2d p1, const Point2d p2, const double radius, const Scalar color, const int thickness)
{
    // 計(jì)算直線的角度
    double angle1 = atan2(-(p1.y - p0.y), (p1.x - p0.x)) * 180 / CV_PI;
    double angle2 = atan2(-(p2.y - p0.y), (p2.x - p0.x)) * 180 / CV_PI;
    // 計(jì)算主軸的角度
    double angle = angle1 <= 0 ? -angle1 : 360 - angle1;
    // 計(jì)算圓弧的結(jié)束角度
    double end_angle = (angle2 < angle1) ? (angle1 - angle2) : (360 - (angle2 - angle1));
    if (end_angle > 180)
    {
        angle = angle2 <= 0 ? -angle2 : 360 - angle2;
        end_angle = 360 - end_angle;
    }
    // 畫圓弧
    ellipse(img, p0, Size(radius, radius), angle, 0, end_angle, 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相對(duì)于p0的正負(fù)
    int j = (p2.y > p1.y) ? 1 : -1;
    double a1 = abs(atan((p2.y - p1.y) / (p2.x - p1.x))); // 直線p1p2相對(duì)于x軸的角度要出,取正值
    double w1 = l1 * cos(a1), h1 = l1 * sin(a1);          // 用于計(jì)算p2相對(duì)于p0的寬高
    Point2d p0(p2.x - w1 * i, p2.y - h1 * j);
    double a2 = 90 * CV_PI / 180 - a1;           // 直線p3p4相對(duì)于x軸的角度
    double w2 = l2 * cos(a2), h2 = l2 * sin(a2); // 用于計(jì)算p3和p4相對(duì)于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 draw_dotted_line(Mat img, const Point2d p1, const Point2d p2, const Scalar color, const int thickness)
{
    double n = 15; // 小線段的長度
    double w = p2.x - p1.x, h = p2.y - p1.y;
    double l = sqrtl(w * w + h * h);
    // 矯正小線段長度鸳君,使小線段個(gè)數(shù)為奇數(shù)
    int m = l / n;
    m = m % 2 ? m : m + 1;
    n = l / m;

    circle(img, p1, 1, color, thickness); // 畫起點(diǎn)
    circle(img, p2, 1, color, thickness); // 畫終點(diǎn)
    // 畫中間的小線段
    if (p1.y == p2.y) //水平線:y = m
    {
        double x1 = min(p1.x, p2.x);
        double x2 = max(p1.x, p2.x);
        for (double x = x1, n1 = 2 * n; x < x2; x = x + n1)
            line(img, Point2d(x, p1.y), Point2d(x + n, p1.y), color, thickness);
    }
    else if (p1.x == p2.x) //垂直線, x = m
    {
        double y1 = min(p1.y, p2.y);
        double y2 = max(p1.y, p2.y);
        for (double y = y1, n1 = 2 * n; y < y2; y = y + n1)
            line(img, Point2d(p1.x, y), Point2d(p1.x, y + n), color, thickness);
    }
    else
    {
        // 直線方程的兩點(diǎn)式:(y-y1)/(y2-y1)=(x-x1)/(x2-x1) -> y = (y2-y1)*(x-x1)/(x2-x1)+y1
        double n1 = n * abs(w) / l;
        double k = h / w;
        double x1 = min(p1.x, p2.x);
        double x2 = max(p1.x, p2.x);
        for (double x = x1, n2 = 2 * n1; x < x2; x = x + n2)
        {
            Point p3 = Point2d(x, k * (x - p1.x) + p1.y);
            Point p4 = Point2d(x + n1, k * (x + n1 - p1.x) + p1.y);
            line(img, p3, p4, color, thickness);
        }
    }
}

// 畫延長線
void draw_extension_line(Mat img, const Vec4d l, Scalar color)
{
    double x1 = l[0], y1 = l[1], x2 = l[2], y2 = l[3];
    double a = -(y2 - y1), b = x2 - x1, c = (y2 - y1) * x1 - (x2 - x1) * y1;
    Point2d p1(0, 0), p2(0, 0);
    if (b != 0)
    {
        p1 = Point2d(0, -c / b);
        p2 = Point2d(img.cols, ((-a * img.cols - c) / b));
    }
    else
    {
        p1 = Point2d(-c / a, 0);
        p2 = Point2d(-c / a, img.rows);
    }
    draw_dotted_line(img, p1, p2, color, 1);
}

// 畫圖
void draw(Mat img, const Vec4d l1, const Vec4d l2)
{
    Vec4d v = lines_intersection(l1, l2); // 判斷是否相交,并計(jì)算交點(diǎn)和夾角

    line(img, Point2d(l1[0], l1[1]), Point2d(l1[2], l1[3]), Scalar(255, 0, 0), 2);               // 畫藍(lán)線
    draw_arrow(img, Point2d(l1[0], l1[1]), Point2d(l1[2], l1[3]), 20, 20, Scalar(255, 0, 0), 2); // 畫箭頭

    line(img, Point2d(l2[0], l2[1]), Point2d(l2[2], l2[3]), Scalar(0, 255, 0), 2);               // 畫綠線
    draw_arrow(img, Point2d(l2[0], l2[1]), Point2d(l2[2], l2[3]), 20, 20, Scalar(0, 255, 0), 2); // 畫箭頭

    draw_extension_line(img, l1, Scalar(255, 0, 0)); // 畫延長線
    draw_extension_line(img, l2, Scalar(0, 255, 0)); // 畫延長線

    draw_angle(img, Point2d(v[1], v[2]), Point2d(l1[2], l1[3]), Point2d(l2[2], l2[3]), 15, Scalar(0, 0, 255), 1); // 畫夾角

    if (v[0])
    {
        string s = "(" + to_string(v[1]) + ", " + to_string(v[2]) + ") " + to_string(v[3]);
        putText(img, s, Point2d(10, 25), cv::FONT_HERSHEY_COMPLEX, 0.5, Scalar(0, 255, 0));
        circle(img, Point2d(v[1], v[2]), 2, Scalar(0, 0, 255), 2); // 畫交點(diǎn)
    }
    else
    {
        putText(img, "no", Point2d(10, 25), cv::FONT_HERSHEY_COMPLEX, 0.5, Scalar(0, 0, 255));
    }
}

// 確定鼠標(biāo)左鍵點(diǎn)擊在兩條直線的那個(gè)點(diǎn)上
Vec6d define_area(const Vec4d l1, const Vec4d l2, const Point2d p)
{
    Vec6d v(-1, 0, 0, 0, 0, 0);
    double w = 20, h = 20;
    double x1 = l1[0], y1 = l1[1], x2 = l1[2], y2 = l1[3];
    double x3 = l2[0], y3 = l2[1], x4 = l2[2], y4 = l2[3];
    Rect r0(x1 - w, y1 - h, 2 * w, 2 * h);    // l1的起點(diǎn)
    Point2d p1((x2 + x1) / 2, (y1 + y2) / 2); // l1的中間點(diǎn)
    Rect r1(p1.x - w, p1.y - h, 2 * w, 2 * h);
    Rect r2(x2 - w, y2 - h, 2 * w, 2 * h); // l1的終點(diǎn)

    Rect r3(x3 - w, y3 - h, 2 * w, 2 * h);    // l2的起點(diǎn)
    Point2d p2((x3 + x4) / 2, (y3 + y4) / 2); // l2的中間點(diǎn)
    Rect r4(p2.x - w, p2.y - h, 2 * w, 2 * h);
    Rect r5(x4 - w, y4 - h, 2 * w, 2 * h); // l2的終點(diǎn)

    if (r0.contains(p)) // 判斷點(diǎn)是否在矩形中
    {
        v = Vec6d(0, x1, y1, 0, 0, 0);
    }
    else if (r1.contains(p))
    {
        v = Vec6d(1, x1, y1, x2, y2, 0);
    }
    else if (r2.contains(p))
    {
        v = Vec6d(2, x2, y2, 0, 0, 0);
    }
    else if (r3.contains(p))
    {
        v = Vec6d(3, x3, y3, 0, 0, 0);
    }
    else if (r4.contains(p))
    {
        v = Vec6d(4, x3, y3, x4, y4, 0);
    }
    else if (r5.contains(p))
    {
        v = Vec6d(5, x4, y4, 0, 0, 0);
    }
    return v;
}

// 根據(jù)鼠標(biāo)移動(dòng)相應(yīng)的修改直線的起點(diǎn)和終點(diǎn)
void modify_line(Vec4d &l1, Vec4d &l2, const Vec6d area, const double w, const double h)
{
    if (area[0] == 0)
    {
        l1[0] = area[1] + w;
        l1[1] = area[2] + h;
    }
    else if (area[0] == 1)
    {
        l1[0] = area[1] + w;
        l1[1] = area[2] + h;
        l1[2] = area[3] + w;
        l1[3] = area[4] + h;
    }
    else if (area[0] == 2)
    {
        l1[2] = area[1] + w;
        l1[3] = area[2] + h;
    }
    else if (area[0] == 3)
    {
        l2[0] = area[1] + w;
        l2[1] = area[2] + h;
    }
    else if (area[0] == 4)
    {
        l2[0] = area[1] + w;
        l2[1] = area[2] + h;
        l2[2] = area[3] + w;
        l2[3] = area[4] + h;
    }
    else if (area[0] == 5)
    {
        l2[2] = area[1] + w;
        l2[3] = area[2] + h;
    }
}

// 鼠標(biāo)回調(diào)函數(shù)
void mouse_callback(int event, int x, int y, int flags, void *param)
{
    static Point2d p1(0, 0), p2(0, 0);
    static Vec6d area(-1, 0, 0, 0, 0, 0);
    switch (event)
    {
    case cv::EVENT_LBUTTONDOWN: // 鼠標(biāo)左鍵點(diǎn)擊
        p1 = Point2d(x, y);
        area = define_area(g_line1, g_line2, p1); // 確定鼠標(biāo)所要移動(dòng)的區(qū)域
        break;
    case cv::EVENT_MOUSEMOVE: // 鼠標(biāo)移動(dòng)
        if (area[0] > -1)     // 移動(dòng)直線
        {
            p2 = Point2d(x, y);
            double w = p2.x - p1.x, h = p2.y - p1.y;
            modify_line(g_line1, g_line2, area, w, h); // 根據(jù)鼠標(biāo)移動(dòng)相應(yīng)的修改直線的起點(diǎn)和終點(diǎn)
        }
        break;
    case cv::EVENT_LBUTTONUP: // 鼠標(biāo)左鍵釋放
        p1 = Point2d(0, 0);
        p2 = Point2d(0, 0);
        area = Vec6d(-1, 0, 0, 0, 0, 0);
        break;
    default:
        break;
    }
}

// 主函數(shù)
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); // 調(diào)用鼠標(biāo)回調(diào)函數(shù)
    while (true)
    {
        Mat img = image_original.clone(); // 拷貝空白圖片患蹂,方便重復(fù)畫圖
        draw(img, g_line1, g_line2);      // 畫圖
        imshow(window_name, img);
        if (waitKey(3) > 0) // 退出循環(huán)
            break;
    }
    return 0;
}

操作方法:
??鼠標(biāo)點(diǎn)擊直線起點(diǎn)或終點(diǎn)并按住移動(dòng)或颊,可改變直線砸紊。鼠標(biāo)點(diǎn)擊直線的中間點(diǎn)并按住移動(dòng),可平移直線囱挑。在鍵盤按任意的鍵可退出程序醉顽。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市平挑,隨后出現(xiàn)的幾起案子游添,更是在濱河造成了極大的恐慌,老刑警劉巖通熄,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唆涝,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡唇辨,警方通過查閱死者的電腦和手機(jī)廊酣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赏枚,“玉大人亡驰,你說我怎么就攤上這事《龇” “怎么了凡辱?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長栗恩。 經(jīng)常有香客問我煞茫,道長,這世上最難降的妖魔是什么摄凡? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任续徽,我火速辦了婚禮,結(jié)果婚禮上亲澡,老公的妹妹穿的比我還像新娘钦扭。我一直安慰自己,他們只是感情好床绪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布客情。 她就那樣靜靜地躺著,像睡著了一般癞己。 火紅的嫁衣襯著肌膚如雪膀斋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天痹雅,我揣著相機(jī)與錄音仰担,去河邊找鬼。 笑死绩社,一個(gè)胖子當(dāng)著我的面吹牛摔蓝,可吹牛的內(nèi)容都是我干的赂苗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼贮尉,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼拌滋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起猜谚,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤败砂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后魏铅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吠卷,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年沦零,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祭隔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡路操,死狀恐怖疾渴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情屯仗,我是刑警寧澤搞坝,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站魁袜,受9級(jí)特大地震影響桩撮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜峰弹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一店量、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鞠呈,春花似錦融师、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至窘茁,卻和暖如春怀伦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背山林。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國打工房待, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓吴攒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親砂蔽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子洼怔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354