如何使用OpenCV找到激光十字中心

3

通过open cv相机可以获取到一个红色的十字标记(如下图所示),我不知道计算十字中心坐标(x,y)的最佳方法是什么?我们可以假设激光是红色的。

enter image description here

可能我需要使用某种物体识别技术。但我需要计算它的中心,并且性能很重要。

有人可以帮忙吗?

我已经找到了如何通过在图片中搜索最红的像素来找到激光指针(红点坐标),但在这种情况下,中心并不总是最红的(整条线都是红色的,有时cv会认为整条线比中心更红)。

3个回答

5

使用goodFeaturesToTrack函数,以下是我是如何实现的:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;


int main(int argc, char* argv[])
{
    Mat laserCross = imread("laser_cross.png");

    vector<Mat> laserChannels;
    split(laserCross, laserChannels);

    vector<Point2f> corners;
    // only using the red channel since it contains the interesting bits...
    goodFeaturesToTrack(laserChannels[2], corners, 1, 0.01, 10, Mat(), 3, false, 0.04);

    circle(laserCross, corners[0], 3, Scalar(0, 255, 0), -1, 8, 0);

    imshow("laser red", laserChannels[2]);
    imshow("corner", laserCross);
    waitKey();

    return 0;
}

这将产生以下输出:
enter image description here 您还可以考虑使用cornerSubPix来提高答案准确性。
编辑:我对vasile的答案进行了实现,发现效果很好!这是我实现他描述的内容。为了分割,我决定使用Otsu方法进行自动阈值选择。只要激光十字与背景之间有很高的分离度,这种方法就会很好用,否则您可能需要切换到像Canny这样的边缘检测器。我确实必须处理一些垂直线(即0和180度)的角度歧义,但代码似乎运行正常(可能有更好的处理角度歧义的方法)。
无论如何,这是代码:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

Point2f computeIntersect(Vec2f line1, Vec2f line2);
vector<Point2f> lineToPointPair(Vec2f line);
bool acceptLinePair(Vec2f line1, Vec2f line2, float minTheta);

int main(int argc, char* argv[])
{
    Mat laserCross = imread("laser_cross.png");

    vector<Mat> laserChannels;
    split(laserCross, laserChannels);

    namedWindow("otsu", CV_WINDOW_NORMAL);
    namedWindow("intersect", CV_WINDOW_NORMAL);

    Mat otsu;
    threshold(laserChannels[2], otsu, 0.0, 255.0, THRESH_OTSU);
    imshow("otsu", otsu);

    vector<Vec2f> lines;
    HoughLines( otsu, lines, 1, CV_PI/180, 70, 0, 0 );

    // compute the intersection from the lines detected...
    int lineCount = 0;
    Point2f intersect(0, 0);
    for( size_t i = 0; i < lines.size(); i++ )
    {
        for(size_t j = 0; j < lines.size(); j++)
        {
            Vec2f line1 = lines[i];
            Vec2f line2 = lines[j];
            if(acceptLinePair(line1, line2, CV_PI / 4))
            {
                intersect += computeIntersect(line1, line2);
                lineCount++;
            }
        }

    }

    if(lineCount > 0)
    {
        intersect.x /= (float)lineCount; intersect.y /= (float)lineCount;
        Mat laserIntersect = laserCross.clone();
        circle(laserIntersect, intersect, 1, Scalar(0, 255, 0), 3);
        imshow("intersect", laserIntersect);
    }

    waitKey();

    return 0;
}

bool acceptLinePair(Vec2f line1, Vec2f line2, float minTheta)
{
    float theta1 = line1[1], theta2 = line2[1];

    if(theta1 < minTheta)
    {
        theta1 += CV_PI; // dealing with 0 and 180 ambiguities...
    }

    if(theta2 < minTheta)
    {
        theta2 += CV_PI; // dealing with 0 and 180 ambiguities...
    }

    return abs(theta1 - theta2) > minTheta;
}

// the long nasty wikipedia line-intersection equation...bleh...
Point2f computeIntersect(Vec2f line1, Vec2f line2)
{
    vector<Point2f> p1 = lineToPointPair(line1);
    vector<Point2f> p2 = lineToPointPair(line2);

    float denom = (p1[0].x - p1[1].x)*(p2[0].y - p2[1].y) - (p1[0].y - p1[1].y)*(p2[0].x - p2[1].x);
    Point2f intersect(((p1[0].x*p1[1].y - p1[0].y*p1[1].x)*(p2[0].x - p2[1].x) -
                       (p1[0].x - p1[1].x)*(p2[0].x*p2[1].y - p2[0].y*p2[1].x)) / denom,
                      ((p1[0].x*p1[1].y - p1[0].y*p1[1].x)*(p2[0].y - p2[1].y) -
                       (p1[0].y - p1[1].y)*(p2[0].x*p2[1].y - p2[0].y*p2[1].x)) / denom);

    return intersect;
}

vector<Point2f> lineToPointPair(Vec2f line)
{
    vector<Point2f> points;

    float r = line[0], t = line[1];
    double cos_t = cos(t), sin_t = sin(t);
    double x0 = r*cos_t, y0 = r*sin_t;
    double alpha = 1000;

    points.push_back(Point2f(x0 + alpha*(-sin_t), y0 + alpha*cos_t));
    points.push_back(Point2f(x0 - alpha*(-sin_t), y0 - alpha*cos_t));

    return points;
}

希望能对您有所帮助!

海报确实说过性能很重要 - goodFeaturesToTrack 是Harris角点检测,速度非常慢。 - Martin Beckett
1
对于这张图片,在一台Core 2 Duo 2.66 GHz的机器上大约需要740微秒。虽然是一张小图片,但足以跟上我见过的任何相机的速度 :) - mevatron
1
我在GIMP中生成了一个800x600版本,大约需要35毫秒。虽然不是很好,但仍接近相机实时。 - mevatron
1
不考虑速度,我认为角点检测并不是最好的方法,如果在线交叉处存在微小的间隙或噪声,它将失败。我认为使用霍夫变换并搜索两条斜率接近90度的直线会是更好的鲁棒解决方案。 - Martin Beckett
1
非常感谢代码片段,它非常有用。在黑暗的环境下一切都运作正常。我将使用它进行网络摄像头的距离计算。你们俩是很酷的家伙(mevarton和vasile),感谢你们的帮助! - Audrius Gailius
显示剩余3条评论

3
扫描图像中的一行,例如向下1/4的位置,寻找红色像素的中心。然后再在靠近底部的一行上重复此过程-例如向下3/4的位置。这给你垂直条上的两个点。
现在,在图像边缘附近重复两列-例如横跨1/4和3/4-这给你水平部分上的两个点。
一个简单的同时方程可以给出交叉点
如果这是一个视频序列,而且时间非常紧迫,您可以使用在先前帧中找到的点,并在该点周围搜索一个小窗口-假设十字架没有移动太多。
附:如果线条不直,或在帧之间随机移动到不同的角度,或者需要分数像素精度,则有更好的技术可用。

2

霍夫变换可以帮助您解决这个问题,即使在更具挑战性的情况下也足够好。

因此,您可以:

  • 进行高斯/中值滤波(可选)
  • 使用Canny算法或分割算法。我推荐您使用分割算法。它将给您更多的线条,下一步将需要更多时间,但精度将达到亚像素级别
  • 使用经典的霍夫变换算法进行直线检测。cv::HoughLines(); 它将返回由rho和theta描述的许多线条。(如果使用分割算法,则可能有数百条线条)

  • 对于那些不属于同一红线的每对线条(abs(theta1-theta2)>minTheta),计算交点。这里需要一些几何知识。

  • 通过x和y平均这些中心点。或者使用其他统计方法获得平均中心点。

以下是您可以开始使用的示例。确保将预处理器#if 0更改为#if 1,以便使用经典变换。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接