获取度量测量用图像单应性及解决矫正图像过小问题
问题描述
我有一张带标记线的便利贴图像,想计算标记线在现实世界中的水平位置(单位:cm)。图像尺寸为3456×3456像素,便利贴实际尺寸是9×9cm。我手动选取了便利贴四个角的像素坐标[[1769, 353], [3199, 835], [2683, 2145], [983,1409]],并尝试用OpenCV的getPerspectiveTransform函数估计单应性矩阵,代码如下:
import cv2 as cv import numpy as np src_bbox = [[0,0], [9, 0], [9, 9], [0, 9]] # [cm] dst_bbox = [[1769, 353], [3199, 835], [2683, 2145], [983,1409]] # [px] H = cv.getPerspectiveTransform(np.array(src_bbox).astype(np.float32), np.array(dst_bbox).astype(np.float32)) H_inv = np.linalg.inv(H) image = cv.imread("sheet.jpg") if image is None: exit("Could not read the image.") mod = cv.warpPerspective(image, H_inv, image.shape[:2]) cv.namedWindow("sheet", cv.WINDOW_NORMAL) cv.resizeWindow("sheet", 400, 400) cv.namedWindow("sheet_warped", cv.WINDOW_NORMAL) cv.resizeWindow("sheet_warped", 400, 400) cv.imshow("sheet", image) cv.imshow("sheet_warped", mod) cv.waitKey(0) cv.destroyAllWindows()
运行后得到的矫正图像过小,没法识别标记线来获取其厘米单位的位置。请问这是不是因为对应点用了不同单位(像素vs厘米)、量级差异导致的缩放问题?该怎么选合适的缩放因子?
解决方案
问题根源:确实是单位量级差异导致的缩放问题。你用厘米(最大数值9)作为源坐标,像素(最大数值超过3000)作为目标坐标,单应性矩阵会把厘米空间的小尺寸映射到像素空间,逆变换时就会把像素空间的内容压缩到极小的厘米空间范围内,所以矫正后的图像才会过小。
调整思路:统一坐标单位的量级,要么把现实世界的厘米坐标按比例放大到像素量级,要么把像素坐标缩小到厘米量级,核心是让源和目标坐标的尺度匹配。
具体实现:
- 计算缩放因子:先算便利贴在图像中的像素宽度/高度,比如取两个对角的像素距离,或者直接用图像尺寸和实际尺寸的比例。这里便利贴实际9cm,对应图像中我们可以先算出它的像素尺寸,比如从
[1769,353]到[3199,835]的水平像素距离是3199-1769=1430像素,对应9cm,所以缩放因子为1430/9 ≈ 158.89像素/cm(也可以用图像整体尺寸3456像素对应9cm,得到3456/9=384像素/cm,两种都可行,前者更贴合实际检测到的便利贴尺寸)。 - 修改源坐标:把现实世界的厘米坐标乘以缩放因子,转换成像素量级的虚拟坐标。比如
src_bbox改成[[0,0], [9*scale, 0], [9*scale, 9*scale], [0, 9*scale]],这样源和目标坐标的尺度就一致了。 - 调整逆变换后的输出尺寸:
warpPerspective的第三个参数是输出图像的尺寸,应该设为我们期望的矫正后尺寸,比如(int(9*scale), int(9*scale)),这样矫正后的图像大小合适,方便识别标记线。
- 计算缩放因子:先算便利贴在图像中的像素宽度/高度,比如取两个对角的像素距离,或者直接用图像尺寸和实际尺寸的比例。这里便利贴实际9cm,对应图像中我们可以先算出它的像素尺寸,比如从
修改后的代码示例:
import cv2 as cv import numpy as np # 实际尺寸转像素量级,用便利贴实际宽度对应的像素距离计算缩放因子 # 取右上角和左上角的水平像素差:3199-1769=1430像素对应9cm scale = (3199 - 1769) / 9 # 约158.89像素/cm # 也可以用图像整体尺寸:scale = 3456 / 9 = 384像素/cm,按需选择 src_bbox = [[0,0], [9*scale, 0], [9*scale, 9*scale], [0, 9*scale]] # 像素量级的虚拟坐标 dst_bbox = [[1769, 353], [3199, 835], [2683, 2145], [983,1409]] # 实际像素坐标 H = cv.getPerspectiveTransform(np.array(src_bbox).astype(np.float32), np.array(dst_bbox).astype(np.float32)) H_inv = np.linalg.inv(H) image = cv.imread("sheet.jpg") if image is None: exit("Could not read the image.") # 设置输出尺寸为矫正后的便利贴像素尺寸 output_size = (int(9*scale), int(9*scale)) mod = cv.warpPerspective(image, H_inv, output_size) cv.namedWindow("sheet", cv.WINDOW_NORMAL) cv.resizeWindow("sheet", 400, 400) cv.namedWindow("sheet_warped", cv.WINDOW_NORMAL) cv.resizeWindow("sheet_warped", 400, 400) cv.imshow("sheet", image) cv.imshow("sheet_warped", mod) cv.waitKey(0) cv.destroyAllWindows()
- 获取标记线的实际位置:矫正后,图像中每个像素对应
1/scale厘米。比如标记线的水平像素坐标是x_px,那么实际水平位置就是x_px / scalecm。
内容的提问来源于stack exchange,提问作者Fru
相关产品推荐
相关产品推荐

