2、图像基础操作:像素是什么?图像坐标系、BGR颜色空间、读取/修改像素值、图像属性
好,咱们正式开始动手操作图像了。
这一节,我会带你搞清楚图像在计算机里到底长什么样。说白了,图像就是一堆数字。你看到的每一张图,在OpenCV眼里,就是一个巨大的数字矩阵。
2.1 像素是什么?
想象一下,你把一张照片无限放大,最后会看到什么?
对,是一个个小方格。每个小方格就是一个像素。它是图像的最小组成单元。
每个像素都有自己的颜色值。在计算机里,颜色是用数字表示的。比如纯黑色是0,纯白色是255。嗯,这里要注意,这个范围是0到255,一共256个级别。为什么是256?因为一个字节刚好能存下(2的8次方)。
2.2 图像坐标系
有了像素,怎么定位它?这就引出了坐标系。
OpenCV的坐标系和数学里不太一样。它的原点在左上角。
- x轴:水平向右,代表图像的列
- y轴:垂直向下,代表图像的行
所以,如果你要访问第3行、第5列的像素,写法是 img[3, 5]。先写行,再写列。这个顺序千万别搞反了。我刚开始学的时候,就经常把行列写反,结果调试了半天才发现是坐标搞错了。
img[行, 列],不是 img[x, y]。我曾经在项目里因为这个问题,导致人脸检测框画歪了,排查了整整一下午。
2.3 BGR颜色空间
说到颜色,这里有个坑。
我们平时用的RGB,是红绿蓝。但OpenCV默认用的是BGR。顺序反了:蓝、绿、红。
为什么会这样?其实是个历史遗留问题。早期的一些相机硬件厂商就是这么存的,OpenCV沿用了这个习惯。
所以,当你用OpenCV读取一张彩色图片时,每个像素其实有三个值:
- 第一个值:蓝色通道 (Blue)
- 第二个值:绿色通道 (Green)
- 第三个值:红色通道 (Red)
如果你用其他库(比如matplotlib)直接显示,颜色会偏蓝。你想想看,是不是很坑?
cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。这是我每次写代码必加的一行。
2.4 读取/修改像素值
理论说完了,咱们直接上代码。
import cv2
import numpy as np
# 读取一张图片
img = cv2.imread('example.jpg')
# 获取某个像素的值(第10行,第20列)
pixel = img[10, 20]
print(pixel) # 输出:[B, G, R] 三个值
# 只获取蓝色通道的值
blue = img[10, 20, 0]
print(blue)
# 修改像素值
img[10, 20] = [255, 0, 0] # 改成纯蓝色
img[10, 20, 1] = 128 # 只改绿色通道为128
你看,操作起来其实很简单。就是数组的索引和赋值。
我个人习惯用 img[行, 列] 来获取整个像素,用 img[行, 列, 通道] 来获取某个通道。这样代码可读性更好。
img[0:10, 0:10] = [0, 0, 255]。切片操作在图像处理里非常常用。
2.5 图像属性(shape, dtype, size)
拿到一张图,你总得知道它的一些基本信息吧?
OpenCV提供了三个最常用的属性:
| 属性 | 含义 | 示例输出 |
|---|---|---|
img.shape |
图像的形状(高度, 宽度, 通道数) | (480, 640, 3) |
img.dtype |
像素值的数据类型 | uint8 |
img.size |
像素总数(高 x 宽 x 通道数) | 921600 |
# 获取图像属性
height, width, channels = img.shape
print(f"高度: {height}, 宽度: {width}, 通道数: {channels}")
print(f"数据类型: {img.dtype}") # 通常是 uint8
total_pixels = img.size
print(f"总像素数: {total_pixels}")
这里有个细节:shape返回的是一个元组。如果是灰度图,它只有两个值(高和宽),没有通道数。所以写代码时要注意判断。
img.dtype 通常是 uint8,取值范围0-255。如果你做数学运算,比如加减乘除,结果可能会溢出。比如255+1,在uint8下会变成0。我遇到过好几次这种问题,后来养成了习惯:做运算前先转成 float32,处理完再转回来。
好了,这一节的内容就这些。总结一下:
- 像素是图像的最小单元,值范围0-255
- 坐标系原点在左上角,先写行再写列
- OpenCV默认用BGR,不是RGB
- 用索引读写像素值,用属性获取图像信息
下一节,咱们会聊图像的基本运算。到时候你会发现,这些基础操作会反复用到。打好基础,后面才能跑得快。