要了解哪些参考形状构成您的图像,您可以
- 定位所有形状中的中心点
- 知道点在哪里,找到正确的形状。
对于这个答案的范围,我使用这些已经预处理的图像。第一张图片是简单的阈值处理,第二张我使用thissn-p。
在预处理图像上找到中心点非常容易。您可以使用cv::connectedComponentsWithStats 检索所有黑色组件,然后删除太大的组件。您可以在下面的函数getCenterPoints 中找到代码。
然后你可以很容易地得到轮廓(稍后需要)这个图像和原始图像的简单组合:
现在我们可以找到点,但我们还需要一种方法来说明最终图像是由哪个形状构成的。
我们可以使用形状的几何形状为每个形状构建一个简单的描述符:我们在Mat 中保存4个值,表示中心到轮廓在垂直和水平方向上的距离:
这可以唯一标识您的所有参考形状。
然后我们对这个 4 元素向量进行归一化,使其成为尺度不变的。使用这个描述符可以让我们避免繁琐的“多尺度模板匹配”之类的东西,而且速度更快且可扩展。您可以在下面的函数computeShapeDescriptor 中找到此代码。
为了计算形状描述符,我们还需要形状中心的正确位置,这就是我们之前找到的斑点的质心。我们基本上再次使用cv::connectedComponentWithStats。请参阅下面的getCentroids。
现在我们知道如何找到点来定位所有形状,并且知道如何描述它们。要在图像中找到相应的参考形状,只需比较描述符。最相似的就是正确的!
完整代码供参考:
#include <opencv2\opencv.hpp>
#include <vector>
void computeShapeDescriptor(const cv::Mat1b shape_outline, cv::Point center, cv::Mat1d& desc)
{
desc = cv::Mat1d(1, 4, 0.0);
// Go up until I find a outline pixel
for (int i = center.y; i >= 0; --i) {
if (shape_outline(i, center.x) > 0) {
desc(0) = std::abs(i - center.y);
break;
}
}
// Go right until I find a outline pixel
for (int i = center.x; i < shape_outline.cols; ++i) {
if (shape_outline(center.y, i) > 0) {
desc(1) = std::abs(i - center.x);
break;
}
}
// Go down until I find a outline pixel
for (int i = center.y; i < shape_outline.rows; ++i) {
if (shape_outline(i, center.x) > 0) {
desc(2) = std::abs(i - center.y);
break;
}
}
// Go left until I find a outline pixel
for (int i = center.x; i >= 0; --i) {
if (shape_outline(center.y, i) > 0) {
desc(3) = std::abs(i - center.x);
break;
}
}
desc /= cv::norm(desc, cv::NORM_L1);
}
void getCenterPoints(const cv::Mat1b& src, cv::Mat1b& dst)
{
dst = cv::Mat1b(src.rows, src.cols, uchar(0));
cv::Mat1i labels;
cv::Mat1i stats;
cv::Mat1d centroids;
int n_labels = cv::connectedComponentsWithStats(~src, labels, stats, centroids);
for (int i = 1; i < n_labels; ++i) {
if (stats(i, cv::CC_STAT_AREA) < 100)
{
dst.setTo(255, labels == i);
}
}
}
void getCentroids(const cv::Mat1b& src, cv::Mat1d& centroids)
{
// Find the central pixel
cv::Mat1i labels;
cv::Mat1i stats;
cv::connectedComponentsWithStats(src, labels, stats, centroids);
// 'centroids' contains in each row x,y coordinates of the centroid
}
int main()
{
// Load the reference shapes
cv::Mat1b reference = cv::imread("path_to_reference_shapes", cv::IMREAD_GRAYSCALE);
// -------------------------
// Compute descriptor for each reference shape
// -------------------------
// Get the centers
cv::Mat1b reference_centers;
getCenterPoints(reference, reference_centers);
// Get the centroids
cv::Mat1d shape_centroids;
getCentroids(reference_centers, shape_centroids);
// Find the outline
cv::Mat1b reference_outline = ~(reference | reference_centers);
// Prepare output image
cv::Mat3b reference_output;
cv::cvtColor(reference, reference_output, cv::COLOR_GRAY2BGR);
// Compute the descriptor for each shape
std::vector<cv::Mat1f> shape_descriptors;
for (int i = 1; i < shape_centroids.rows; ++i)
{
cv::Point center;
center.x = std::round(shape_centroids(i, 0));
center.y = std::round(shape_centroids(i, 1));
cv::Mat1d desc;
computeShapeDescriptor(reference_outline, center, desc);
shape_descriptors.push_back(desc.clone());
// Draw the ID of the shape
cv::putText(reference_output, cv::String(std::to_string(i)), center, cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(0, 0, 255));
}
// -------------------------
// Find shapes in image
// -------------------------
cv::Mat1b img = cv::imread("path_to_image", cv::IMREAD_GRAYSCALE);
// Get the centers
cv::Mat1b img_centers;
getCenterPoints(img, img_centers);
// Get the centroids
cv::Mat1d img_centroids;
getCentroids(img_centers, img_centroids);
// Find the outline
cv::Mat1b img_outline = ~(img | img_centers);
// Prepare output image
cv::Mat3b img_output;
cv::cvtColor(img, img_output, cv::COLOR_GRAY2BGR);
// Compute the descriptor for each found shape, and assign to nearest descriptor among reference shapes
for (int i = 1; i < img_centroids.rows; ++i)
{
cv::Point center;
center.x = std::round(img_centroids(i, 0));
center.y = std::round(img_centroids(i, 1));
cv::Mat1d desc;
computeShapeDescriptor(img_outline, center, desc);
// Compute the distance with all reference descriptors
double minDist = 1e10;
int minIdx = 0;
for (size_t j = 0; j < shape_descriptors.size(); ++j)
{
// Actual distance computation
double dist = 0.0;
for (int c = 0; c < desc.cols; ++c) {
dist += std::abs(desc(c) - shape_descriptors[j](c));
}
if (minDist > dist) {
minDist = dist;
minIdx = j;
}
}
// Draw the ID of the shape
cv::putText(img_output, cv::String(std::to_string(minIdx + 1)), center, cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(0, 0, 255, 255));
}
return 0;
}