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格式模型文件,确保文件路径正确

系列直达

相关推荐
AI的探索之旅3 小时前
97 个 OpenCV 实例(二十九):三相机联合标定,把三只眼睛绑在一起
人工智能·opencv·计算机视觉
He BianGu16 小时前
【WPF-VisionMaster】机器视觉通用平台V5.0版本发行说明
opencv·c#·wpf·halcon·机器视觉·visionmaster
hhzz17 小时前
【OpenCV 入门到精通 10】视频分析与光流跟踪:背景减除与运动检测
人工智能·python·opencv·性能优化
辰辉创聚1 天前
重组蛋白聚集的形成机制及其对蛋白活性的影响
opencv·时序数据库·visual studio·重组蛋白·蛋白聚集·蛋白活性·蛋白聚集体
hhzz1 天前
【OpenCV 入门到精通 11】机器学习应用:KNN、SVM 与 K-Means 实战
人工智能·python·深度学习·opencv
qq7422349841 天前
OpenCV 之外的另一半工具箱:Supervision 视觉工程实战
人工智能·opencv·计算机视觉
hhzz3 天前
OpenCV 入门到精通 09】特征检测与匹配:从 Harris 到 ORB 图像配准
人工智能·opencv·计算机视觉·开源·交互
Tp_jh5 天前
AMD 395本地部署Qwen3.8-Flash-Next实测,端侧AGI时代也要来了
图像处理·人工智能·opencv·语言模型·自然语言处理·千问
yyk333245 天前
计算机视觉OpenCV中的FisherFace人脸识别
人工智能·opencv·计算机视觉
棣廷5 天前
初识OpenCV——Dlib人脸检测、关键点定位与表情识别
opencv·目标检测·机器学习