代码实现了一个典型的文档/物体轮廓检测与透视矫正流程:先找到图像中最大的四边形轮廓,然后通过透视变换把它"拉正",最后做二值化、去噪和旋转,方便后续识别或处理。
代码示例:
1. 辅助函数
1.1 cv_show(name, img)
def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
-
作用:封装 OpenCV 的显示函数,方便调试。
-
知识点:
cv2.imshow显示窗口,cv2.waitKey(0)等待任意按键后关闭/继续。
1.2 order_points(pts)
def order_points(pts):
rect = np.zeros((4,2), dtype="float32")
s = pts.sum(axis=1)
rect0 = ptsnp.argmin(s) # 左上
rect2 = ptsnp.argmax(s) # 右下
diff = np.diff(pts, axis=1)
rect1 = ptsnp.argmin(diff) # 右上
rect3 = ptsnp.argmax(diff) # 左下
return rect
-
作用:将无序的 4 个角点按 左上、右上、右下、左下 的顺序排列。
-
知识点:
-
对于左上和右下:
sum = x + y,左上角点x+y最小,右下角点x+y最大。 -
对于右上和左下:
diff = y - x(因为np.diff对每行做后一列减前一列),右上角点y - x最小(x 大 y 小),左下角点y - x最大(x 小 y 大)。
-
1.3 four_point_transform(image, pts)
def four_point_transform(image, pts):
rect = order_points(pts)
(tl, tr, br, bl) = rect
计算宽度
widthA = np.sqrt(((br0-bl0)**2)+((br1-bl1)**2))
widthB = np.sqrt(((tr0-tl0)**2)+((tr1-tl1)**2))
maxWidth = max(int(widthA), int(widthB))
计算高度
heightA = np.sqrt(((tr0-br0)**2)+((tr1-br1)**2))
heightB = np.sqrt(((tl0-bl0)**2)+((tl1-bl1)**2))
maxHeight = max(int(heightA), int(heightB))
目标点
dst = np.array(\[0,0,maxWidth-1,0,maxWidth-1,maxHeight-1,0,maxHeight-1], dtype="float32")
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
return warped
-
作用:根据 4 个源角点计算透视变换矩阵,将原图中的四边形区域矫正为矩形。
-
知识点:
-
欧氏距离计算两点距离,分别求上下边、左右边长度,取最大值作为目标矩形的宽和高。
-
cv2.getPerspectiveTransform(src, dst):根据 4 对点计算 3×3 透视变换矩阵。 -
cv2.warpPerspective(image, M, dsize):应用透视变换,输出尺寸为(maxWidth, maxHeight)。 -
透视变换可以校正拍摄角度造成的变形,是文档扫描的核心步骤。
-
1.4 resize(image, width=None, height=None, inter=cv2.INTER_AREA)
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
(h, w) = image.shape:2
if width is None and height is None:
return image
if width is None:
r = height / float(h)
dim = (int(w * r), height)
else:
r = width / float(w)
dim = (width, int(h * r))
resized = cv2.resize(image, dim, interpolation=inter)
return resized
-
作用:等比缩放图像,指定宽度或高度之一即可。
-
知识点:
-
cv2.resize可以调整图像尺寸。 -
插值方法
cv2.INTER_AREA适合缩小图像,能更好地保留细节并减少锯齿。
-
2. 主流程
2.1 读取并缩放图像
image = cv2.imread('4.jpg')
ratio = image.shape0 / 500.0
orig = image.copy()
image = resize(orig, height=500)
-
作用:读取原图,记录缩放比例
ratio,将图像等比缩放至高度 500 像素,便于后续处理(加快速度、减少噪声)。 -
知识点:
image.shape[0]是高度,image.shape[1]是宽度。
2.2 灰度化 + Otsu 二值化
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edged = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)1
-
作用:转为灰度图,然后用 Otsu 自动阈值得到二值图(前景为白色,背景为黑色)。
-
知识点:
-
cv2.cvtColor:颜色空间转换,COLOR_BGR2GRAY表示 BGR 转灰度。 -
cv2.threshold:固定/自动阈值二值化。这里THRESH_OTSU自动计算最优阈值,使前景和背景的类间方差最大。 -
注意:变量名
edged虽然叫 "edged",但这里其实不是边缘检测(如 Canny),而是二值图。后面findContours就是在这个二值图上找轮廓。
-
2.3 轮廓检测与绘制
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)-2
image_contours = cv2.drawContours(image.copy(), cnts, -1, (0,255,0), 1)
-
作用:在二值图中找出所有外部轮廓,并画在原图上显示。
-
知识点:
-
cv2.findContours:-
第一个参数是二值图像(非零像素被视为前景)。
-
RETR_EXTERNAL:只提取最外层轮廓,忽略内部孔洞。 -
CHAIN_APPROX_SIMPLE:压缩水平、垂直、对角线段,只保留端点,减少存储量。
-
-
不同 OpenCV 版本返回值的数量不同,OpenCV 3/4 返回
(image, contours, hierarchy),所以用[-2]取contours。 -
cv2.drawContours:可以绘制多条轮廓,-1表示绘制所有轮廓。
-
2.4 获取最大轮廓并进行多边形近似
screenCnt = sorted(cnts, key=cv2.contourArea, reverse=True)0
peri = cv2.arcLength(screenCnt, True)
screenCnt = cv2.approxPolyDP(screenCnt, 0.05*peri, True)
-
作用:选择面积最大的轮廓(假设它是目标文档),计算其周长,然后用 Douglas-Peucker 算法近似为多边形(通常得到四边形)。
-
知识点:
-
cv2.contourArea:计算轮廓面积。 -
cv2.arcLength:计算轮廓周长,True表示闭合曲线。 -
cv2.approxPolyDP:多边形近似,epsilon=0.05*peri是近似精度,值越大形状越简单。 -
这里假设最大轮廓就是目标文档,如果图像中有其他更大的轮廓(如背景边框),会出错。
-

2.5 透视变换矫正
warped = four_point_transform(orig, screenCnt.reshape(4,2)*ratio)
-
作用:将轮廓的 4 个角点乘以
ratio还原到原始图像尺寸,然后对原图进行透视矫正。 -
知识点:
-
因为之前的轮廓是在缩放后的图像上检测的,坐标需要乘
ratio才能对应到原图。 -
screenCnt形状通常是(N,1,2),reshape(4,2)变成(4,2)方便处理。
-
2.6 再次二值化与缩放
warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
ref = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)1
ref = resize(ref, width=900)
-
作用:将矫正后的彩色图转为灰度图,再次 Otsu 二值化,使文字/内容更清晰,然后等比缩放至宽度 900。
-
知识点:二次二值化可以进一步分离前景和背景,便于后续处理。
2.7 形态学腐蚀
kernel = np.ones((2,2), np.uint8)
ref_new = cv2.erode(ref, kernel, iterations=1)
-
作用:用 2×2 的矩形结构元素对二值图进行一次腐蚀操作。
-
知识点:
-
cv2.erode:腐蚀操作,可以去除小的白色噪点、断开细微连接。 -
kernel是结构元素,决定腐蚀的邻域形状和大小。 -
iterations=1表示腐蚀一次。
-
2.8 旋转图像
rotated_image = cv2.rotate(ref_new, cv2.ROTATE_90_COUNTERCLOCKWISE)
cv_show('rotated_image', rotated_image)
-
作用:将图像逆时针旋转 90 度并显示。
-
知识点:
cv2.rotate是固定角度旋转,比仿射变换更简单。常用参数:-
ROTATE_90_CLOCKWISE:顺时针 90° -
ROTATE_90_COUNTERCLOCKWISE:逆时针 90° -
ROTATE_180:旋转 180° -

-
3. 主要知识点总结
| 操作 | 知识点 | OpenCV 函数 |
|---|---|---|
| 缩放 | 等比缩放、插值方法 | cv2.resize |
| 灰度化 | BGR → Gray | cv2.cvtColor |
| 二值化 | Otsu 自动阈值 | cv2.threshold + THRESH_OTSU |
| 轮廓检测 | 只找外部轮廓、压缩存储 | cv2.findContours |
| 轮廓属性 | 面积、周长 | cv2.contourArea、cv2.arcLength |
| 轮廓近似 | Douglas-Peucker 算法 | cv2.approxPolyDP |
| 透视变换 | 四点矫正、视角校正 | cv2.getPerspectiveTransform、cv2.warpPerspective |
| 形态学操作 | 腐蚀去噪 | cv2.erode |
| 旋转 | 固定角度旋转 | cv2.rotate |
4. 注意事项
-
cv2.findContours版本差异 :OpenCV 3/4 返回 3 个值,用[-2]取轮廓;OpenCV 2 返回 2 个值,需调整。 -
最大轮廓假设:代码默认最大轮廓是目标文档,如果图像中有更大的干扰轮廓,结果会错误。
-
多边形近似点数 :
approxPolyDP可能得到多于或少于 4 个点,若点数不是 4,four_point_transform会报错。实际应用中应检查len(screenCnt) == 4。 -
Otsu 的适用性:Otsu 适合直方图双峰明显的图像,如果光照不均或前景背景差异小,二值化效果会差。
-
腐蚀结构元素:结构元素太小(2×2)去噪能力有限,太大又会损伤文字笔画,需根据图像调整。
-
缩放比例还原 :注意
ratio的计算基于高度缩放,若之后也做了宽度缩放则可能不适用,但这里缩放函数保证等比,所以没问题。