角度范围的表现
在阅读apollo::percetion 模块时发现,apollo在对点云取box时 使用了 cv::minAreaRect()寻找面积最小的外接矩形方法。 但是此方法return 出来的 angle 却不确定。
/apollo/modules/perception/base/point_cloud_util.cc
bool GetPointCloudMinareaBbox(const PointFCloud& pc, BoundingCube* box,
const int& min_num_points, const bool& verbose) {
if (pc.size() <= static_cast<size_t>(min_num_points)) {
return false;
}
std::vector<cv::Point2f> pts;
float min_z = std::numeric_limits<float>::max();
float max_z = -std::numeric_limits<float>::max();
for (size_t i = 0; i < pc.size(); ++i) {
pts.push_back(cv::Point2f(pc[i].x, pc[i].y));
min_z = std::min(min_z, pc[i].z);
max_z = std::max(max_z, pc[i].z);
}
// compute MinAreaRect
cv::RotatedRect mar = cv::minAreaRect(pts);
// adjust angle
/* Remarque: this only works on old version opencv
in version 4.5.1 : the return value range is (0,90]
旋转角是顺时针的,取点顺序也是顺时针的
先寻找 x 方向上值最小的点作为 起始点
如果 x 方向上值相同, 再选 y 方向上值最小的点
输出值的范围是 (0,90] 0 对应 负Y 方向
O ——> x
|
v
y
*/
if (mar.size.width < mar.size.height) {
mar.angle -= 90;
float tmp = mar.size.width;
mar.size.width = mar.size.height;
mar.size.height = tmp;
}
if (verbose) {
AINFO << "center = " << mar.center.x << " " << mar.center.y << std::endl;
AINFO << "size = " << mar.size.height << " " << mar.size.width << std::endl;
AINFO << "yaw = " << mar.angle << std::endl;
AINFO << "height = " << max_z - min_z << std::endl;
}
box->x = mar.center.x;
box->y = mar.center.y;
box->z = static_cast<float>((min_z + max_z) / 2.0);
box->length = mar.size.width;
box->width = mar.size.height;
box->height = max_z - min_z;
box->yaw = static_cast<float>((M_PI * (mar.angle + 180)) / 180);
return true;
}
网上大部分的教程,给出的都是老版本 Opencv的解释, 即返回的angle 在[-90,0) 区间内。
参考:https://theailearner.com/tag/angle-of-rotation-by-cv2-minarearect/
但是我测试发现在4.5.2 版本中 返回值其实 在 (0,90] 区间内 :
外接矩形四个角点的颜色由浅入深,红色直线为过起始点的X轴垂线。
angle = 67.8781
angle = 88.2194
angle = 1.01398
(输入摆正的矩形) angle = 90
由此推测:
-
新的算法改变了矩形起始点(P1)的选择。优先 x 方向上值最小的点,如果 x 方向上值相同, 再选 y 方向上值最小的点 (经验论,有待源码阅读确认)
-
确定P1后,后续点按照 clockwise 的顺序选取
-
以 Y轴负方向为0度,返回的角度是过<P1的X轴垂线>和 <P1P2的连线>clockwise的夹角
git Issue:: https://github.com/opencv/opencv/issues/19749
角度是如何计算的
/opencv/modules/imgproc/src/rotcalipers.cpp
cv::RotatedRect cv::minAreaRect( InputArray _points )
{
CV_INSTRUMENT_REGION();
Mat hull;
Point2f out[3];
RotatedRect box;
// 找凸包
convexHull(_points, hull, false, true);
// depth()返回的是矩阵元素类型,和维度无关
// 转到32bits float
if( hull.depth() != CV_32F )
{
Mat temp;
hull.convertTo(temp, CV_32F);
hull = temp;
}
// 获取凸包点数
int n = hull.checkVector(2);
const Point2f* hpoints = hull.ptr<Point2f>();
if( n > 2 )
{
// In case CV_CALIPERS_MINAREARECT
// ((CvPoint2D32f*)out)[0] - corner
//