存储包含简单多边形的二进制图像的详细信息

Store details of a binary image consisting simple polygons

这个问题涉及到一些实践和基于经验的过程。我有一个 Mat 二值图像,它由黑色背景中的简单白色多边形组成。实际上,这些多边形代表报纸页面中的一篇文章。所以我想要的是存储报纸页面内部文章位置的详细信息。一张 Mat 图像中只有一个多边形。所以一种选择是

  1. 使用纯 OpenCV 调用将 Mat 存储到 .xml 或 .yml 文件中(How to write a Float Mat to a file in OpenCV:接受的答案)
  2. 找到有顶点的多边形的坐标,只将这些坐标存储到数据库中

以下是我要存储的垫子的示例图像。

第一种方案似乎可行,但我不知道如何实施第二种方案。如果可能的话,那将是我认为最有效的,因为这样每篇文章只需要保存很少的坐标。我可以实现一个复杂的过程来找到它的顶点,并在需要时使用这些坐标重新绘制 Mat 图像。但我希望在 opencv 中有一个简单的过程来完成这个任务。

所以我想知道哪种方法更好,如果第二种方法更好,如何在 opencv 中使用 C++ 实现。我既不是 opencv 专家也不是 c++ 专家,所以一个合适的答案可以节省我很多时间,也可以提高程序的效率。

一种稍微不合时宜的方法...您可以轻松地将 Mat 保存为 OpenCV 中的图像 - 最好是 PGMPNG,因为它们是无损的。然后你可以将图像传递给像 potrace 这样的矢量跟踪程序,让它以 SVG 格式告诉你轮廓并将其存储在你的数据库中。

因此,potrace 喜欢 PGM 文件,因此您可以将大纲保存为 OpenCV 中的 PGMPNG,然后使用 ImageMagick 制作将其放入 PGM 并将其传递给 potrace,如下所示:

convert OpenCVImage.png pgm:- | potrace - -b svg -o file.svg

这将为您提供一个 svg 文件,如下所示:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
 "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
 width="3486.000000pt" height="4747.000000pt" viewBox="0 0 3486.000000 4747.000000"
 preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.13, written by Peter Selinger 2001-2015
</metadata>
<g transform="translate(0.000000,4747.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M0 23735 l0 -23735 17430 0 17430 0 0 23735 0 23735 -17430 0 -17430
0 0 -23735z m20980 6560 l0 -3415 -399 0 c-293 0 -402 3 -407 12 -7 11 -68 11
-2391 -9 l-781 -6 -6 -6576 c-3 -3617 -9 -6840 -12 -7163 l-6 -588 -1939 0
-1939 0 0 10580 0 10580 3940 0 3940 0 0 -3415z"/>
</g>
</svg>

顺便说一句,您可以在网络浏览器中查看。

您可以随时调用图像并使用 ImageMagick 或其他工具在命令行重新创建它,如下所示:

convert outline.svg outline.png

我会注意到您的整个 PNG 实际上只有 32kB,而且存储空间非常便宜,因此生成矢量化图像来保存 space 似乎不值得麻烦。事实上,如果你使用像 ImageMagick 这样的好工具并将你的图像转换为一位 PNG,它会减少到 6,150 字节,这是非常小的......

convert YourBigInefficientOutline.png  NiceImageMagickOutlineOf6kB.png

而且,如果您可以将轮廓尺寸缩小到原来的 1/5,这可能仍然足以找到报纸文章,您可以这样做:

convert YourBig.png -resize 700x900 MySmall.png

重量仅为 1,825 字节。

cv::Mat inputImage = cv::imread("input.png", CV_LOAD_IMAGE_GRAYSCALE);

// find non-zero point coordinates
cv::Mat nonZeroCoordinates;
cv::findNonZero(inputImage, nonZeroCoordinates);

然后您可以将nonZeroCoordinates矩阵保存到您的文件中以供使用。

如果你想使用这些坐标创建相同的图像,你可以这样做:

std::vector<std::vector<cv::Point> > points;
points.push_back(nonZeroCoordinates);

cv::Mat output = cv::Mat::zeros(inputImage.size(), CV_8UC1);
cv::fillPoly(output, points, cv::Scalar(255));

希望对您有所帮助!

这取决于多边形的通用程度。如果多边形的边缘始终平行于 xy 轴,那么您可以查看特定像素的 8 个邻域中的像素,如果有奇数个白色像素,您会发现角落。或者使用 4 个邻域并测试偶数个白色像素。

您可以简单地使用 findContours,并使用适当的 轮廓近似方法 。基本上,除了将存储所有点的 CV_CHAIN_APPROX_NONE 之外,其他所有方法都适用于此示例:CV_CHAIN_APPROX_SIMPLECV_CHAIN_APPROX_TC89_L1CV_CHAIN_APPROX_TC89_KCOS.

您可以将这些点存储在您的数据库中。然后您可以重新加载这些点,并使用 fillPoly.

绘制原始图像

这个简单的示例显示了使用近似方法检索到的轮廓点,以及如何使用这些点重新绘制图像。

注意 你的图像有别名(你可能在 png 之前将它保存在 jpeg 中),所以你需要删除别名,例如只保留值等于 255 的点.

#include <opencv2\opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;

int main()
{
    Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE);

    // Removing compression artifacts
    img = img == 255;

    vector<vector<Point>> contours;
    findContours(img.clone(), contours, RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

    if (contours.empty()) {return -1;}

    // Save the vertices "contours[0]"

    // Result image
    Mat3b res;
    cvtColor(img, res, COLOR_GRAY2BGR);
    for (int i = 0; i < contours[0].size(); ++i)
    {
        circle(res, contours[0][i], 3, Scalar(0,255,0));
    }

    // Reconstruct image from contours vertex

    // Load the vertices
    vector<vector<Point>> vertices = { contours[0] };

    Mat1b rec(img.rows, img.cols, uchar(0));
    fillPoly(rec, vertices, Scalar(255));

    imshow("Vertices", res);
    imshow("Reconstructed", rec);
    waitKey();

    return 0;
}

具有等高线近似方法的绿色顶点: