存储包含简单多边形的二进制图像的详细信息
Store details of a binary image consisting simple polygons
这个问题涉及到一些实践和基于经验的过程。我有一个 Mat 二值图像,它由黑色背景中的简单白色多边形组成。实际上,这些多边形代表报纸页面中的一篇文章。所以我想要的是存储报纸页面内部文章位置的详细信息。一张 Mat 图像中只有一个多边形。所以一种选择是
- 使用纯 OpenCV 调用将 Mat 存储到 .xml 或 .yml 文件中(How to write a Float Mat to a file in OpenCV:接受的答案)
- 找到有顶点的多边形的坐标,只将这些坐标存储到数据库中
以下是我要存储的垫子的示例图像。
第一种方案似乎可行,但我不知道如何实施第二种方案。如果可能的话,那将是我认为最有效的,因为这样每篇文章只需要保存很少的坐标。我可以实现一个复杂的过程来找到它的顶点,并在需要时使用这些坐标重新绘制 Mat 图像。但我希望在 opencv 中有一个简单的过程来完成这个任务。
所以我想知道哪种方法更好,如果第二种方法更好,如何在 opencv 中使用 C++ 实现。我既不是 opencv 专家也不是 c++ 专家,所以一个合适的答案可以节省我很多时间,也可以提高程序的效率。
一种稍微不合时宜的方法...您可以轻松地将 Mat 保存为 OpenCV 中的图像 - 最好是 PGM
或 PNG
,因为它们是无损的。然后你可以将图像传递给像 potrace
这样的矢量跟踪程序,让它以 SVG 格式告诉你轮廓并将其存储在你的数据库中。
因此,potrace
喜欢 PGM
文件,因此您可以将大纲保存为 OpenCV 中的 PGM
或 PNG
,然后使用 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));
希望对您有所帮助!
这取决于多边形的通用程度。如果多边形的边缘始终平行于 x
和 y
轴,那么您可以查看特定像素的 8 个邻域中的像素,如果有奇数个白色像素,您会发现角落。或者使用 4 个邻域并测试偶数个白色像素。
您可以简单地使用 findContours,并使用适当的 轮廓近似方法 。基本上,除了将存储所有点的 CV_CHAIN_APPROX_NONE
之外,其他所有方法都适用于此示例:CV_CHAIN_APPROX_SIMPLE
、CV_CHAIN_APPROX_TC89_L1
和 CV_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;
}
具有等高线近似方法的绿色顶点:
这个问题涉及到一些实践和基于经验的过程。我有一个 Mat 二值图像,它由黑色背景中的简单白色多边形组成。实际上,这些多边形代表报纸页面中的一篇文章。所以我想要的是存储报纸页面内部文章位置的详细信息。一张 Mat 图像中只有一个多边形。所以一种选择是
- 使用纯 OpenCV 调用将 Mat 存储到 .xml 或 .yml 文件中(How to write a Float Mat to a file in OpenCV:接受的答案)
- 找到有顶点的多边形的坐标,只将这些坐标存储到数据库中
以下是我要存储的垫子的示例图像。
第一种方案似乎可行,但我不知道如何实施第二种方案。如果可能的话,那将是我认为最有效的,因为这样每篇文章只需要保存很少的坐标。我可以实现一个复杂的过程来找到它的顶点,并在需要时使用这些坐标重新绘制 Mat 图像。但我希望在 opencv 中有一个简单的过程来完成这个任务。
所以我想知道哪种方法更好,如果第二种方法更好,如何在 opencv 中使用 C++ 实现。我既不是 opencv 专家也不是 c++ 专家,所以一个合适的答案可以节省我很多时间,也可以提高程序的效率。
一种稍微不合时宜的方法...您可以轻松地将 Mat 保存为 OpenCV 中的图像 - 最好是 PGM
或 PNG
,因为它们是无损的。然后你可以将图像传递给像 potrace
这样的矢量跟踪程序,让它以 SVG 格式告诉你轮廓并将其存储在你的数据库中。
因此,potrace
喜欢 PGM
文件,因此您可以将大纲保存为 OpenCV 中的 PGM
或 PNG
,然后使用 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));
希望对您有所帮助!
这取决于多边形的通用程度。如果多边形的边缘始终平行于 x
和 y
轴,那么您可以查看特定像素的 8 个邻域中的像素,如果有奇数个白色像素,您会发现角落。或者使用 4 个邻域并测试偶数个白色像素。
您可以简单地使用 findContours,并使用适当的 轮廓近似方法 。基本上,除了将存储所有点的 CV_CHAIN_APPROX_NONE
之外,其他所有方法都适用于此示例:CV_CHAIN_APPROX_SIMPLE
、CV_CHAIN_APPROX_TC89_L1
和 CV_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;
}
具有等高线近似方法的绿色顶点: