OpenCV 图像匹配——从照片到表单模板
OpenCV image matching - form photo vs form template
我正在尝试检测照片是否表示填充有数据的预定义公式模板。
我是图像处理和 OpenCV 的新手,但我的第一次尝试是使用 FlannBasedMatcher 并比较检测到的关键点的数量。
有更好的方法吗?
import numpy as np
import cv2
from matplotlib import pyplot as plt
MIN_MATCH_COUNT = 10
img1 = cv2.imread('filled-form.jpg',0) # queryImage
img2 = cv2.imread('template-form.jpg',0) # trainImage
# Initiate SIFT detector
sift = cv2.xfeatures2d.SIFT_create()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks = 50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1,des2,k=2)
# store all the good matches as per Lowe's ratio test.
good = []
for m,n in matches:
if m.distance < 0.7*n.distance:
good.append(m)
if len(good)>MIN_MATCH_COUNT:
print "ALL GOOD!"
else:
print "Not enough matches are found - %d/%d" % (len(good),MIN_MATCH_COUNT)
matchesMask = None
我认为使用 SIFT 和 keypoints matcher 是最 robust 的方法这个问题。它应该适用于许多不同的表单模板。但是,SIFT 算法已 获得专利 ,这是另一种应该也能很好工作的方法:
第 1 步:二值化
- 使用
THRESH_OTSU
标签限制您的照片和模板表单。
- 用
bitwise_not
函数反转两个二进制结果Mat
s。
第 2 步:找到表单的边界矩形
对于来自步骤 1 的两个二进制 Mat
:
- 找到最大的轮廓。
- 使用
approxPolyDP
将找到的轮廓近似为四边形(见上图)。
在我的代码中,这是在 getQuadrilateral()
.
内部完成的
第 3 步:单应和变形
- 用
findHomography
求出两种形式的边界矩形之间的变换
- 使用
warpPerspective
(以及之前计算的单应性 Mat
)扭曲照片的二进制 Mat
。
第四步:模板与照片对比
- 扩展模板表单的二进制文件
Mat
。
- 减去变形二进制
Mat
和扩展模板形式的二进制 Mat
。
这允许提取填充的信息。但你也可以反过来做:
模板形式 - 膨胀变形Mat
在这种情况下,减法的结果应该是全黑的。然后我会使用 mean
来获得平均像素值。最后,如果该值小于(比方说)2,我会假设照片上的表格与模板表格匹配。
这是C++代码,翻译成Python应该不难:)
vector<Point> getQuadrilateral(Mat & grayscale)
{
vector<vector<Point>> contours;
findContours(grayscale, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
vector<int> indices(contours.size());
iota(indices.begin(), indices.end(), 0);
sort(indices.begin(), indices.end(), [&contours](int lhs, int rhs) {
return contours[lhs].size() > contours[rhs].size();
});
vector<vector<Point>> polygon(1);
approxPolyDP(contours[indices[0]], polygon[0], 5, true);
if (polygon[0].size() == 4) // we have found a quadrilateral
{
return(polygon[0]);
}
return(vector<Point>());
}
int main(int argc, char** argv)
{
Mat templateImg, sampleImg;
templateImg = imread("template-form.jpg", 0);
sampleImg = imread("sample-form.jpg", 0);
Mat templateThresh, sampleTresh;
threshold(templateImg, templateThresh, 0, 255, THRESH_OTSU);
threshold(sampleImg, sampleTresh, 0, 255, THRESH_OTSU);
bitwise_not(templateThresh, templateThresh);
bitwise_not(sampleTresh, sampleTresh);
vector<Point> corners_template = getQuadrilateral(templateThresh);
vector<Point> corners_sample = getQuadrilateral(sampleTresh);
Mat homography = findHomography(corners_sample, corners_template);
Mat warpSample;
warpPerspective(sampleTresh, warpSample, homography, Size(templateThresh.cols, templateThresh.rows));
Mat element_dilate = getStructuringElement(MORPH_ELLIPSE, Size(8, 8));
dilate(templateThresh, templateThresh, element_dilate);
Mat diff = warpSample - templateThresh;
imshow("diff", diff);
waitKey(0);
return 0;
}
我希望它足够清楚! ;)
P.S。这个帮我找回了最大的轮廓。
我正在尝试检测照片是否表示填充有数据的预定义公式模板。
我是图像处理和 OpenCV 的新手,但我的第一次尝试是使用 FlannBasedMatcher 并比较检测到的关键点的数量。
有更好的方法吗?
import numpy as np
import cv2
from matplotlib import pyplot as plt
MIN_MATCH_COUNT = 10
img1 = cv2.imread('filled-form.jpg',0) # queryImage
img2 = cv2.imread('template-form.jpg',0) # trainImage
# Initiate SIFT detector
sift = cv2.xfeatures2d.SIFT_create()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks = 50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1,des2,k=2)
# store all the good matches as per Lowe's ratio test.
good = []
for m,n in matches:
if m.distance < 0.7*n.distance:
good.append(m)
if len(good)>MIN_MATCH_COUNT:
print "ALL GOOD!"
else:
print "Not enough matches are found - %d/%d" % (len(good),MIN_MATCH_COUNT)
matchesMask = None
我认为使用 SIFT 和 keypoints matcher 是最 robust 的方法这个问题。它应该适用于许多不同的表单模板。但是,SIFT 算法已 获得专利 ,这是另一种应该也能很好工作的方法:
第 1 步:二值化
- 使用
THRESH_OTSU
标签限制您的照片和模板表单。 - 用
bitwise_not
函数反转两个二进制结果Mat
s。
第 2 步:找到表单的边界矩形
对于来自步骤 1 的两个二进制 Mat
:
- 找到最大的轮廓。
- 使用
approxPolyDP
将找到的轮廓近似为四边形(见上图)。
在我的代码中,这是在 getQuadrilateral()
.
第 3 步:单应和变形
- 用
findHomography
求出两种形式的边界矩形之间的变换
- 使用
warpPerspective
(以及之前计算的单应性Mat
)扭曲照片的二进制Mat
。
第四步:模板与照片对比
- 扩展模板表单的二进制文件
Mat
。 - 减去变形二进制
Mat
和扩展模板形式的二进制Mat
。
这允许提取填充的信息。但你也可以反过来做:
模板形式 - 膨胀变形Mat
在这种情况下,减法的结果应该是全黑的。然后我会使用 mean
来获得平均像素值。最后,如果该值小于(比方说)2,我会假设照片上的表格与模板表格匹配。
这是C++代码,翻译成Python应该不难:)
vector<Point> getQuadrilateral(Mat & grayscale)
{
vector<vector<Point>> contours;
findContours(grayscale, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
vector<int> indices(contours.size());
iota(indices.begin(), indices.end(), 0);
sort(indices.begin(), indices.end(), [&contours](int lhs, int rhs) {
return contours[lhs].size() > contours[rhs].size();
});
vector<vector<Point>> polygon(1);
approxPolyDP(contours[indices[0]], polygon[0], 5, true);
if (polygon[0].size() == 4) // we have found a quadrilateral
{
return(polygon[0]);
}
return(vector<Point>());
}
int main(int argc, char** argv)
{
Mat templateImg, sampleImg;
templateImg = imread("template-form.jpg", 0);
sampleImg = imread("sample-form.jpg", 0);
Mat templateThresh, sampleTresh;
threshold(templateImg, templateThresh, 0, 255, THRESH_OTSU);
threshold(sampleImg, sampleTresh, 0, 255, THRESH_OTSU);
bitwise_not(templateThresh, templateThresh);
bitwise_not(sampleTresh, sampleTresh);
vector<Point> corners_template = getQuadrilateral(templateThresh);
vector<Point> corners_sample = getQuadrilateral(sampleTresh);
Mat homography = findHomography(corners_sample, corners_template);
Mat warpSample;
warpPerspective(sampleTresh, warpSample, homography, Size(templateThresh.cols, templateThresh.rows));
Mat element_dilate = getStructuringElement(MORPH_ELLIPSE, Size(8, 8));
dilate(templateThresh, templateThresh, element_dilate);
Mat diff = warpSample - templateThresh;
imshow("diff", diff);
waitKey(0);
return 0;
}
我希望它足够清楚! ;)
P.S。这个