圆形物体旋转角度检测

Circular objects rotate angle detection

我正在尝试检测两个圆形物体之间的角度差,如下图 2 所示。

我正在考虑以某个小角度旋转其中一个图像。每次旋转一个图像时,将计算旋转图像与另一个图像之间的 SSIM。 SSIM最大的角度就是角度差。

但是,找到极端从来都不是一件容易的事。所以我的问题是:在这种情况下是否可以使用其他算法 (opencv)?

图像 #1

图像 #2

编辑:

谢谢@Micka,我只是按照他建议的方式做,并像@Yves Daoust 所说的那样删除黑色区域以缩短处理时间。这是我的最终结果:

原图 旋转 + 移动图像

这是一种方法:

  1. 检测圆(例如,我假设圆位于图像中心,半径为图像宽度的 50%)
  2. 按极坐标展开圆形图像
  3. 确保第二张图片在第一张图片中完全可见,没有“圆端溢出”
  4. 简单的模板匹配

以下代码的结果:

min: 9.54111e+07
pos: [0, 2470]
angle-right: 317.571
angle-left: -42.4286

我认为这在一般情况下应该工作得很好。

int main()
{
    // load images
    cv::Mat image1 = cv::imread("C:/data/Whosebug/circleAngle/circleAngle1.jpg");
    cv::Mat image2 = cv::imread("C:/data/Whosebug/circleAngle/circleAngle2.jpg");

    // generate circle information. Here I assume image center and image is filled by the circles.
    // use houghCircles or a RANSAC based circle detection instead, if necessary
    cv::Point2f center1 = cv::Point2f(image1.cols/2.0f, image1.rows/2.0f);
    cv::Point2f center2 = cv::Point2f(image2.cols / 2.0f, image2.rows / 2.0f);
    float radius1 = image1.cols / 2.0f;
    float radius2 = image2.cols / 2.0f;

    cv::Mat unrolled1, unrolled2;
    // define a size for the unrolling. Best might be to choose the arc-length of the circle. The smaller you choose this, the less resolution is available (the more pixel information of the circle is lost during warping)
    cv::Size unrolledSize(radius1, image1.cols * 2);

    // unroll the circles by warpPolar
    cv::warpPolar(image1, unrolled1, unrolledSize, center1, radius1, cv::WARP_POLAR_LINEAR);
    cv::warpPolar(image2, unrolled2, unrolledSize, center2, radius2, cv::WARP_POLAR_LINEAR);

    // double the first image (720° of the circle), so that the second image is fully included without a "circle end overflow"
    cv::Mat doubleImg1;
    cv::vconcat(unrolled1, unrolled1, doubleImg1);

    // the height of the unrolled image is exactly 360° of the circle
    double degreesPerPixel = 360.0 / unrolledSize.height;

    // template matching. Maybe correlation could be the better matching metric
    cv::Mat matchingResult;
    cv::matchTemplate(doubleImg1, unrolled2, matchingResult, cv::TemplateMatchModes::TM_SQDIFF);

    double minVal; double maxVal; cv::Point minLoc; cv::Point maxLoc;
    cv::Point matchLoc;
    cv::minMaxLoc(matchingResult, &minVal, &maxVal, &minLoc, &maxLoc, cv::Mat());

    std::cout << "min: " << minVal << std::endl;
    std::cout << "pos: " << minLoc << std::endl;

    // angles in clockwise direction:
    std::cout << "angle-right: " << minLoc.y * degreesPerPixel << std::endl;
    std::cout << "angle-left: " << minLoc.y * degreesPerPixel -360.0 << std::endl;
    double foundAngle = minLoc.y * degreesPerPixel;
    
    // visualizations:
    // display the matched position
    cv::Rect pos = cv::Rect(minLoc, cv::Size(unrolled2.cols, unrolled2.rows));
    cv::rectangle(doubleImg1, pos, cv::Scalar(0, 255, 0), 4);

    // resize because the images are too big
    cv::Mat resizedResult;
    cv::resize(doubleImg1, resizedResult, cv::Size(), 0.2, 0.2);
    
    cv::resize(unrolled1, unrolled1, cv::Size(), 0.2, 0.2);
    cv::resize(unrolled2, unrolled2, cv::Size(), 0.2, 0.2);

    double startAngleUpright = 0;
    cv::ellipse(image1, center1, cv::Size(100, 100), 0, startAngleUpright, startAngleUpright + foundAngle, cv::Scalar::all(255), -1, 0);

    cv::resize(image1, image1, cv::Size(), 0.5, 0.5);
    cv::imshow("image1", image1);

    cv::imshow("unrolled1", unrolled1);
    cv::imshow("unrolled2", unrolled2);

    cv::imshow("resized", resizedResult);

    cv::waitKey(0);

    
}

这是中间图像和结果的样子:

展开图像 1 / 展开图像 2 / 展开图像 1 (720°) / 展开图像 2 在展开图像 1 (720°) 中的最佳匹配:

这是相同的想法,但相关性是通过卷积 (FFT) 而不是 matchTemplate 完成的。如果数据很多,FFT 会更快。

加载输入:

im1 = cv.imread("circle1.jpg", cv.IMREAD_GRAYSCALE)
im2 = cv.imread("circle2.jpg", cv.IMREAD_GRAYSCALE)
height, width = im1.shape

极坐标变换(记录极坐标作为 reader 的练习),带有一些影响“分辨率”的任意参数:

maxradius = width // 2

stripwidth = maxradius
stripheight = int(maxradius * 2 * pi) # approximately square at the radius
#stripheight = 360

def polar(im):
    return cv.warpPolar(im, center=(width/2, height/2),
        dsize=(stripwidth, stripheight), maxRadius=maxradius,
        flags=cv.WARP_POLAR_LOG*0 + cv.INTER_LINEAR)

strip1 = polar(im1)
strip2 = polar(im2)

卷积:

f1 = np.fft.fft2(strip1[::-1, ::-1])
f2 = np.fft.fft2(strip2)
conv = np.fft.ifft2(f1 * f2)

最小最大定位:

conv = np.real(conv) # or np.abs, can't decide
(i,j) = np.unravel_index(conv.argmax(), conv.shape)
i,j = (i+1) % stripheight, (j+1) % stripwidth

那是什么角度:

print("degrees:", i / stripheight * 360)
# 42.401091405184175

https://gist.github.com/crackwitz/3da91f43324b0c53504d587a394d4c71