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就能更好地抓住这个不变特征。
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就是操作这些矩阵的工具。下一章我们会深入图像预处理——滤波、边缘检测、二值化,这些都是车道线检测的「基本功」。
可可精品资料库