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

我正在考虑将一个图像旋转一些小角度。每旋转一个图像,就会计算旋转后的图像和另一个图像之间的SSIM。具有最大SSIM的角度将是角度差。

但是,寻找极端值从来都不是一个简单的问题。所以我的问题是:在这种情况下,是否有其他算法(opencv)可以使用?

EDIT:

谢谢@Micka,我只是按照他的建议做了同样的事情,并像@Yves Daoust所说的那样去除黑色区域以提高处理时间。这是我的最终结果。

旋转+移位的图像
5 个评论
试试Hough圆形变换。一旦你确定了这些圆,你就可以用类似匹配过滤的方法将它们围绕共同中心旋转,以确定最佳的旋转。
这个图像对的结果是什么?这个例子中的角度差是42°吗?
@Micka 是的,结果必须是42°左右。
don't 一步一步地旋转图像。这很浪费。虽然圆是一个必要的起始步骤(或其他东西来掌握圆)。 but then 你需要一个极坐标变换。那么图片只在沿一个轴的平移上有差异。平移的估计可以通过卷积来处理。
如果你需要额外考虑比例问题,可以做一个对数极性转换。
python
opencv
DungNH
DungNH
发布于 2021-12-15
2 个回答
Micka
Micka
发布于 2021-12-22
已采纳
0 人赞同

这里有一个方法。

  • detect circles (for the example I assume circle is in the image center and radius is 50% of the image width)
  • unroll circle images by polar coordinates
  • make sure that the second image is fully visible in the first image, without a "circle end overflow"
  • simple template matching
  • Result for the following code:

    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/StackOverflow/circleAngle/circleAngle1.jpg");
        cv::Mat image2 = cv::imread("C:/data/StackOverflow/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°)/开卷1(720°)中开卷2的最佳匹配。

    是的,这就是我的做法(但没有这些黑色区域)。
    画出的绿色矩形太小了(用image2而不是unrolled2的大小),我将更新答案。
    通过首先检测内圆和外圆部分(或最小和最大半径)来去除黑色区域会更好,是的
    如果模板匹配不符合你的要求,你可以做一个卷积(同样的事情,但有所不同)。
    Christoph Rackwitz
    Christoph Rackwitz
    发布于 2021-12-22
    0 人赞同

    这里是同样的想法,但相关是用卷积(FFT)而不是 matchTemplate 完成的。如果有很多数据,FFT的速度会更快。

    负载输入。

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

    极坐标变换(对数极坐标,供读者练习),有一些影响 "分辨率 "的任意参数。

    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)
    

    Convolution:

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

    minmaxloc:

    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