OpenCV入门(五):图像拼接、背景建模、光流估计与DNN风格迁移

OpenCV入门(五):图像拼接、背景建模、光流估计与DNN风格迁移

前言 :本文是"OpenCV入门"系列的第五篇。前四篇我们学习了图像基础操作、图像预处理、轮廓检测与模板匹配、图像金字塔与透视转换。本篇我们将进入五个更高级的实战应用:图像拼接 ------将多张重叠图像合成为全景图;背景建模与运动检测 ------从视频中分离运动目标;光流估计 ------追踪像素的运动轨迹;摄像头文档扫描 ------实时检测并矫正文档;DNN风格迁移------利用深度学习模型实现图像艺术化。这些技术涵盖了计算机视觉的核心应用场景。

目录

  • 一、图像拼接
  • 二、背景建模与运动检测
  • 三、光流估计
  • 四、摄像头文档扫描
  • 五、DNN风格迁移
  • 六、总结

一、图像拼接

1.1 什么是图像拼接?

图像拼接是指将多张具有重叠区域 的图像合成为一张全景图像的技术,广泛应用于手机全景拍照、无人机航拍、VR场景构建等领域。

1.2 核心技术流程

复制代码
读取图片 → SIFT特征提取 → 特征匹配 → 计算单应性矩阵 → 透视变换 → 图像融合

1.3 完整代码

python 复制代码
import cv2
import numpy as np
import sys

def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)

def detect_and_describe(image):
    """提取图像的 SIFT 特征点和描述子"""
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    sift = cv2.SIFT_create()
    kps, des = sift.detectAndCompute(gray, None)
    kps_float = np.float32([kp.pt for kp in kps])
    return kps, kps_float, des

# ====================读取待拼接图片==========================
image_a = cv2.imread("left.jpg")
image_b = cv2.imread("right.jpg")
cv2.imshow("image_a", image_a)
cv2.imshow("image_b", image_b)

# ====================提取特征==========================
kps_a, kps_float_a, des_a = detect_and_describe(image_a)
kps_b, kps_float_b, des_b = detect_and_describe(image_b)

# ====================特征匹配==========================
bf = cv2.BFMatcher()
raw_matches = bf.knnMatch(des_b, des_a, k=2)

good = []
matches = []
for m in raw_matches:
    if len(m) == 2 and m[0].distance < 0.65 * m[1].distance:
        good.append(m)
        matches.append((m[0].queryIdx, m[0].trainIdx))

print(f"找到 {len(good)} 个好的匹配点")

# ====================计算单应性矩阵==========================
if len(matches) > 4:
    pts_b = np.float32([kps_float_b[i] for (i, _) in matches])
    pts_a = np.float32([kps_float_a[j] for (_, j) in matches])
    H, mask = cv2.findHomography(pts_b, pts_a, cv2.RANSAC, 10)
else:
    print("图片未找到4个以上的匹配点")
    sys.exit()

# ====================透视变换与拼接==========================
result = cv2.warpPerspective(
    image_b,
    H,
    (image_b.shape[1] + image_a.shape[1], image_b.shape[0])
)

# 将左图放入拼接结果
result[0:image_a.shape[0], 0:image_a.shape[1]] = image_a

cv2.imwrite("panorama.jpg", result)
cv_show("panorama", result)

1.4 关键步骤说明

步骤 说明
SIFT特征提取 提取图像中具有尺度不变性和旋转不变性的关键点
BFMatcher匹配 暴力匹配两张图片的所有特征点
Lowe's比率测试 通过最近邻与次近邻距离之比筛选可靠匹配点
findHomography 使用RANSAC算法剔除误匹配,计算单应性矩阵
warpPerspective 对右图进行透视变换,拼接到左图坐标系中

二、背景建模与运动检测

2.1 什么是背景建模?

背景建模是视频分析的核心技术,用于从视频序列中分离前景(运动目标)和背景(静止场景),广泛应用于安防监控、行人检测、车辆跟踪、交通流量分析等场景。

2.2 MOG2背景减除

MOG2(Mixture of Gaussians 2)是一种基于高斯混合模型的自适应背景建模算法。它对每个像素建立多个高斯模型,能够适应光照变化、树叶晃动等动态背景。

2.3 完整代码

python 复制代码
import cv2

cap = cv2.VideoCapture("video.mp4")

# 创建形态学内核
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3))

# 创建 MOG2 背景减除器
fgbg = cv2.createBackgroundSubtractorMOG2()

while True:
    ret, frame = cap.read()
    if not ret:
        break

    # 背景减除,得到前景掩膜
    fgmask = fgbg.apply(frame)

    # 开运算去噪(先腐蚀后膨胀)
    fgmask_new = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)

    # 查找轮廓
    contours = cv2.findContours(fgmask_new, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]

    for c in contours:
        peri = cv2.arcLength(c, True)
        if peri > 188:  # 过滤掉面积过小的噪点轮廓
            x, y, w, h = cv2.boundingRect(c)
            cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

    cv2.imshow("result", frame)

    if cv2.waitKey(60) == 27:
        break

cap.release()
cv2.destroyAllWindows()

2.4 核心步骤说明

步骤 说明
createBackgroundSubtractorMOG2 创建MOG2背景减除器,自动学习背景模型
apply 对每一帧应用背景减除,返回前景掩膜(白色为前景,黑色为背景)
MORPH_OPEN 开运算先腐蚀后膨胀,有效去除前景掩膜中的噪声点
findContours 查找运动目标的轮廓边界
boundingRect 计算轮廓的外接矩形,在原图上绘制检测框

三、光流估计

3.1 什么是光流?

光流是视频中物体运动的视觉表现形式,描述了像素从一帧到下一帧的移动方向和速度。光流信息可以反映物体的运动状态,广泛应用于动作识别、目标跟踪、视频压缩、机器人导航等领域。

3.2 Lucas-Kanade光流

Lucas-Kanade是一种稀疏光流算法,它假设在一个局部窗口内所有像素的运动一致,通过最小二乘法求解运动矢量。该算法只计算特征点的运动,计算量小,适合实时应用。

3.3 完整代码

python 复制代码
import cv2
import numpy as np

cap = cv2.VideoCapture("test.avi")

# 随机生成颜色,用于区分不同点的轨迹
color = np.random.randint(0, 255, (100, 3))

# 读取第一帧
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)

# 角点检测参数
feature_params = dict(maxCorners=100, qualityLevel=0.3, minDistance=7)

# 光流参数
lk_params = dict(winSize=(15, 15), maxLevel=2)

# 检测初始角点作为跟踪特征点
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)

mask = np.zeros_like(old_frame)

while True:
    ret, frame = cap.read()
    if not ret:
        break

    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # 计算光流,跟踪特征点
    p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)

    # 选取跟踪成功的点
    good_new = p1[st == 1]
    good_old = p0[st == 1]

    # 绘制运动轨迹
    for i, (new, old) in enumerate(zip(good_new, good_old)):
        a, b = new.ravel()
        c, d = old.ravel()
        mask = cv2.line(mask, (int(a), int(b)), (int(c), int(d)), color[i].tolist(), 2)

    # 将轨迹掩膜叠加到当前帧
    img = cv2.add(frame, mask)
    cv2.imshow("frame", img)

    if cv2.waitKey(150) == 27:
        break

    # 更新旧帧和特征点
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1, 1, 2)

cap.release()
cv2.destroyAllWindows()

3.4 关键步骤说明

步骤 说明
goodFeaturesToTrack 使用Shi-Tomasi角点检测算法提取高质量特征点
calcOpticalFlowPyrLK 基于金字塔的Lucas-Kanade光流计算,跟踪特征点的位置变化
status 状态向量,1表示跟踪成功,0表示跟踪丢失
轨迹绘制 将每个特征点当前帧位置与上一帧位置连线,形成运动轨迹

四、摄像头文档扫描

4.1 什么是透视变换?

透视变换是将图像从一个视角投影到另一个视角的过程。在文档扫描场景中,它将倾斜拍摄的文档矫正为正面视角,是扫描全能王、证件识别等应用的核心技术。

4.2 完整代码

python 复制代码
import cv2
import numpy as np

def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(25)

def order_points(pts):
    """对四个角点排序:左上、右上、右下、左下"""
    rect = np.zeros((4, 2), dtype="float32")
    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]    # 左上(坐标和最小)
    rect[2] = pts[np.argmax(s)]    # 右下(坐标和最大)
    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)] # 右上(y-x最小)
    rect[3] = pts[np.argmax(diff)] # 左下(y-x最大)
    return rect

def four_point_transform(image, pts):
    """执行透视变换,将倾斜文档矫正为正面视图"""
    rect = order_points(pts)
    (tl, tr, br, bl) = rect

    # 计算目标宽度
    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, (maxWidth, maxHeight))
    return warped

# ====================打开摄像头==========================
cap = cv2.VideoCapture(0)

while True:
    flag = 0
    ret, image = cap.read()
    if not ret:
        break

    orig = image.copy()
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray, (5, 5), 0)
    edged = cv2.Canny(gray, 15, 45)

    cnts = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
    cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:3]

    for c in cnts:
        peri = cv2.arcLength(c, True)
        approx = cv2.approxPolyDP(c, 0.05 * peri, True)
        area = cv2.contourArea(approx)

        if area > 30000 and len(approx) == 4:
            screenCnt = approx
            flag = 1
            break

    if flag == 1:
        warped = four_point_transform(orig, screenCnt.reshape(4, 2))
        warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
        ref = cv2.threshold(warped, 135, 255, cv2.THRESH_BINARY)[1]
        cv2.imshow("scan_result", ref)

    if cv2.waitKey(150) == 27:
        break

cap.release()
cv2.destroyAllWindows()

五、DNN风格迁移

5.1 什么是风格迁移?

风格迁移是利用深度学习模型将一幅图像的艺术风格 应用到另一幅图像上的技术。OpenCV的dnn模块支持加载预训练的神经网络模型进行推理,无需安装深度学习框架即可实现风格迁移效果。

5.2 完整代码

python 复制代码
import cv2

# 读取输入图像
image = cv2.imread("input.jpg")

# 缩放图像便于预览
image = cv2.resize(image, None, fx=0.4, fy=0.4, interpolation=cv2.INTER_AREA)
cv2.imshow("original", image)
cv2.waitKey(0)

(h, w) = image.shape[:2]

# 创建Blob,将图像转换为模型输入格式
blob = cv2.dnn.blobFromImage(image, 1, (w, h), [0, 0, 0], False, crop=False)

# 加载预训练风格迁移模型
net = cv2.dnn.readNet("model/feathers.t7")
net.setInput(blob)

# 前向推理
out = net.forward()

# 后处理:调整维度并归一化到[0,1]范围
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("style_transfer", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

5.3 关键步骤说明

步骤 说明
blobFromImage 将图像转换为深度学习模型可接受的输入格式
readNet 加载预训练的.t7格式风格迁移模型
forward 执行模型前向推理,生成风格化图像
normalize 将输出归一化到0,1范围以便正确显示
transpose 调整维度顺序从(C,H,W)转为(H,W,C)

六、总结

核心函数速查

类别 函数 用途
特征提取 SIFT_create()detectAndCompute() 提取图像特征点
特征匹配 BFMatcher()knnMatch() 匹配特征点
单应性矩阵 findHomography() 计算图像变换矩阵
背景减除 createBackgroundSubtractorMOG2() 分离前景与背景
光流计算 calcOpticalFlowPyrLK() 计算像素运动矢量
角点检测 goodFeaturesToTrack() 检测高质量特征角点
透视变换 getPerspectiveTransform()warpPerspective() 矫正图像视角
DNN推理 dnn.readNet()forward() 加载模型并执行推理

注意事项

要点 说明
图像拼接 两张图片必须有足够重叠区域,否则匹配特征点数量不足
背景减除 需要连续视频流支持,单张图片无法建模背景
光流估计 依赖连续两帧图像,特征点数量直接影响跟踪效果
透视变换 需要准确的四个角点坐标,角点排序必须正确
DNN模型 需提前下载.t7格式模型文件,确保文件路径正确

系列直达

相关推荐
派大_星36 分钟前
OpenCV中的文档扫描与DNN图像处理
人工智能·opencv·计算机视觉
QT界面美化性能优化10 小时前
QT+AI:使用AI技术为QT应用程序赋能
c++·人工智能·qt·opencv·qt教程·qt6.3
AndrewHZ17 小时前
图像处理入门020 | 图像二值化:阈值分割基础 —— cv2.threshold 五种类型与自适应阈值铺垫
图像处理·opencv·文档扫描·阈值分割·图像二值化·trackbar·自适应阈值
AI的探索之旅1 天前
97 个 OpenCV 实例(十三):目标跟踪,CamShift 反向投影
人工智能·opencv·目标跟踪
Mr.Lu ‍2 天前
C++开发,使用openCV API对图片进行压缩
开发语言·c++·opencv
SamChan902 天前
Python+OpenCV检测PDF翻译后排版偏移:像素级格式一致性验证
python·opencv·ai·pdf·wpf
sali-tec2 天前
C# 基于OpenCv的视觉工作流-章106-差值追踪
图像处理·人工智能·opencv·算法·计算机视觉
磁场转动100万匹3 天前
OpenCV 答题卡识别判卷实战:从图像预处理到自动评分
人工智能·opencv·计算机视觉
阿图灵3 天前
OpenCV 绘图五件套:画圆、文本、线段、矩形与椭圆
图像处理·人工智能·python·opencv·计算机视觉·绘图