一、calculateAngle
double calculate_angle_3FPoint(const SDL_FPoint& a, const SDL_FPoint& b, const SDL_FPoint& c)

calculate_angle_3FPoint(a, b, c) 计算的是以b为顶点的角∠ABC,即向量BA和BC之间的夹角。调用时需填对参数b,互换参数a和c不影响结果。。
check_raised_hands负责提取手臂的关键点,并计算左右手臂的弯曲角度。用的是两次calculate_angle_3FPoint,调用时需填对b,用左右肘(elbow)。
void check_raised_hands(const SDL_FPoint* landmarks, double& left_arm_angle, double& right_arm_angle)
{
// Left arm angle detection
const SDL_FPoint& l_shoulder = landmarks[11];
const SDL_FPoint& l_elbow = landmarks[13];
const SDL_FPoint& l_wrist = landmarks[15];
double left_arm_rad = calculate_angle_3FPoint(l_shoulder, l_elbow, l_wrist);
left_arm_angle = RAD2DEG(left_arm_rad);
// Right arm angle detection
const SDL_FPoint& r_shoulder = landmarks[12];
const SDL_FPoint& r_elbow = landmarks[14];
const SDL_FPoint& r_wrist = landmarks[16];
double right_arm_rad = calculate_angle_3FPoint(r_shoulder, r_elbow, r_wrist);
right_arm_angle = RAD2DEG(right_arm_rad);
}
二、imgcoor_calculate_angle_2p + angles::normalize_angle_positive

联合这两个函数,可计算二维向量AB(图像坐标系)与X轴正方向的夹角,并将其归一化到 [0, 2π) 范围。AB长度小于1e-10时,得到是0度。
参数(start_x、start_y)是向量AB中的起点A,参数(end_x、end_y)是终点B。不能互换,一旦互换,可看图2,值完全变了。
double imgcoor_calculate_angle_2p(double start_x, double start_y, double end_x, double end_y)
{
double deltax = end_x - start_x;
double deltay = end_y - start_y;
if (fabs(deltax) < 1e-10 && fabs(deltay) < 1e-10) {
// 若 x 和 y 同时为 0,atan2 的结果未定义,由库实现决定。例如 MSVC 通常返回 0。
return 0;
}
double theta = atan2(deltay, deltax);
// atan2 的返回值:>0 表示逆时针,<0 表示顺时针,这符合数学规则。
// 但 @start_x/start_y、@end_x/end_y 是图像坐标,其 Y 轴方向与数学坐标相反。
// 因此乘以 -1 来修正,使最终角度遵循数学惯例:
// >0 表示逆时针,<0 表示顺时针。
return -1 * theta;
}imgcoor_calculate_angle_2p得到[-π, π),这角度正负和数学课教的一样,即逆时针是正,顺时针是负。
normalize_angle_positive负责把范围归一化到[0, 2π) 。