我以前也研究过类似的问题。我使用 OpenCV 的 C++ 实现,但我有一些提示给您。
分割纸张
为了实现更好的细分,请考虑尝试图像量化 https://www.pyimagesearch.com/2014/07/07/color-quantization-opencv-using-k-means-clustering/。该技术将图像分割为N 个簇,即将颜色相似的像素分为一组。然后,该组由一种颜色表示。
该技术相对于其他技术(例如纯技术)的优势二进制阈值,是它可以识别多种颜色分布——那些将被分组的颜色分布N 个簇。看看(抱歉链接,我还不允许发布直接图像):
这将帮助您更好地分割论文。该实现使用称为的聚类算法“K-means”(稍后会详细介绍)。在我的示例中,我尝试了 3 个集群和 5 次算法“运行”(或尝试,因为 K 均值通常运行多次)。
cv::Mat imageQuantization( cv::Mat inputImage, int numberOfClusters = 3, int iterations = 5 ){
//step 1 : map the src to the samples
cv::Mat samples(inputImage.total(), 3, CV_32F);
auto samples_ptr = samples.ptr<float>(0);
for( int row = 0; row != inputImage.rows; ++row){
auto src_begin = inputImage.ptr<uchar>(row);
auto src_end = src_begin + inputImage.cols * inputImage.channels();
//auto samples_ptr = samples.ptr<float>(row * src.cols);
while(src_begin != src_end){
samples_ptr[0] = src_begin[0];
samples_ptr[1] = src_begin[1];
samples_ptr[2] = src_begin[2];
samples_ptr += 3; src_begin +=3;
}
}
//step 2 : apply kmeans to find labels and centers
int clusterCount = numberOfClusters; //Number of clusters to split the set by
cv::Mat labels;
int attempts = iterations; //Number of times the algorithm is executed using different initial labels
cv::Mat centers;
int flags = cv::KMEANS_PP_CENTERS;
cv::TermCriteria criteria = cv::TermCriteria( CV_TERMCRIT_ITER | CV_TERMCRIT_EPS,
10, 0.01 );
//the call to kmeans:
cv::kmeans( samples, clusterCount, labels, criteria, attempts, flags, centers );
//step 3 : map the centers to the output
cv::Mat clusteredImage( inputImage.size(), inputImage.type() );
for( int row = 0; row != inputImage.rows; ++row ){
auto clusteredImageBegin = clusteredImage.ptr<uchar>(row);
auto clusteredImageEnd = clusteredImageBegin + clusteredImage.cols * 3;
auto labels_ptr = labels.ptr<int>(row * inputImage.cols);
while( clusteredImageBegin != clusteredImageEnd ){
int const cluster_idx = *labels_ptr;
auto centers_ptr = centers.ptr<float>(cluster_idx);
clusteredImageBegin[0] = centers_ptr[0];
clusteredImageBegin[1] = centers_ptr[1];
clusteredImageBegin[2] = centers_ptr[2];
clusteredImageBegin += 3; ++labels_ptr;
}
}
//return the output:
return clusteredImage;
}
请注意,该算法还生成两个附加矩阵。"Labels"是用标识其簇的整数标记的实际像素。“中心”是mean每个簇的值。
检测边缘
现在,运行一个边缘检测器在此分割图像上。我们来试试坎尼吧。当然,您可以调整参数。在这里,我尝试了一个较低的门槛0f 30,和一个上限90。相当标准,只需确保上阈值遵循 = 3 * 下阈值的条件,按照 Canny 的建议。这是结果:
cv::Mat testEdges;
float lowerThreshold = 30;
float upperThreshold = 3 * lowerThreshold;
cv::Canny( testSegmented, testEdges, lowerThreshold, upperThreshold );
检测线条
好的。想要检测边缘检测器产生的线条吗?在这里,至少有两个选择。第一个也是最直接的:使用霍夫线检测器。然而,正如您肯定已经看到的那样,调整霍夫以找到您真正正在寻找的线条可能很困难。
过滤 Hough 返回的行的一种可能的解决方案是运行“角度滤镜”,因为我们只寻找(接近)垂直和水平线。您还可以按长度过滤行。
此代码片段给出了想法,您需要实际实现过滤器:
// 运行霍夫线检测器:
cv::HoughLinesP(grad,linesP,1,CV_PI/180,minVotes,minLineLength,maxLineGap);
// Process the points (lines)
for( size_t i = 0; i < linesP.size(); i++ ) //points are stored in linesP
{
//get the line
cv::Vec4i l = linesP[i]; //get the line
//get the points:
cv::Point startPoint = cv::Point( l[0], l[1] );
cv::Point endPoint = cv::Point( l[2], l[3] );
//filter horizontal & vertical:
float dx = abs(startPoint.x - endPoint.x);
float dy = abs(startPoint.y - endPoint.y);
//angle filtering, delta y and delta x
if ( (dy < maxDy) || (dx < maxDx) ){
//got my target lines!
}
}
在上面的代码中,我实际上正在使用线组件,而不是角度。所以,我的“角度”限制由 2 定义最小元件长度: maxDy- y 轴上的最大“增量”长度,以及maxDx对于 x 轴。
线条检测的另一个解决方案是利用这样一个事实:您只查看具有以下特征的线条:CORNERS或者它们之间大约 90 度。您可以运行形态过滤器来通过以下方式检测这些“模式”:击中或没打中手术 :)
不管怎样,回到霍夫,这是我在没有太多参数调整并且应用角度/长度线滤波器之后得到的检测:
凉爽的。绿点代表线条的起点和终点。如您所见,有很多。怎样才能将它们“结合”起来呢?如果我们计算mean那些点?好的,但我们应该得到每个“象限”线的平均值。如下图所示,我将输入图像分为 4 个象限(黄线):
希望每个象限都包含描述纸张角的点。对于每个象限,检查哪些点落在给定象限上并计算它们的平均值。这就是总体思路。
这需要编写相当多的代码。幸运的是,如果我们稍微研究一下这个问题,我们就会发现所有的绿点都倾向于CLUSTER在一些非常明确的区域(或者,正如我们之前所说的,“象限”。)再次输入 K 均值。
无论如何,K 均值都会对具有相似值的数据进行分组。它可以是像素,它可以是空间点,它可以是任何东西,只要给它数据集和你想要的簇的数量,它就会吐出找到的簇和所述簇的含义——很好!
如果我使用 Hough 返回的线点运行 K 均值,我会得到最后一个图像中显示的结果。我还丢弃了与平均值相差太远的点。点的平均值通过“中心”矩阵返回,在这里它们以橙色呈现——非常接近!
希望其中一些对您有所帮助! :)