OpenCV实战:文档扫描与图像风格迁移

最近又捣鼓了两个 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

python 复制代码
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

python 复制代码
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. 打开摄像头并循环读取

python 复制代码
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. 遍历轮廓,找到四边形文档

python 复制代码
    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. 如果检测到文档,进行透视变换和二值化

python 复制代码
    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. 输出后处理并显示

python 复制代码
# ======输出处理=======
# 重塑形状(忽略第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 中传统图像处理和深度学习模块的常见用法,代码都不复杂,跑通之后可以再根据自己的需求调整参数或模型。希望对你有所帮助,有问题欢迎在评论区留言。

相关推荐
智嵌研习社12 分钟前
从 Prompt 到工程化技能包:AI Skills 标准与 Continue 落地
人工智能·prompt·skill
淬炼之火12 分钟前
笔记:Visually-Guided Policy Optimization for Multimodal Reasoning
人工智能·笔记·算法·机器学习·语言模型·自然语言处理
benchmark_cc14 分钟前
数据 API 稳定性为什么会影响量化策略?从数据获取到信号执行的完整分析
开发语言·python·数据分析·量化·股票数据·quantdash·量化数据源
STLearner18 分钟前
KDD 2026 | (2月轮)时空数据(Spatial-Temporal)论文总结时空(交通)预测,轨迹数据挖掘(表示,生成)
论文阅读·人工智能·python·深度学习·学习·机器学习·数据挖掘
学习星球18 分钟前
空天地一体化网络(NTN)深度解析:从Starlink D2C到3GPP NTN,卫星直连手机是如何实现的?
网络·人工智能·算法·智能手机·php
Liudef0626 分钟前
腾讯混元Hunyuan3D-Part:重新定义3D部件生成的革命性架构
人工智能·3d·架构·腾讯混元hunyuan3d
zhangjin112026 分钟前
NLP英文分词
人工智能·自然语言处理
axinawang27 分钟前
ddddocr--识别验证码
python