【问题标题】:How to extract different shapes from an Image如何从图像中提取不同的形状
【发布时间】:2017-11-05 05:29:01
【问题描述】:

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

问题陈述:

我有一张由图案组成的图像。该图案是使用不同的个人形状创建的。下面是图案和用于形成图案的单个形状。

详细的问题陈述

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

所有独特的形状: 更多图案图片

我想要达到的目标

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

注意:我没有包括所有单独的形状,因为问题变得太大了。

图案图像:

个人形状:

【问题讨论】:

  • 所以你需要检测这些点,然后找到每个点周围的区域?
  • 我知道天气我正在为您提供正确的答案,但是。我对完整的个人形状更感兴趣。除了两个形状外,每个形状都有不同数量的角。这两个形状具有相同数量的角(两个角),但是两个形状的角位置不同。如果我们可以使用这些功能,那么我想我们可以在主图像中检测到不同的形状及其位置@CrisLuengo
  • 更新了问题以便更好地理解@CrisLuengo
  • 所以尝试一下:检测点,找到它们周围的区域作为要比较的单独形状,找到它们的轮廓,找到角的相对位置,匹配它们以找到独特的形状。傅里叶描述符可能是比较形状的一种更通用的方法,如果您事先不知道它们会有什么不同。
  • 如果您有一本形状字典,您可以尝试模板匹配来查找使用过的形状和位置。那可能会更简单。

标签: opencv image-processing


【解决方案1】:

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

  1. 定位所有形状中的中心点
  2. 知道点在哪里,找到正确的形状。

对于这个答案的范围,我使用这些已经预处理的图像。第一张图片是简单的阈值处理,第二张我使用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;
}

【讨论】:

  • 我无语了。你是我的神!!它太神奇了。太感谢了。你的回答很中肯。再次感谢。
  • 我想关注你@Miki 需要让自己更新你的工作:)
  • 只要有一点经验,你就可以很容易地做到这一点。所以继续尝试和学习......不要等待互联网上的随机人来解决你的问题。不过,这个很有趣;)很高兴它有帮助!
  • 只有一个帮助。如何开始阅读和练习计算机视觉?我已经读过很多书了。但是从来没有一个完美的地方可以详细阅读所有有关计算机视觉的信息。你是怎么开始的? @Miki 如果你有时间请告诉我。
  • 将复杂问题分解为更小更简单的问题。你有很多在线资源(比如 SO)来解决简单的问题。学习计算机视觉课程(大学、博士等)也可能有助于学习基础知识
【解决方案2】:

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

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

【讨论】:

  • 用更多模式更新了问题@YvesDaoust
  • 如果可能的话,你能给我提供一些实现吗?我对这种图像处理完全陌生。
  • @Hiren:完全重写了我的答案。看到你的模式,情况很容易:找到点,看看中点。而且,对不起,不,我不是免费的编码工厂。
  • 感谢这个想法。让我尝试实施。如果我卡住了,会在 cmets 中通知你。
【解决方案3】:

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

以上假设模板的比例和方向与图片所示相同。如果大小可以不同,则需要使用多尺度模板匹配方法,我涉及的内容稍微多一些,但并不难。只需尝试以不同的比例和方向多次匹配每个形状。或旋转和缩放您的图像。我认为,根据您的示例,您只需要测试 4 个方向和 1 个比例,因此这是一种合理的方法。

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

【讨论】:

  • 听起来不错,适用于我。让我先试试看。如果我遇到任何问题会在 cmets 中通知您。
  • 我想我可能需要多尺度模板匹配技术。但我真的很困惑如何开始。将不胜感激。
  • 我自己没有任何可以分享的例子。谷歌确实显示了很多点击量。比如这个:codethatactuallyworks.blogspot.com/2015/12/…
  • 我正在为你工作。一旦我得到一些结果,我一定会分享更新。
猜你喜欢
  • 2018-10-30
  • 1970-01-01
  • 1970-01-01
  • 2013-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-21
  • 1970-01-01
相关资源
最近更新 更多