检测图像中的十字形

时间:2012-03-24 18:35:28

标签: c image-processing opencv ffmpeg

我正在开发一个程序,用于检测探测设备的尖端并分析探测过程中的颜色变化。输入/输出机制或多或少地到位。我现在需要的是事物的实际肉体:检测提示。

在下图中,提示位于十字架的中心。我考虑过在某些阈值之后将BFS应用于图像,但后来卡住了,不知道如何继续。然后我在阅读之后转向OpenCV,它提供了图像中的特征检测。但是,我对这里使用的大量概念和技术感到不知所措,对于如何继续进行无能为力。

我是以正确的方式看待它吗?你能给我一些指示吗?

Colored Image 从短视频中提取的图像

Binary image with threshold set at 95 阈值设置为95的二进制版本

3 个答案:

答案 0 :(得分:10)

模板匹配方法

这是一个简单的matchTemplate解决方案,类似于Guy Sirton提到的方法。

只要您的目标没有太多缩放或旋转,模板匹配就会起作用。

这是我使用的模板: enter image description here

这是我用来检测几个无障碍十字架的代码:

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

using namespace cv;
using namespace std;

int main(int argc, char* argv[])
{
    string inputName = "crosses.jpg";
    string outputName = "crosses_detect.png";
    Mat img   = imread( inputName, 1);
    Mat templ = imread( "crosses-template.jpg", 1);

    int resultCols =  img.cols - templ.cols + 1;
    int resultRows = img.rows - templ.rows + 1;
    Mat result( resultCols, resultRows, CV_32FC1 );

    matchTemplate(img, templ, result, CV_TM_CCOEFF);
    normalize(result, result, 0, 255.0, NORM_MINMAX, CV_8UC1, Mat());

    Mat resultMask;
    threshold(result, resultMask, 180.0, 255.0, THRESH_BINARY);

    Mat temp = resultMask.clone();
    vector< vector<Point> > contours;
    findContours(temp, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(templ.cols / 2, templ.rows / 2));

    vector< vector<Point> >::iterator i;
    for(i = contours.begin(); i != contours.end(); i++)
    {
        Moments m = moments(*i, false);
        Point2f centroid(m.m10 / m.m00, m.m01 / m.m00);
        circle(img, centroid, 3, Scalar(0, 255, 0), 3);
    }

    imshow("img", img);
    imshow("results", result);
    imshow("resultMask", resultMask);

    imwrite(outputName, img);

    waitKey(0);

    return 0;
}

这导致该检测图像:
enter image description here

此代码基本上设置了一个阈值,用于将交叉峰与图像的其余部分分开,然后检测所有这些轮廓。最后,它计算每个轮廓的质心以检测十字架的中心。

形状检测替代

以下是使用三角形检测的替代方法。它似乎不如matchTemplate方法准确,但可能是您可以使用的替代方案。

使用findContours我们检测到图像中的所有三角形,结果如下:

enter image description here

然后我注意到所有三角形顶点聚集在十字中心附近,所以这些聚类用于质心下面显示的十字中心点:

enter image description here

最后,这是我用来执行此操作的代码:

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

using namespace cv;
using namespace std;

vector<Point> getAllTriangleVertices(Mat& img, const vector< vector<Point> >& contours);
double euclideanDist(Point a, Point b);

vector< vector<Point> > groupPointsWithinRadius(vector<Point>& points, double radius);
void printPointVector(const vector<Point>& points);
Point computeClusterAverage(const vector<Point>& cluster);

int main(int argc, char* argv[])
{
    Mat img   = imread("crosses.jpg", 1);
    double resizeFactor = 0.5;
    resize(img, img, Size(0, 0), resizeFactor, resizeFactor);

    Mat momentImg = img.clone();

    Mat gray;
    cvtColor(img, gray, CV_BGR2GRAY);

    adaptiveThreshold(gray, gray, 255.0, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 19, 15);
    imshow("threshold", gray);
    waitKey();

    vector< vector<Point> > contours;
    findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

    vector<Point> allTriangleVertices = getAllTriangleVertices(img, contours);

    imshow("img", img);
    imwrite("shape_detect.jpg", img);
    waitKey();

    printPointVector(allTriangleVertices);
    vector< vector<Point> > clusters = groupPointsWithinRadius(allTriangleVertices, 10.0*resizeFactor);
    cout << "Number of clusters: " << clusters.size() << endl;

    vector< vector<Point> >::iterator cluster;
    for(cluster = clusters.begin(); cluster != clusters.end(); ++cluster)
    {
        printPointVector(*cluster);

        Point clusterAvg = computeClusterAverage(*cluster);
        circle(momentImg, clusterAvg, 3, Scalar(0, 255, 0), CV_FILLED);
    }

    imshow("momentImg", momentImg);
    imwrite("centroids.jpg", momentImg);
    waitKey();

    return 0;
}

vector<Point> getAllTriangleVertices(Mat& img, const vector< vector<Point> >& contours)
{
    vector<Point> approxTriangle;
    vector<Point> allTriangleVertices;
    for(size_t i = 0; i < contours.size(); i++)
    {
        approxPolyDP(contours[i], approxTriangle, arcLength(Mat(contours[i]), true)*0.05, true);
        if(approxTriangle.size() == 3)
        {
            copy(approxTriangle.begin(), approxTriangle.end(), back_inserter(allTriangleVertices));
            drawContours(img, contours, i, Scalar(0, 255, 0), CV_FILLED);

            vector<Point>::iterator vertex;
            for(vertex = approxTriangle.begin(); vertex != approxTriangle.end(); ++vertex)
            {
                circle(img, *vertex, 3, Scalar(0, 0, 255), 1);
            }
        }
    }

    return allTriangleVertices;
}

double euclideanDist(Point a, Point b)
{
    Point c = a - b;
    return cv::sqrt(c.x*c.x + c.y*c.y);
}

vector< vector<Point> > groupPointsWithinRadius(vector<Point>& points, double radius)
{
    vector< vector<Point> > clusters;
    vector<Point>::iterator i;
    for(i = points.begin(); i != points.end();)
    {
        vector<Point> subCluster;
        subCluster.push_back(*i);

        vector<Point>::iterator j;
        for(j = points.begin(); j != points.end(); )
        {
            if(j != i &&  euclideanDist(*i, *j) < radius)
            {
                subCluster.push_back(*j);
                j = points.erase(j);
            }
            else
            {
                ++j;
            }
        }

        if(subCluster.size() > 1)
        {
            clusters.push_back(subCluster);
        }

        i = points.erase(i);
    }

    return clusters;
}

Point computeClusterAverage(const vector<Point>& cluster)
{
    Point2d sum;
    vector<Point>::const_iterator point;
    for(point = cluster.begin(); point != cluster.end(); ++point)
    {
        sum.x += point->x;
        sum.y += point->y;
    }

    sum.x /= (double)cluster.size();
    sum.y /= (double)cluster.size();

    return Point(cvRound(sum.x), cvRound(sum.y));
}

void printPointVector(const vector<Point>& points)
{
    vector<Point>::const_iterator point;
    for(point = points.begin(); point != points.end(); ++point)
    {
        cout << "(" << point->x << ", " << point->y << ")";
        if(point + 1 != points.end())
        {
            cout << ", ";
        }
    }
    cout << endl;
}

我在之前的实现中修复了一些错误,并稍微清理了一下代码。我还用各种调整大小的因素测试了它,它似乎表现得相当好。然而,在我达到四分之一刻度后,它开始难以正确检测三角形,因此这对于极小的十字架可能不适用。此外,moments函数中似乎存在一个错误,就像它返回的某些有效集群(-NaN,-NaN)位置一样。所以,我相信准确性会有所改善。它可能需要进行一些调整,但总的来说,我认为它应该是一个很好的起点。

我认为如果三角形周围的黑色边框更粗/更清晰,并且三角形本身的阴影更少,我的三角形检测效果会更好。

希望有所帮助!

答案 1 :(得分:3)

如果您在图像中有一个漂亮的周期性模式,那么如何简单地确定自相关呢?

假设您有目标图片:

enter image description here

和要同步的模板图像

enter image description here

您可以确定两者的自相关:

enter image description here enter image description here

两者都可以检测到ACF峰值,如本例所示 enter image description here

这些峰值可以使用匈牙利算法相互匹配。这里匹配用白线表示。

enter image description here

这为您提供了一组匹配的2D坐标,有望满足关系:

x = Ax'

其中A是具有缩放和旋转的转换矩阵。因此,解决它可以为您提供模板和目标图像之间的旋转和缩放。然后可以通过与部分校正/校正的图像和模板图像的正常互相关来确定平移。

答案 2 :(得分:2)

我过去在一个非常类似的应用程序中使用过名为HexSight(http://www.lmi3d.com/product/hexsight)的商业工具。我们对它的性能和准确性非常满意。

如果您正在寻找非常粗糙/基本的东西,您可以尝试在参考图像和您正在查看的图像之间调整cross correlation。另一种选择(HexSight使用的)是在尝试查找与参考图像的匹配之前从一些边缘检测算法开始。在任何一种情况下,一旦你有一个粗略的候选人,你可以跟进一些改进算法。鉴于您正在尝试查找某些特定目标类型,您可以应用一些启发式方法或利用自定义算法利用特定目标。

这是一个自定义解决方案的想法,假设十字架在网格上均匀间隔,并且图像不会太扭曲:

  • 对图像的所有行和列求和。这将在十字的行和列之间产生峰值(白色)。
  • 通过搜索角度来确定图像旋转,从而最大化这些峰的宽度和幅度。您可以在+/-最大预期角度之间进行二进制搜索。
  • 确定角度后,您现在可以使用峰的中心线来确定探头位置。实际上,你会在目标中心附近有另一个狭窄/较小的峰值。

在假设下,这应该是相当健壮和准确的,因为它使用来自整个图像的信息。它不会对任何本地问题过于敏感,如果目标(或少数)完全丢失或模糊,它甚至会起作用。

具有良好的光学和照明性能是从任何系统中获得高质量结果的先决条件。你所附的图像看起来并不那么好。