将图像注册到较小的部分图像上,但具有一些共同特征

问题描述 投票:0回答:1

我想转换书面文本的图像(由具有不同 3D 旋转、比例等的手机摄像头拍摄),使它们看起来“正交”(我的意思是,就像您从电子书阅读器中阅读它们一样)。我的下一步将对这些图像进行 OCR,我希望它们不受相机旋转的影响。

所有输入图像都有与所有输入图像相同的页眉和页脚。也就是说,每个图像的顶部都有一个相同的徽标。假设这些是标题、徽标等。在页眉和页脚之间有各种文本内容,其高度(即文本行)各不相同。

每个页面都是这样的,

HEADER HEADER LOGO
-------------------
content   |
content   | height of content varies
...       |
content   |
------------------
FOOTER FOOTER LOGO

我通过获取一个这样的输入图像页面创建了两个“模板”,并应用手动变换来“正交化”它,然后通过删除之间的所有文本内容来隔离页眉和页脚。

所以,我现在有 2 个模板图像

TH
TF
,没有倾斜和正确的旋转等。这两个模板图像仅包含页眉或页脚,因此它们的大小比包含页眉的输入图像小得多,页脚和中间的文本内容。

然后,对于需要注册的每个输入图像,我使用 OpenCV 计算特征描述符(AKAZE/SIFT 等),并将这些特征与模板中的特征进行匹配

TH
(可选地单独使用
TF
以获得鲁棒性) 。单应性矩阵 (
H
) 被计算(通过
findHomography()
)并通过
warpPerspective()
应用于输入图像。

特征计算正确,输入图像与

TH
(或
TF
)之间存在匹配。

问题是当我将单应性应用于输入图像以消除其倾斜时。它缩小了它,因为模板只包含页眉和页脚,而不包含文本内容,因此与输入页面相比它很小。

理想情况下,我希望单应性矩阵不包含任何缩放或翻译,因为模板非常小。我希望它包含的只是旋转/倾斜信息。对我来说,旋转/“矫正”输入图像就足够了,以获得下一阶段 OCR 的更好结果。

我正在使用 OpenCV(Python 或 C++ 不是问题)。

我可以从单应性矩阵中删除一些项目以仅保留旋转吗?

或者提议的管道有缺陷吗?

我想最常见的问题是:如何使用较小的图像作为参考(其中包含,例如,仅一个徽标)来注册大图像。

编辑:我已删除示例代码,请使用我在下面发布的答案中的代码。

python opencv image-processing computer-vision affinetransform
1个回答
0
投票

我在这里试图解决的问题是通过将第一张图像(输入图像)注册到第二张图像(参考)上来固定第一张图像(输入图像)的方向。第一个图像将始终包含第二个图像中包含的徽标。这是使用特征提取注册两个图像的特殊情况,但当第二个图像很小并且仅包含第一个图像的一小部分且方向正确时。

我试图通过将两个图像传递给特征提取器(例如 SIFT/AKAZE 等)来解决这个问题,匹配特征,从匹配的特征创建单应性,最后对第一张图像进行反扭曲。

第三张图显示了使用 SIFT 特征提取器进行的匹配。效果很好。

但是未变形的图像(第四张图像)存在问题。它仅在匹配图像区域(与参考图像,第二个图像匹配)的下方和左侧可见。该匹配区域左侧和上方的所有内容都不可见。

这是我发布问题时遇到的问题。

我的解决方案是找到第一个图像空间中左上角匹配特征的坐标,并将所有第一个图像的匹配特征的坐标减去该量。

另一个解决方案是修改单应性矩阵(H)并删除 x,y 平移分量。它们是 H[0,2] 和 H[1,2](分别沿 x 轴和 y 轴平移)。这样我不确定是否有副作用。

我可以观察到最终图像的方向远非完美。我不确定这是否是该解决方案的副作用,或者只是因为这就是反扭曲的工作原理。

这是重现此工作流程的基本代码。评论显示解决方案在哪里:

import numpy as np
import cv2 as cv

img_ref = cv.imread('SOsubmit/testref.jpg', cv.IMREAD_GRAYSCALE)
# sensedImage
img_inp = cv.imread('SOsubmit/testskewed.jpg', cv.IMREAD_GRAYSCALE)

# Initiate SIFT detector 
sift_detector = cv.SIFT_create()

# Find the keypoints and descriptors with SIFT on the lower resolution images
kp_ref, des_ref = sift_detector.detectAndCompute(img_ref, None)
kp_inp, des_inp = sift_detector.detectAndCompute(img_inp, None)
if des_ref is None or des_inp is None:
    print("failed with descriptors")
    exit(1)

# BFMatcher with default params
bf = cv.BFMatcher()
matches = bf.knnMatch(des_ref, des_inp, k=2)

# Filter out poor matches
good_matches = []
for m,n in matches:
    if m.distance < 0.75*n.distance:
        good_matches.append(m)

matches = good_matches
points_ref = np.zeros((len(matches), 2), dtype=np.float32)
points_inp = np.zeros((len(matches), 2), dtype=np.float32)

img_matches = np.empty((max(img_ref.shape[0], img_inp.shape[0]), img_ref.shape[1]+img_inp.shape[1], 3), dtype=np.uint8)
cv.drawMatches(img_ref, kp_ref, img_inp, kp_inp,
    matches, img_matches, flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
)
cv.imwrite("matches.jpg", img_matches);

# if matches = bf.knnMatch(des_ref, des_inp, k=2)
# then queryIdx is the index of kp_ref (corresponding to des_ref)
# and trainIdx to kp_inp
for i, match in enumerate(matches):
    points_ref[i, :] = kp_ref[match.queryIdx].pt
    points_inp[i, :] = kp_inp[match.trainIdx].pt
    print(i, ") ", kp_ref[match.queryIdx].pt, " -> ", kp_inp[match.trainIdx].pt)

# find the bounding box of the matches on the target image img_inp
ximg_inp=points_inp[0][0]
yimg_inp=points_inp[0][1]
for p in points_inp:
    if p[0] < ximg_inp: ximg_inp = p[0]
    if p[1] < yimg_inp: yimg_inp = p[1]
print("matched points on img_inp start at coordinates: ", ximg_inp, yimg_inp)
# shift the matched points of on img_inp
for p in points_inp: p[0] -= ximg_inp; p[1] -= yimg_inp

# Find homography
#H, mask = cv.findHomography(points_ref, points_inp, cv.RANSAC)
#print("Homography 1:\n", H)
H, mask2 = cv.findHomography(points_inp, points_ref, cv.RANSAC)
print("Homography 2:\n", H)
# alternative solution: remove the translation component of H
# Does it affect the transform in other ways?
#H[0][2] = 0
#H[1][2] = 0
# Warp image 1 to align with image 2
img_ref_unwarped = cv.warpPerspective(
    img_inp,
    H,
    # width is shape[1], height is shape[0]
    (img_inp.shape[1]+int(ximg_inp), img_inp.shape[0]+int(yimg_inp))
)
print("unwarped image size: ", img_ref_unwarped.shape)

cv.imwrite('output.jpg', img_ref_unwarped)
print("unwarped image saved to 'output.jpg'")
© www.soinside.com 2019 - 2024. All rights reserved.