欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

OpenCV 从零学习 · 第 2 讲:像素操作、ROI 与通道拆分(你真的会"改图"吗)

OpenCV 从零学习 · 第 2 讲:像素操作、ROI 与通道拆分

上节课我们学会了把图片读进来、显示出来、存回去。但"能看"还不够——真正有用的图像操作是"能改"。这一讲我们从改一个像素开始,到截取一块区域(ROI),再到通道级别的处理。每个函数我都配一个真实场景,让你一眼看懂"它到底用在哪"。

0. 先记住一件事:图像就是 NumPy 数组

OpenCV 读进来的图片 img 本质是一个 NumPy 数组:

  • 彩色图形状是 (高, 宽, 3) —— 注意顺序是高在前、宽在后
  • 灰度图形状是 (高, 宽)
  • 数组里的数字类型是 uint8,范围 0~255
import cv2
img = cv2.imread('test.jpg')
print(img.shape)     # 例如 (480, 640, 3)  → 高480, 宽640, 3通道
print(img.dtype)     # uint8
print(img[0, 0])     # 左上角像素的 [B, G, R] 值, 例如 [23, 45, 200]
新手必踩坑 ①:坐标顺序是 (行 y, 列 x),不是 (x, y)!
取"第 100 行、第 200 列"的像素要写 img[100, 200]。很多人习惯性写 img[x, y] 导致取到的不是想要的像素,甚至越界报错。

1. 取一个像素 / 改一个像素 —— 你真的"看"懂这张图了吗

函数:直接用 NumPy 下标访问

写法含义
img[y, x]取 (x, y) 处像素的 [B,G,R]
img[y, x, 0]取该像素的 蓝(B) 通道值
img[y, x] = [0,0,255]把该像素改成纯红
场景案例 A:想知道图片正中心是什么颜色
做图像质检时,常需要读某个固定点的颜色判断是否曝光正常:
h, w = img.shape[:2]
cy, cx = h // 2, w // 2
b, g, r = img[cy, cx]
print(f'中心点颜色 B={b} G={g} R={r}')
场景案例 B:把图片"反色"(底片效果)
扫描件常是"黑字白底",想转成"白字黑底"方便打印/识别,一行就够:
img_inv = 255 - img      # 每个像素 255 减去自身 → 反色
cv2.imwrite('invert.jpg', img_inv)
新手必踩坑 ②:uint8 会"溢出回绕"
img + 100 如果某像素是 200,结果不会是 300,而是 44(300-256 回绕)。想"提亮但不发灰",要用 cv2.add(带饱和,自动封顶 255),见第 4 节。

2. ROI(感兴趣区域)—— 用 NumPy 切片"框住一块图"

ROI = Region Of Interest,就是图片里你关心的那一块矩形。语法和普通 NumPy 切片一样:

roi = img[y1:y2, x1:x2]    # 行从 y1 到 y2, 列从 x1 到 x2
新手必踩坑 ③:切片是"视图"不是"副本"!
你改 roi 就等于改了原图同一块内存。想拿到一块独立的小图去处理、又不想动原图,必须 roi.copy()
场景案例 C:给人脸打码
假设人脸区域在 (y:50~150, x:120~220),直接把这块填成灰色块(简单马赛克):
img[50:150, 120:220] = [128, 128, 128]   # 整块涂灰
# 更好的马赛克:先缩小 10 倍再放大回原尺寸(最近邻), 产生像素块效果
face = img[50:150, 120:220]
small = cv2.resize(face, None, fx=0.1, fy=0.1, interpolation=cv2.INTER_NEAREST)
mosaic = cv2.resize(small, (face.shape[1], face.shape[0]), interpolation=cv2.INTER_NEAREST)
img[50:150, 120:220] = mosaic
场景案例 D:把 logo 贴到右上角
给图片加水印,本质是"把小图赋值给大图的对应 ROI":
logo = cv2.imread('logo.png')          # 假设 60x60
lh, lw = logo.shape[:2]
h, w = img.shape[:2]
img[0:lh, w-lw:w] = logo               # 贴到右上角 (注意 logo 尺寸要能对上)

3. 亮度 / 对比度调节 —— cv2.addcv2.multiply 的饱和特性

函数作用和直接用 + / * 的区别
cv2.add(img, 50)整体提亮+ / * 会按 uint8 溢出回绕;cv2 系列会自动封顶到 0~255,不会出现诡异灰色
cv2.subtract(img, 30)整体压暗
cv2.multiply(img, 1.3)提高对比度
场景案例 E:夜景照片提亮
直接 img + 80 会让已经很亮的路灯"爆掉回绕"成黑色杂点;用 cv2.add(img, 80) 则平滑封顶,画面干净:
bright = cv2.add(img, 80)        # 推荐:饱和提亮
bad    = img + 80                 # 不推荐:uint8 回绕产生噪点

4. 通道拆分与合并 —— 单独"玩"某一个颜色

函数签名

b, g, r = cv2.split(img)         # 拆成 3 个单通道图
merged  = cv2.merge([b, g, r])   # 再合并回去
场景案例 F:只想看"红色物体"更突出
合成一张"只有红色通道"的单色红图:把 G、B 通道清零,只留 R:
zeros = np.zeros_like(r)
red_only = cv2.merge([zeros, zeros, r])   # 合成 (0,0,R) → 纯红单色图
场景案例 G:"去红"滤镜
把 R 通道清零,模拟老照片/复古效果:
no_red = cv2.merge([b, g, np.zeros_like(r)])   # (B, G, 0)
小提示cv2.split 比直接 img[:,:,2] 慢,但语义清晰。只想要某个通道时,直接 r = img[:,:,2] 更快。

5. 本讲要点清单

  • 图像是 NumPy 数组,形状 (高, 宽, 3);访问像素用 img[y, x](行在前)。
  • 反色:255 - img
  • ROI 切片是视图,要独立副本用 .copy();常用于打码、贴 logo。
  • 提亮/压暗/对比度用 cv2.add / subtract / multiply,它们会饱和封顶,优于直接 + / *(会回绕)。
  • 通道级处理用 cv2.split / cv2.merge,可实现单色图、去色滤镜等。
✅ 下一讲预告:为什么"按颜色抠图"要先把 BGR 转成 HSV?第 3 讲:色彩空间转换 cvtColor

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功