2. 图像处理基础(上):图像在计算机中的表示与OpenCV入门

各位同学,欢迎来到第二章。说实话,很多初学者一上来就急着调模型、跑检测,结果连图像在内存里长什么样都没搞明白。我当年也犯过这个错——调了三天参数,最后发现是图像通道顺序搞反了。嗯,咱们今天就把这些基础打扎实。

本章核心:搞懂图像的「数字本质」,学会用OpenCV跟图像「对话」。

2.1 图像在计算机中的表示

你想想看,计算机不认识「红色」、「蓝色」这些词。它只认识数字。一张图像,说白了就是一个巨大的数字矩阵。

2.1.1 像素——图像的最小单元

像素就是图像上的一个小点。一张1920×1080的图像,就是由1920列、1080行,总共约207万个像素点组成的。每个像素点存储一个数值,这个数值代表亮度。

我个人习惯把像素想象成「格子纸上的小方格」。每个格子涂上不同深浅的灰色,拼在一起就是一张黑白照片。

2.1.2 通道——彩色图像的「三原色」

彩色图像就复杂一点了。每个像素不再是一个数,而是三个数——分别代表红色(R)、绿色(G)、蓝色(B)的亮度。这就是我们常说的RGB色彩空间。

举个例子:一个纯红色的像素,在计算机里存的是(255, 0, 0)。纯白色是(255, 255, 255),纯黑色是(0, 0, 0)。

颜色 R值 G值 B值
红色 255 0 0
绿色 0 255 0
蓝色 0 0 255
白色 255 255 255
黑色 0 0 0

所以一张彩色图像在内存里,其实是三个二维矩阵叠在一起。OpenCV里默认的通道顺序是BGR,不是RGB。这一点坑了我很久——我第一次用OpenCV显示图像,发现红色和蓝色是反的。后来才知道,OpenCV读图像默认用BGR顺序。

避坑指南:OpenCV读取彩色图像时,通道顺序是BGR!如果你用matplotlib显示,记得用cv2.cvtColor(img, cv2.COLOR_BGR2RGB)转换一下。

2.1.3 常见的色彩空间

除了RGB,车道线检测里最常用的是灰度图和HSV色彩空间。

  • 灰度图:只有一个通道,每个像素0~255,0是纯黑,255是纯白。车道线检测中,我们经常先把彩色图转成灰度图,再处理。
  • HSV:色调(Hue)、饱和度(Saturation)、明度(Value)。这个色彩空间对光照变化更鲁棒。我在做车道线颜色筛选时,用HSV比RGB好使很多。

为什么会这样?因为RGB三个通道跟光照强度强相关,而HSV把颜色信息(H)和亮度信息(V)分开了。你想想看,白天和傍晚的车道线,亮度变化很大,但颜色(白色或黄色)基本不变。用HSV就能更好地抓住这个不变特征。

图像在计算机中的表示 像素 (Pixel) 最小单元,存储亮度值 通道 (Channel) BGR / 灰度 / HSV 色彩空间 RGB / HSV / 灰度 读取 imread() 显示 imshow() 保存 imwrite() ROI 感兴趣区域提取

2.2 OpenCV基础操作:读取、显示、保存

OpenCV是计算机视觉界的「瑞士军刀」。咱们先学三个最基础的操作:读图、看图、存图。

2.2.1 读取图像——cv2.imread()

代码很简单,但参数有讲究:

import cv2

# 读取彩色图像(默认BGR)
img_color = cv2.imread('road.jpg')

# 读取灰度图像
img_gray = cv2.imread('road.jpg', cv2.IMREAD_GRAYSCALE)

# 读取原始图像(包含alpha通道)
img_unchanged = cv2.imread('road.jpg', cv2.IMREAD_UNCHANGED)

第二个参数决定了图像怎么被读进来。我个人习惯在车道线检测中,先用彩色图读进来,需要时再转灰度。因为有些预处理步骤(比如颜色筛选)需要彩色信息。

小技巧:如果图像路径不对,imread()不会报错,而是返回None。所以读图后最好加一句if img is None: print('图像读取失败')。我曾经因为这个bug排查了半小时。

2.2.2 显示图像——cv2.imshow()

cv2.imshow('Window Title', img_color)
cv2.waitKey(0)  # 等待按键
cv2.destroyAllWindows()

waitKey(0)的意思是无限等待按键。如果你想让窗口自动关闭,可以设置一个时间,比如waitKey(3000)表示等待3秒后自动关闭。

嗯,这里要注意:imshow()显示的窗口大小可能跟图像实际尺寸不一样。你可以用cv2.namedWindow('win', cv2.WINDOW_NORMAL)来创建可调整大小的窗口。

2.2.3 保存图像——cv2.imwrite()

cv2.imwrite('output.jpg', img_color)
cv2.imwrite('output.png', img_color)  # PNG支持无损压缩

保存格式由文件扩展名决定。我一般用PNG保存中间结果,因为无损压缩不会丢失细节。JPG适合最终输出,文件小。

2.3 图像属性与ROI提取

2.3.1 图像的基本属性

一张图像在OpenCV里其实就是一个NumPy数组。我们可以通过数组的shape属性获取图像尺寸:

import cv2

img = cv2.imread('road.jpg')
print(img.shape)   # 输出:(1080, 1920, 3)
print(img.dtype)   # 输出:uint8
print(img.size)    # 输出:1080*1920*3 = 6220800
  • shape:(高度, 宽度, 通道数)。灰度图只有两个值:(高度, 宽度)。
  • dtype:数据类型,通常是uint8(0~255)。
  • size:像素总数 = 高度 × 宽度 × 通道数。

你想想看,一张1080p的彩色图像,内存里占了6MB多。如果做实时处理,这个数据量可不小。

2.3.2 ROI提取——只关心「那一块」

车道线检测中,我们不需要处理整张图像。天空、路边的树木都是干扰。我们只关心路面那一块区域。这就是ROI(Region of Interest,感兴趣区域)。

ROI提取说白了就是数组切片:

# 假设图像尺寸是 1080x1920
# 我们只取下半部分(路面区域)
roi = img[540:1080, 0:1920]  # 行范围:540~1080,列范围:0~1920

# 更精细的:只取中间车道区域
lane_roi = img[700:1000, 300:1600]

为什么从540开始?因为上半部分通常是天空和远处,对车道线检测帮助不大。我在实际项目中,会根据摄像头安装位置调整ROI范围。有时候甚至用多边形来定义ROI,而不是简单的矩形。

核心思路:ROI不是随便切的。你要根据场景特点,只保留包含车道线的区域。这样能大幅减少计算量,提高检测精度。

2.3.3 实战小例子:提取车道线区域

假设我们有一张道路图像,想提取中间偏下的矩形区域作为ROI:

import cv2

img = cv2.imread('road.jpg')
h, w = img.shape[:2]

# 定义ROI:从图像高度60%处开始,到90%处结束
# 宽度取中间60%
roi_start_y = int(h * 0.6)
roi_end_y = int(h * 0.9)
roi_start_x = int(w * 0.2)
roi_end_x = int(w * 0.8)

roi = img[roi_start_y:roi_end_y, roi_start_x:roi_end_x]

# 显示ROI
cv2.imshow('Lane ROI', roi)
cv2.waitKey(0)
cv2.destroyAllWindows()

这段代码在实际项目中很常用。你可以根据摄像头视角微调这些比例。比如摄像头装得高,ROI可以往下移;装得低,ROI可以往上移。

个人经验:ROI的边界不要切得太死。留一点余量,防止车道线刚好在边界上被切掉。我一般会在计算出的边界上各加20~30像素的缓冲。

好了,这一章的内容就到这里。图像在计算机里就是数字矩阵,OpenCV就是操作这些矩阵的工具。下一章我们会深入图像预处理——滤波、边缘检测、二值化,这些都是车道线检测的「基本功」。


可可精品资料库