OpenCV车道线检测实战(一):透视变换技术详解

车道线检测是自动驾驶和辅助驾驶系统中的基础技术,它能够帮助车辆识别道路边界,实现车道保持和车道偏离预警等功能。在这个系列文章中,我们将通过四个部分完整实现一个车道线检测系统。今天先讲解第一部分——透视变换技术

一、透视变换在车道线检测中的作用

1.1 为什么需要透视变换?

在普通相机拍摄的图像中,由于透视效应,原本平行的车道线在图像中会交汇于一点(消失点)。这种透视效果会给车道线检测带来困难,因为:

  1. 车道线不是平行线,难以用统一的数学模型处理
  2. 距离相机越远,车道线像素越密集,难以准确提取

透视变换可以将图像从前视视角转换为鸟瞰视角(俯视图),这样:

  1. 原本交汇的车道线变成平行线
  2. 车道线的曲率变化更加直观
  3. 便于后续的车道线提取和拟合

二、透视变换实现步骤详解

下面我们将一步步实现透视变换功能,让你清楚地了解每个步骤的作用。
请添加图片描述

2.1 获取图像尺寸

首先,我们需要知道输入图像的尺寸,以便正确设置变换参数:

def perspective_transform(image):
    # 获取图像的宽度和高度
    # image.shape[1] 是宽度,image.shape[0] 是高度
    image_size = (image.shape[1], image.shape[0])

代码解释:

  • image.shape返回一个三元组(高度, 宽度, 通道数)
  • image.shape[1]获取图像的宽度
  • image.shape[0]获取图像的高度
  • 我们将尺寸存储在image_size变量中,格式为(宽度, 高度)

2.2 定义源点(原始图像中的梯形区域)

源点是原始图像中我们想要变换的梯形区域的四个顶点。这个梯形应该覆盖我们感兴趣的道路区域:
在这里插入图片描述

    src = np.float32(
        [[80, image_size[1]],                      # 左下点
         [450, image_size[1]],                     # 右下点
         [image_size[0] // 2 + 40, image_size[1] // 2 - 20],  # 右上点
         [image_size[0] // 2 - 40, image_size[1] // 2 - 20]]  # 左上点
    )

代码解释:

  • np.float32():创建32位浮点数数组,OpenCV的几何变换函数需要这种数据类型
  • 左下点 [80, image_size[1]]:图像底部左侧的车道线起点
  • 右下点 [450, image_size[1]]:图像底部右侧的车道线起点
  • 右上点 [image_size[0]//2+40, image_size[1]//2-20]:图像中上部右侧的车道线终点
  • 左上点 [image_size[0]//2-40, image_size[1]//2-20]:图像中上部左侧的车道线终点

这四个点构成了一个梯形区域,代表我们关心的道路部分。

2.3 定义目标点(变换后的矩形区域)

目标点是鸟瞰图中对应的矩形区域的四个顶点:

    dst = np.float32(
        [[image_size[0] / 4, image_size[1]],       # 左下点
         [image_size[0] * 3 / 4, image_size[1]],   # 右下点
         [image_size[0] * 3 / 4, 0],               # 右上点
         [image_size[0] / 4, 0]]                   # 左上点
    )

代码解释:

  • 左下点 [image_size[0]/4, image_size[1]]:变换后图像的左下角,x坐标为图像宽度的1/4
  • 右下点 [image_size[0]*3/4, image_size[1]]:变换后图像的右下角,x坐标为图像宽度的3/4
  • 右上点 [image_size[0]*3/4, 0]:变换后图像的右上角,y坐标为0(图像顶部)
  • 左上点 [image_size[0]/4, 0]:变换后图像的左上角

这样定义的目标点确保变换后的车道线是平行的垂直线。

2.4 计算透视变换矩阵

有了源点和目标点,我们可以计算透视变换矩阵:

    # 获取透视变换矩阵
    M = cv2.getPerspectiveTransform(src, dst)
    
    # 获取逆透视变换的矩阵
    minv = cv2.getPerspectiveTransform(dst, src)

代码解释:

  • cv2.getPerspectiveTransform(src, dst):计算从源点到目标点的透视变换矩阵
  • cv2.getPerspectiveTransform(dst, src):计算从目标点到源点的逆透视变换矩阵
  • 为什么需要两个矩阵?
    • M:将原始图像变换为鸟瞰图(用于车道线检测)
    • minv:将鸟瞰图变换回原始视角(用于将检测结果叠加到原始图像)
      在这里插入图片描述

2.5 执行透视变换

使用计算得到的变换矩阵对图像进行透视变换:

    # 调用函数进行透视变换
    image_warp = cv2.warpPerspective(image, M, image_size, flags=cv2.INTER_LINEAR)
    
    return image_warp, minv

代码解释:

  • cv2.warpPerspective():执行透视变换
  • 参数说明:
    • image:输入图像
    • M:3×3的透视变换矩阵
    • image_size:输出图像的大小(宽度, 高度)
    • flags=cv2.INTER_LINEAR:使用线性插值方法
  • 函数返回变换后的图像和逆变换矩阵

三、完整透视变换代码

将上述步骤整合,得到完整的透视变换函数:

import cv2
import numpy as np

def perspective_transform(image):
    """
    对输入图像进行透视变换,将前视图转换为鸟瞰图
    
    参数:
        image: 输入图像(BGR格式)
        
    返回:
        image_warp: 透视变换后的图像(鸟瞰图)
        minv: 逆透视变换矩阵
    """
    # 获取图像尺寸:宽度和高度
    image_size = (image.shape[1], image.shape[0])
    
    # 定义源点(原始图像中的梯形区域)
    src = np.float32(
        [[80, image_size[1]],                      # 左下点
         [450, image_size[1]],                     # 右下点
         [image_size[0] // 2 + 40, image_size[1] // 2 - 20],  # 右上点
         [image_size[0] // 2 - 40, image_size[1] // 2 - 20]]  # 左上点
    )
    
    # 定义目标点(变换后的矩形区域)
    dst = np.float32(
        [[image_size[0] / 4, image_size[1]],       # 左下点
         [image_size[0] * 3 / 4, image_size[1]],   # 右下点
         [image_size[0] * 3 / 4, 0],               # 右上点
         [image_size[0] / 4, 0]]                   # 左上点
    )
    
    # 获取透视变换矩阵
    M = cv2.getPerspectiveTransform(src, dst)
    
    # 获取逆透视变换矩阵
    minv = cv2.getPerspectiveTransform(dst, src)
    
    # 执行透视变换
    image_warp = cv2.warpPerspective(image, M, image_size, flags=cv2.INTER_LINEAR)
    
    return image_warp, minv

# 测试代码
if __name__ == "__main__":
    # 读取测试图像
    image = cv2.imread('./test_image.png')
    
    if image is not None:
        # 执行透视变换
        warped_image, inverse_matrix = perspective_transform(image)
        
        # 显示结果
        cv2.imshow('原始图像', image)
        cv2.imshow('鸟瞰图', warped_image)
        cv2.waitKey(0)
        cv2.destroyAllWindows()
    else:
        print("无法读取图像,请检查文件路径")

四、关键OpenCV函数详解

4.1 cv2.getPerspectiveTransform()

cv2.getPerspectiveTransform(src, dst)

功能:计算两个四边形之间的透视变换矩阵

参数

  • src:源图像中四边形顶点的坐标,形状为(4, 2)的浮点数数组
  • dst:目标图像中四边形顶点的坐标,形状为(4, 2)的浮点数数组

返回值

  • 一个3×3的透视变换矩阵

使用注意事项

  1. 源点和目标点的顺序必须一致(都是顺时针或都是逆时针)
  2. 至少需要4个点对来计算变换矩阵
  3. 返回的矩阵是3×3的,因为透视变换在齐次坐标下表示

4.2 cv2.warpPerspective()

cv2.warpPerspective(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])

功能:对图像进行透视变换

参数说明

  • src:输入图像,可以是单通道或多通道
  • M:3×3的透视变换矩阵
  • dsize:输出图像的大小,格式为(宽度, 高度)
  • flags:插值方法,常用的有:
    • cv2.INTER_LINEAR:线性插值(默认)
    • cv2.INTER_NEAREST:最近邻插值
    • cv2.INTER_CUBIC:三次样条插值
  • borderMode:边界像素处理方式
  • borderValue:当borderModecv2.BORDER_CONSTANT时的边界填充值

返回值

  • 变换后的图像

工作流程

  1. 对于输出图像中的每个像素点(x', y')
  2. 计算其在输入图像中的对应位置(x, y)
  3. 使用插值方法计算(x, y)处的像素值
  4. 将该像素值赋给输出图像的(x', y')位置

五、透视变换的数学原理

可以看up之前写的
计算机视觉处理(OpenCV基础教学(十二):图像透视变换基础)

5.1 基本概念

透视变换是射影几何中的一种变换,用于描述三维空间中的物体在二维平面上的投影。在图像处理中,它可以将一个视角下的图像变换到另一个视角。

5.2 变换公式

透视变换可以用一个3×3的矩阵表示:

| x' |   | a11 a12 a13 |   | x |
| y' | = | a21 a22 a23 | * | y |
| w' |   | a31 a32  1  |   | 1 |

其中:

  • (x, y)是原始图像中的点(齐次坐标(x, y, 1)
  • (x', y', w')是变换后的齐次坐标
  • 实际图像坐标:(x'/w', y'/w')

5.3 计算变换矩阵

给定4个点对(xi, yi) ↔ (xi', yi'),可以建立8个方程:

xi' = (a11*xi + a12*yi + a13) / (a31*xi + a32*yi + 1)
yi' = (a21*xi + a22*yi + a23) / (a31*xi + a32*yi + 1)

通过解这个方程组,可以求出8个未知数a11, a12, a13, a21, a22, a23, a31, a32

总结

透视变换是车道线检测系统中的关键预处理步骤,它将前视道路图像转换为鸟瞰图,为后续的车道线提取和拟合奠定基础。通过本文的学习,你应该掌握:

  1. 透视变换的基本原理:理解为什么需要将前视图转换为鸟瞰图
  2. 实现步骤:从获取图像尺寸到执行变换的完整流程
  3. 关键函数用法cv2.getPerspectiveTransform()cv2.warpPerspective()的使用方法
  4. 代码实现:能够独立实现透视变换功能

在实际的车道线检测系统中,透视变换参数需要根据具体的相机安装位置和道路情况进行调整。一个好的透视变换应该能够将车道线转换为近似平行的直线,便于后续处理。

下一篇预告:在下一篇文章中,我们将讲解如何从鸟瞰图中提取车道线,包括基于梯度的方法和基于颜色的方法。我们将详细介绍Sobel算子、颜色空间转换等关键技术,敬请期待!

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐