检测图像中的十字架

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

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

我正确地看着它吗? 你能给我一些指示吗?

彩色图像 从短video中提取的图像

阈值设置为95的二进制图像 阈值设置为95的二进制版本

模板匹配方法

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

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

这是我使用的模板: 在此处输入图像描述

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

#include  #include  #include  #include  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 > contours; findContours(temp, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(templ.cols / 2, templ.rows / 2)); vector< vector >::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; } 

这导致了这个检测图像:
在此处输入图像描述

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

形状检测方案

这是使用三角形检测的替代方法。 它看起来不像matchTemplate那样准确,但可能是你可以使用的另一种选择。

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

在此处输入图像描述

然后我注意到所有三角形顶点聚集在交叉中心附近,因此这些聚类用于质心如下所示的交叉中心点:

在此处输入图像描述

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

 #include  #include  #include  #include  #include  using namespace cv; using namespace std; vector getAllTriangleVertices(Mat& img, const vector< vector >& contours); double euclideanDist(Point a, Point b); vector< vector > groupPointsWithinRadius(vector& points, double radius); void printPointVector(const vector& points); Point computeClusterAverage(const vector& 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 > contours; findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); vector allTriangleVertices = getAllTriangleVertices(img, contours); imshow("img", img); imwrite("shape_detect.jpg", img); waitKey(); printPointVector(allTriangleVertices); vector< vector > clusters = groupPointsWithinRadius(allTriangleVertices, 10.0*resizeFactor); cout << "Number of clusters: " << clusters.size() << endl; vector< vector >::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 getAllTriangleVertices(Mat& img, const vector< vector >& contours) { vector approxTriangle; vector 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::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(cx*cx + cy*cy); } vector< vector > groupPointsWithinRadius(vector& points, double radius) { vector< vector > clusters; vector::iterator i; for(i = points.begin(); i != points.end();) { vector subCluster; subCluster.push_back(*i); vector::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& cluster) { Point2d sum; vector::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& points) { vector::const_iterator point; for(point = points.begin(); point != points.end(); ++point) { cout << "(" << point->x << ", " << point->y << ")"; if(point + 1 != points.end()) { cout << ", "; } } cout << endl; } 

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

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

希望有所帮助!

如何简单地确定自相关,因为你的图像中有一个漂亮的周期性模式。

假设您有目标图像:

在此处输入图像描述

和要同步的模板图像

在此处输入图像描述

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

在此处输入图像描述在此处输入图像描述

在两者中,您都可以检测到ACF峰值,如本例所示 在此处输入图像描述

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

在此处输入图像描述

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

 x = Ax' 

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

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

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

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

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

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

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