最近又捣鼓了两个 opencv 的小项目,一个是实时检测摄像头画面中的文档,通过透视变换把它“摆正”并二值化,有点像手机扫描软件;另一个是用训练好的神经网络模型对图片做风格迁移,把普通照片变成卡通或油画效果。代码都不长,但涉及的知识点挺实用,整理出来分享一下。
一、文档扫描与透视变换
这个例子实现了一个实时文档扫描功能:打开摄像头,检测画面中的四边形文档,然后利用透视变换把倾斜的文档转成正视图,最后做二值化处理,方便后续识别或保存。核心步骤是:边缘检测 → 轮廓近似 → 找到四边形 → 透视变换。
1. 导入库和辅助函数
import numpy as np
import cv2
def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitkey(1)- 导入 numpy 和 cv2。
- 定义
cv_show函数,用于快速显示图像,waitkey(1)表示显示 1 毫秒,适合在循环中实时刷新。
2. 坐标排序函数 order_points
def order_points(pts):
# 一共4个坐标点
rect = np.zeros((4, 2), dtype="float32") # 用来存储排序之后的坐标位置
# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下
s = pts.sum(axis=1) # 对pts矩阵的每一行进行求和操作。(x+y)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
diff = np.diff(pts, axis=1) # 对pts矩阵的每一行进行求差操作。(y-x)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rect这个函数的作用是把随意输入的四个点按“左上、右上、右下、左下”的顺序排好。
s = pts.sum(axis=1):计算每个点的 x+y,和最小的点就是左上角(因为 x 和 y 都小),和最大的点就是右下角。diff = np.diff(pts, axis=1):计算每个点的 y‑x,差最小的点(即 y 小 x 大)是右上角,差最大的点(y 大 x 小)是左下角。- 这样排序后,后面做透视变换时四个点就能和标准矩形的四个角一一对应。
3. 透视变换函数 four_point_transform
def four_point_transform(image, pts):
# 获取输入坐标点
rect = order_points(pts)
(tl, tr, br, bl) = rect
# 计算输入的w和h值
widtha = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
widthb = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
maxwidth = max(int(widtha), int(widthb))
heighta = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightb = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 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, dsize=(maxwidth, maxheight))
# 返回变换后结果
return warped
- 计算文档实际宽度:取底边长度和顶边长度的最大值,因为透视可能导致两边不一样长。
- 计算实际高度:取左边长度和右边长度的最大值。
- 定义目标矩形的四个角点,左上角为原点,宽为
maxwidth,高为maxheight。 cv2.getperspectivetransform计算变换矩阵,cv2.warpperspective执行透视变换,把原图中的四边形区域拉伸成矩形。
4. 打开摄像头并循环读取
cap = cv2.videocapture(0) # 确保摄像头是可以启动的状态。
if not cap.isopened(): # 打开失败
print("cannot open camera")
exit()
while true:
flag = 0 # 用于标识 当前是否检测到文档
ret, image = cap.read() # 如果正确读取帧,ret为true
image=cv2.flip(image, 1) #画面反转
orig = image.copy()
if not ret: # 读取失败,则退出循环
print("不能读取摄像头")
break
cv_show("image", image)
- 打开默认摄像头(索引 0)。
- 循环读取每一帧,
cv2.flip(image, 1)水平翻转,让画面像照镜子一样,更自然。 orig保存一份原始图像的副本,后面做透视变换用原图。flag用来标记当前帧是否检测到文档,初始为 0。
5. 预处理:灰度、高斯模糊、边缘检测
gray = cv2.cvtcolor(image, cv2.color_bgr2gray)
gray = cv2.gaussianblur(gray, ksize=(5, 5), sigmax=0)
edged = cv2.canny(gray, threshold1=15, threshold2=45)
cv_show('1', edged)- 转灰度图,减少计算量。
- 高斯模糊去噪,让边缘更平滑。
- canny 边缘检测,两个阈值 15 和 45,低于 15 的不是边缘,高于 45 的肯定是边缘,之间的看连通性。这里阈值较低,能检测到较弱的边缘。
6. 查找轮廓并排序
cnts = cv2.findcontours(edged, cv2.retr_external, cv2.chain_approx_simple)[-2]
cnts = sorted(cnts, key=cv2.contourarea, reverse=true)[:3]
image_contours = cv2.drawcontours(image, cnts, -1, color=(0, 255, 0), thickness=2)
cv_show("image_contours", image_contours)- 查找外部轮廓。
- 按轮廓面积从大到小排序,取前三个最大的轮廓(因为文档通常是画面中最大的四边形)。
- 把前三个轮廓画出来,方便调试观察。
7. 遍历轮廓,找到四边形文档
for c in cnts:
# 计算轮廓近似
peri = cv2.arclength(c, closed=true) #计算轮廓的周长
# c表示输入的点集
# epsilon表示从原始轮廓到近似轮廓的最大距离,它是一个准确度参数
# true表示封闭的
approx = cv2.approxpolydp(c, 0.05 * peri, closed=true) # 轮廓近似
area = cv2.contourarea(approx)
# 4个点的时候就拿出来
if area > 30000 and len(approx) == 4: #20000
screencnt = approx
flag = 1
print(peri, area)
print('检测到文档')
break- 计算轮廓周长
peri。 cv2.approxpolydp对轮廓进行多边形逼近,0.05 * peri表示近似精度,值越小越接近原轮廓,值越大越简化。这里希望把轮廓近似成四边形。- 如果近似后的多边形有 4 个顶点,并且面积大于 30000(过滤小物体),就认为找到了文档,记录下四个顶点,设置
flag=1并跳出循环。
8. 如果检测到文档,进行透视变换和二值化
if flag == 1:
# 展示结果
# print("step 2: 获取轮廓")
image_contours = cv2.drawcontours(image, contours=[screencnt], contouridx=0, color=(0, 255, 0), thickness=2)
cv_show("image", image_contours)
# 透视变换
warped = four_point_transform(orig, screencnt.reshape(4, 2))
cv_show("warped", warped)
# 二值处理
warped = cv2.cvtcolor(warped, cv2.color_bgr2gray)
ref = cv2.threshold(warped,135, 255, cv2.thresh_binary)[1]
# ref = cv2.threshold(warped, 0, 255, cv2.thresh_binary | cv2.thresh_otsu)[1]
cv_show("ref", ref)
if cv2.waitkey(20)==27:
break- 在原图上画出找到的四边形。
- 调用
four_point_transform,传入原始图像和四个顶点(reshape(4,2)把形状从(4,1,2)变成(4,2)),得到矫正后的文档图像。 - 转灰度,然后二值化:阈值 135,大于 135 变为 255(白色),小于变为 0(黑色)。这样文档就变成黑白分明,类似扫描件。
9. 释放资源
cap.release() cv2.destroyallwindows()
- 释放摄像头和关闭所有窗口。
小结:这个例子完整演示了从摄像头采集图像、边缘检测、轮廓逼近到透视变换的流程,是实现文档扫描的基础。实际应用中还可以加入自动检测阈值、去除阴影等优化。
二、图像风格迁移:用神经网络把照片变成卡通画
第二个例子使用一个已经训练好的风格迁移模型(candy.t7)对输入图片进行处理,生成卡通风格的图像。opencv 的 dnn 模块可以加载多种深度学习模型,使用起来很方便。
1. 读取并显示输入图像
import cv2
image = cv2.imread('haimianbaobao.png')
cv2.imshow('yuan tu', image)
cv2.waitkey(0)- 读取一张图片(这里是海绵宝宝),显示原图。按任意键继续。
2. 图像预处理 blobfromimage
(h, w) = image.shape[:2] blob = cv2.dnn.blobfromimage(image, scalefactor=1, size=(w, h), mean=(0, 0, 0), swaprb=false, crop=false)
- 深度学习模型通常要求输入固定尺寸的“blob”(四维数组:batch, channels, height, width)。
cv2.dnn.blobfromimage的作用是把 opencv 图像转换成 blob。主要参数:scalefactor=1:不缩放像素值。size=(w, h):这里直接用了原图尺寸,但很多模型要求固定尺寸(比如 224×224),使用时要看模型说明。此处因为模型是 torch 的,可能对任意尺寸兼容。mean=(0,0,0):不减去均值。swaprb=false:不交换 bgr 到 rgb,因为模型训练时可能用的就是 bgr。
- 最终得到一个 blob,形状是
(1, 3, h, w)。
3. 加载模型并前向传播
net = cv2.dnn.readnet(r'model\candy.t7') net.setinput(blob) out = net.forward()
cv2.dnn.readnet加载模型文件。candy.t7是一个 torch 格式的模型,能把图片转换为糖果/卡通风格。setinput设置输入 blob。forward执行前向传播,输出结果。输出也是一个四维数组,形状通常是(1, c, h, w),c 是通道数(彩色 图一般为 3)。
4. 输出后处理并显示
# ======输出处理=======
# 重塑形状(忽略第1维),4维变3维
# 调整输出out的形状,模型推理输出out是四维bchw形式的,调整为三维chw形式
out_new = out.reshape(out.shape[1], out.shape[2], out.shape[3])
# 对输入的数组(或图像)进行归一化处理,使其数值范围在指定的范围内
cv2.normalize(out_new, out_new, norm_type=cv2.norm_minmax)
# 转置输出结果的维度
result = out_new.transpose(1, 2, 0)
# 显示转换后的图像
cv2.imshow('stylized image', result)
cv2.waitkey(0)
cv2.destroyallwindows()- 输出
out是四维(1, c, h, w),用reshape去掉第一维 batch,变成(c, h, w)。 cv2.normalize把像素值范围缩放到 01 或 0255(取决于数据类型),这里使用norm_minmax线性归一化。- 转置维度从
(c, h, w)变成(h, w, c),这是 opencv 显示图像需要的格式(高、宽、通道)。 - 显示风格化后的图片。
小结:这个例子展示了 opencv 的 dnn 模块如何加载预训练模型并做推理,核心就三步:blobfromimage 预处理、forward 前向传播、后处理转回图像。更换不同的模型文件就能实现不同的风格效果。
以上两个例子覆盖了 opencv 中传统图像处理和深度学习模块的常见用法,代码都不复杂,跑通之后可以再根据自己的需求调整参数或模型。希望对你有所帮助,有问题欢迎在评论区留言。
以上就是python基于opencv实现文档扫描与图像风格迁移的详细内容,更多关于python opencv文档扫描与图像风格迁移的资料请关注代码网其它相关文章!
发表评论