2010-08-15 72 views
14

Cho điểm ABC, làm thế nào tôi có thể tìm thấy góc ABC? Tôi đang thực hiện một công cụ tính phí cho một ứng dụng vẽ vector và để giảm thiểu số điểm tạo ra, tôi sẽ không thêm điểm trừ khi góc của vị trí chuột và 2 điểm cuối lớn hơn một ngưỡng nhất định. Cảm ơnGóc giữa 3 điểm?

những gì tôi có:

int CGlEngineFunctions::GetAngleABC(POINTFLOAT a, POINTFLOAT b, POINTFLOAT c) 
{ 
    POINTFLOAT ab; 
    POINTFLOAT ac; 

    ab.x = b.x - a.x; 
    ab.y = b.y - a.y; 

    ac.x = b.x - c.x; 
    ac.y = b.y - c.y; 

    float dotabac = (ab.x * ab.y + ac.x * ac.y); 
    float lenab = sqrt(ab.x * ab.x + ab.y * ab.y); 
    float lenac = sqrt(ac.x * ac.x + ac.y * ac.y); 

    float dacos = dotabac/lenab/lenac; 

    float rslt = acos(dacos); 
    float rs = (rslt * 180)/3.141592; 
    RoundNumber(rs); 
    return (int)rs; 


} 
+2

tôi đang làm khá tốt, tôi có một algorthm cho nó nhưng nó đã không làm các trick. – jmasterx

+5

@abelenky: và điều đó làm cho câu hỏi "không rõ ràng hoặc không hữu ích", chính xác như thế nào? Bạn có thể đã hiểu lầm mục đích của đại diện. Nó không ở đó để cho phép bạn trừng phạt mọi người vì cố gắng làm điều gì đó mới mẻ đối với họ. – jalf

Trả lời

25

gợi ý đầu tiên về phương pháp của bạn:

Những gì bạn gọi ac thực sự là cb. Nhưng đó là ok, đây là những gì thực sự cần thiết. Tiếp theo,

float dotabac = (ab.x * ab.y + ac.x * ac.y); 

Đây là sai lầm đầu tiên của bạn. Các thực chấm sản phẩm của hai vectơ là:

float dotabac = (ab.x * ac.x + ab.y * ac.y); 

Bây giờ,

float rslt = acos(dacos); 

Ở đây bạn nên lưu ý rằng do một số tổn thất chính xác trong việc tính toán đó là về mặt lý thuyết có thể là dacos sẽ trở nên lớn hơn 1 (hoặc nhỏ hơn -1). Do đó - bạn nên kiểm tra điều này một cách rõ ràng.

Thêm ghi chú hiệu suất: bạn gọi hàm nặng sqrt hai lần để tính chiều dài của hai vectơ. Sau đó, bạn chia sản phẩm dấu chấm theo các độ dài đó. Thay vào đó, bạn có thể gọi sqrt trên phép nhân của bình phương chiều dài của cả hai vectơ.

Và cuối cùng, bạn nên lưu ý rằng kết quả của bạn chính xác đến số sign. Nghĩa là, phương pháp của bạn sẽ không phân biệt được 20 ° và -20 °, vì cosin của cả hai đều giống nhau. Phương pháp của bạn sẽ mang lại góc tương tự cho ABC và CBA.

Một phương pháp chính xác cho việc tính toán góc là như "oslvbo" gợi ý:

float angba = atan2(ab.y, ab.x); 
float angbc = atan2(cb.y, cb.x); 
float rslt = angba - angbc; 
float rs = (rslt * 180)/3.141592; 

(Tôi vừa mới thay thế atan bởi atan2).

Đó là phương pháp đơn giản nhất, luôn mang lại kết quả chính xác. Hạn chế của phương pháp này là bạn thực sự gọi hàm lượng giác nặng atan2 hai lần.

Tôi đề xuất phương pháp sau. Đó là một chút phức tạp hơn (đòi hỏi một số kỹ năng lượng giác để hiểu), tuy nhiên nó vượt trội so với quan điểm thực hiện. Nó chỉ gọi một lần hàm lượng giác atan2. Và không có tính toán căn bậc hai.

int CGlEngineFunctions::GetAngleABC(POINTFLOAT a, POINTFLOAT b, POINTFLOAT c) 
{ 
    POINTFLOAT ab = { b.x - a.x, b.y - a.y }; 
    POINTFLOAT cb = { b.x - c.x, b.y - c.y }; 

    // dot product 
    float dot = (ab.x * cb.x + ab.y * cb.y); 

    // length square of both vectors 
    float abSqr = ab.x * ab.x + ab.y * ab.y; 
    float cbSqr = cb.x * cb.x + cb.y * cb.y; 

    // square of cosine of the needed angle  
    float cosSqr = dot * dot/abSqr/cbSqr; 

    // this is a known trigonometric equality: 
    // cos(alpha * 2) = [ cos(alpha) ]^2 * 2 - 1 
    float cos2 = 2 * cosSqr - 1; 

    // Here's the only invocation of the heavy function. 
    // It's a good idea to check explicitly if cos2 is within [-1 .. 1] range 

    const float pi = 3.141592f; 

    float alpha2 = 
     (cos2 <= -1) ? pi : 
     (cos2 >= 1) ? 0 : 
     acosf(cos2); 

    float rslt = alpha2/2; 

    float rs = rslt * 180./pi; 


    // Now revolve the ambiguities. 
    // 1. If dot product of two vectors is negative - the angle is definitely 
    // above 90 degrees. Still we have no information regarding the sign of the angle. 

    // NOTE: This ambiguity is the consequence of our method: calculating the cosine 
    // of the double angle. This allows us to get rid of calling sqrt. 

    if (dot < 0) 
     rs = 180 - rs; 

    // 2. Determine the sign. For this we'll use the Determinant of two vectors. 

    float det = (ab.x * cb.y - ab.y * cb.y); 
    if (det < 0) 
     rs = -rs; 

    return (int) floor(rs + 0.5); 


} 

EDIT:

Gần đây tôi đã làm việc trên một chủ đề có liên quan. Và sau đó tôi đã nhận ra có một cách tốt hơn. Nó thực sự nhiều hơn hoặc ít hơn (phía sau hậu trường). Tuy nhiên nó là IMHO đơn giản hơn.

Ý tưởng là xoay cả hai vectơ sao cho giá trị đầu tiên được căn chỉnh theo hướng X dương (dương). Rõ ràng xoay cả hai vectơ không ảnh hưởng đến góc giữa chúng. OTOH sau một vòng quay như vậy chỉ cần tìm ra góc của vector thứ 2 liên quan đến trục X. Và đây chính là điều mà atan2 dành cho.

Rotation được thực hiện bằng cách nhân một vector của các ma trận sau:

  • rìu, ay
  • -ay, rìu

Khi có thể thấy rằng vector a nhân với một ma trận thực sự như vậy quay về phía trục X dương.

Lưu ý: Nói đúng ma trận ở trên không chỉ xoay, nó còn mở rộng quy mô. Nhưng điều này là ok trong trường hợp của chúng tôi, vì điều duy nhất quan trọng là hướng vector, không phải chiều dài của nó.

Đã xoay vector b trở thành:

  • a.x * b.x + a.y * b.y = một dot b
  • -a.y * b.x + a.x * b.y = một chéo b

Cuối cùng, câu trả lời có thể được diễn tả như:

int CGlEngineFunctions::GetAngleABC(POINTFLOAT a, POINTFLOAT b, POINTFLOAT c) 
{ 
    POINTFLOAT ab = { b.x - a.x, b.y - a.y }; 
    POINTFLOAT cb = { b.x - c.x, b.y - c.y }; 

    float dot = (ab.x * cb.x + ab.y * cb.y); // dot product 
    float cross = (ab.x * cb.y - ab.y * cb.x); // cross product 

    float alpha = atan2(cross, dot); 

    return (int) floor(alpha * 180./pi + 0.5); 
} 
+0

Giải pháp tuyệt vời! Tôi lo lắng về cách đối phó với vấn đề góc dấu hiệu cho đến khi tôi đọc câu trả lời của bạn –

+0

Hàm cuối cùng (trong đó có 'return (int) floor (alpha * 180./pi + 0.5);') là tốt và đưa ra câu trả lời khác để abc và cba. Hoạt động tốt! –

+0

Chỉ có giải pháp không cho tôi vấn đề chính xác. Cảm ơn bạn! – Tiago

1

Tắt chủ đề? Nhưng bạn có thể làm điều đó với luật cosin:

Tìm khoảng cách giữa A và B (gọi đây là x) và khoảng cách giữa B và C (gọi y) và khoảng cách giữa A và C (gọi z).

Sau đó, bạn biết rằng z^2 = x^2 + y^2-2 * x y cos (ANGLE BẠN MUỐN)

do đó, góc đó là cos^-1 ((z^2 -x^2-y^2)/(2xy)) = ANGLE

+0

Bạn bị mất âm trong phần. Nó phải là 'x^2 + y^2 - z^2' như trong câu trả lời của tôi. –

4

β = ArccOS ((a^2 + c^2 - b^2)/2AC)

nơi một là điều ngược lại bên góc α, b là góc đối diện β, và c là góc đối diện γ. Vì vậy, β là những gì bạn gọi là góc ABC.

+1

Làm thế nào để bạn vuông một điểm? – jmasterx

+0

@Milo: bạn không - anh ấy đang sử dụng 'a',' b' và 'c' làm khoảng cách giữa các cặp điểm. –

+0

oh ok thanks :) – jmasterx

1
float angba = atan((a.y - b.y)/(a.x - b.x)); 
float angbc = atan((c.y - b.y)/(c.x - b.y)); 
float rslt = angba - angbc; 
float rs = (rslt * 180)/3.141592; 
+2

Thay vì sử dụng atan (dy/dx), sử dụng tốt hơn atan2 (dy, dx) – valdo

+1

@ valdo: Đúng vậy. Cảm ơn bạn đã nhắc nhở. – oslvbo

3

Tiếp cận với arccos là nguy hiểm, bởi vì chúng ta có nguy cơ để có nó là lập luận bằng, nói, 1,0000001 và kết thúc với EDOMAIN lỗi. Ngay cả cách tiếp cận atan cũng nguy hiểm, vì nó liên quan đến các bộ phận, điều này có thể dẫn đến sự phân chia bằng lỗi số không. Sử dụng tốt hơn atan2, chuyển các giá trị dxdy cho nó.

2

Đây là một cách nhanh chóng và chính xác của việc tính toán giá trị góc phải:

double AngleBetweenThreePoints(POINTFLOAT pointA, POINTFLOAT pointB, POINTFLOAT pointC) 
{ 
    float a = pointB.x - pointA.x; 
    float b = pointB.y - pointA.y; 
    float c = pointB.x - pointC.x; 
    float d = pointB.y - pointC.y; 

    float atanA = atan2(a, b); 
    float atanB = atan2(c, d); 

    return atanB - atanA; 
} 
+0

Điều này trả về các câu trả lời trong phạm vi '[-2 * pi ... + 2 * pi]' (2 vòng quay). – chux

0

Dưới đây là cách OpenCV để lấy góc giữa 3 điểm (A, B, C) với B làm đỉnh:

int getAngleABC(cv::Point2d a, cv::Point2d b, cv::Point2d c) 
{ 
    cv::Point2d ab = { b.x - a.x, b.y - a.y }; 
    cv::Point2d cb = { b.x - c.x, b.y - c.y }; 

    float dot = (ab.x * cb.x + ab.y * cb.y); // dot product 
    float cross = (ab.x * cb.y - ab.y * cb.x); // cross product 

    float alpha = atan2(cross, dot); 

    return (int) floor(alpha * 180./M_PI + 0.5); 
} 

Dựa trên những giải pháp tuyệt vời bởi @valdo

Các vấn đề liên quan