如何从图像中提取不同的形状

时间:2017-11-04 07:25:17

标签: opencv image-processing

我是图像处理世界的新手,我有一个问题陈述,我需要先行解决它。

问题陈述:

我有一个由图案组成的图像。使用不同的单个形状创建此模式。下面是用于形成图案的图案和个别形状。

详细问题陈述

我有15个独特的形状(下图),我可以使用它绘制不同的图案(已经给出了一个例子)。我有超过400种模式。我想使用图像处理来找出用于生成特定图案的不同形状(及其在图案中的位置)。

所有独特的形状: enter image description here 更多模式图片enter image description here

我想要实现的目标

我想输入图案图像并找出用于形成图案的各个形状以及形状放置在图案中的位置?:

注意:由于问题变得太大,我没有包含所有单独的形状。

模式图片:

enter image description here 个人形状 enter image description here enter image description here enter image description here

3 个答案:

答案 0 :(得分:3)

要了解哪些参考形状构成您的图像,您可以

  1. 本地化所有形状中的中心点
  2. 知道点的位置,找到正确的形状。
  3. 对于本答案的范围,我使用这些已经预处理过的图像。第一张图片是简单的阈值,第二张图片使用了this代码段。

    enter image description here enter image description here

    在预处理的图像上找到中心点非常简单。您可以使用cv::connectedComponentsWithStats检索所有黑色组件,然后删除那些太大的组件。您可以在下面的函数getCenterPoints中找到代码。

    enter image description here

    然后,您可以通过此图像与原始图像的简单组合轻松获取轮廓(稍后需要):

    enter image description here

    现在我们能够找到这些点,但我们还需要一种方法来说明哪种形状构成最终图像。 我们可以使用形状的几何形状为每个形状构建一个简单的描述符:我们保存Mat 4个值,表示中心在垂直和水平方向上与轮廓的距离:

    enter image description here

    这可以唯一标识所有参考形状。 然后我们将这个4元素向量归一化,使其变得不变。使用这个描述符可以避免繁琐的多尺度模板匹配"喜欢的东西,也更快和可扩展。您可以在下面的函数computeShapeDescriptor中找到相应的代码。

    为了计算形状描述符,我们还需要形状中心的正确位置,这只是我们之前发现的blob的质心。我们基本上再次使用cv::connectedComponentWithStats。请参阅下面的getCentroids

    现在我们知道如何找到点来定位所有形状,并知道如何描述它们。要在图像中找到相应的参考形状,只需比较描述符。最相似的一个是正确的!

    enter image description here

    enter image description here enter image description here enter image description here

    完整的参考代码:

    #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;
    }
    

答案 1 :(得分:1)

考虑到您有一个形状字典,您可以通过模板匹配来查找图像中的哪个形状。模板匹配相当简单:你计算一个'#34;拟合优度&#34;对于形状和图像,测量诸如相关性,均方误差等,其形状位于图像中的每个点。请参阅示例these lecture notes。如果使用相关(在这种情况下有意义),您可以使用FFT显着加快计算速度。请查看此OpenCV tutorial

的示例

以上假设模板的尺寸和方向与图像中所示的相同。如果尺寸可能不同,则需要使用多尺度模板匹配方法,我稍微涉及但不困难。只需尝试在不同的比例和方向上多次匹配每个形状。或者旋转和缩放图像。我认为,根据你的例子,你只需要测试4个方向和1个尺度,所以这是一个合理的方法。

更灵活的方法是检测点(例如使用模板匹配),在点周围填充以填充形状(假设它们都是简单的多边形),并提取检测区域的边界。然后可以使用例如字典中的边界匹配该边界。 Fourier descriptors。这将允许您以任意比例和方向检测形状。

答案 2 :(得分:1)

您可以使用这些点来定位各个形状(二值化+连接组件标记)。

然后,检测方角的不存在/存在是一件容易的事,例如通过成对的相邻形状之间的小窗口。这将为您提供可用于区分模式的二进制代码。

enter image description here