3. 图像处理基础:数字图像表示、OpenCV基础操作、图像增强与数据增强技术

做病虫害识别,说白了就是让算法学会「看」叶子。但算法看到的不是叶子,是一堆数字。这一章我们就来聊聊,怎么把一张照片变成算法能理解的东西,以及怎么让数据变得更丰富、更抗揍。

3.1 数字图像的本质:像素矩阵

一张图像在计算机眼里,就是一个二维数组。每个格子叫像素,每个像素存一个数值。灰度图是单通道,每个像素 0~255,0 是黑,255 是白。彩色图是三个通道叠加——R、G、B。

我记得刚入行时有个误区:以为图像越大越好。后来做水稻稻瘟病识别,一张 4000×3000 的图直接让训练显存爆了。你想想看,一张图就是 1200 万个像素,三个通道就是 3600 万个数值。模型要在这上面做卷积,计算量可想而知。

核心概念: 数字图像 = 像素矩阵 + 通道数。灰度图 shape = (H, W),彩色图 shape = (H, W, 3)。

实际项目中,我们通常不会直接用原始分辨率。我习惯先做一次降采样,把长边缩到 1024 或 512。这样既保留纹理细节,又不会让训练太慢。

3.2 OpenCV 基础操作:读、写、显示、变换

OpenCV 是图像处理的瑞士军刀。我几乎每个项目都离不开它。下面这几个操作,是病虫害识别流水线里最常用的。

3.2.1 读取与显示

import cv2

# 读取图像,默认是 BGR 格式
img = cv2.imread('rice_leaf.jpg')
print(img.shape)  # (H, W, 3)

# 转成 RGB(因为 matplotlib 用 RGB)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 显示
cv2.imshow('Rice Leaf', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
注意: OpenCV 默认用 BGR 顺序,而 matplotlib、PIL 用 RGB。我曾经因为这个 bug 排查了一整天——训练出来的模型在验证集上效果很好,但部署到前端后颜色全偏蓝。原因就是前端用 RGB 加载,而训练时用的 BGR。所以,统一色彩空间是第一步。

3.2.2 几何变换

病虫害识别中,叶子可能歪着、斜着、大小不一。我们需要用几何变换来对齐。

# 缩放
resized = cv2.resize(img, (512, 512))

# 旋转
(h, w) = img.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, 45, 1.0)
rotated = cv2.warpAffine(img, M, (w, h))

# 翻转
flipped = cv2.flip(img, 1)  # 水平翻转

我个人习惯在数据预处理阶段,先把所有图片缩放到统一尺寸,比如 512×512。这样 batch 训练时不用动态 padding,效率高很多。

3.2.3 色彩空间转换

有时候 RGB 不是最好的特征空间。比如识别早期稻瘟病,病斑往往是黄褐色,在 HSV 空间里更容易分割。

hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 提取黄色区域
lower_yellow = np.array([20, 100, 100])
upper_yellow = np.array([30, 255, 255])
mask = cv2.inRange(hsv, lower_yellow, upper_yellow)
经验之谈: 做病虫害分割时,HSV 空间比 RGB 更鲁棒。因为光照变化主要影响 V 通道,而 H(色调)相对稳定。我做过一个对比实验:在 RGB 上做阈值分割,准确率只有 60%;换到 HSV 后直接跳到 85%。

3.3 图像增强:让特征更明显

图像增强不是为了让图更好看,而是为了让算法更容易抓住关键特征。说白了,就是「把病斑凸显出来」。

3.3.1 直方图均衡化

如果一张图整体偏暗或偏亮,直方图均衡化可以把对比度拉开。

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
equalized = cv2.equalizeHist(gray)

但要注意,全局均衡化有时会放大噪声。我更喜欢用 CLAHE(对比度受限自适应直方图均衡化),它只在局部窗口内做均衡,不会把背景噪声也放大。

clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
clahe_img = clahe.apply(gray)

3.3.2 锐化与去噪

病虫害识别中,病斑边缘往往是关键。锐化能让边缘更清晰。

kernel = np.array([[-1,-1,-1],
                   [-1, 9,-1],
                   [-1,-1,-1]])
sharpened = cv2.filter2D(img, -1, kernel)

但锐化也会放大噪声。我通常先做一次高斯模糊去噪,再做锐化。

blurred = cv2.GaussianBlur(img, (5,5), 0)
sharpened = cv2.addWeighted(img, 1.5, blurred, -0.5, 0)
增强流程建议: 去噪 → 对比度增强 → 锐化。顺序不能乱,否则噪声会被锐化放大。

3.4 数据增强:用有限数据训练无限泛化

这是我最想强调的部分。病虫害数据集通常很小——你可能只有几百张带病斑的叶子照片。直接训练,模型很容易过拟合。数据增强就是「用魔法打败魔法」。

3.4.1 几何增强

增强方式 适用场景 注意事项
随机旋转(±30°) 叶子朝向不固定 旋转后黑边需裁剪或填充
水平/垂直翻转 对称性病害 文字类病害不要垂直翻转
随机裁剪 局部病斑检测 裁剪比例不要小于 50%
缩放(0.8~1.2) 不同拍摄距离 保持长宽比或等比缩放

3.4.2 色彩增强

光照变化是野外拍摄最大的干扰。我习惯用 HSV 空间的随机扰动来模拟不同光照。

def random_brightness_contrast(img):
    alpha = 1.0 + random.uniform(-0.2, 0.2)  # 对比度
    beta = random.randint(-30, 30)           # 亮度
    return cv2.convertScaleAbs(img, alpha=alpha, beta=beta)

3.4.3 高级增强:MixUp 与 CutMix

这两个是近年来的「黑科技」。简单说,就是把两张图混合在一起,标签也按比例混合。

# MixUp 伪代码
lam = np.random.beta(1.0, 1.0)
mixed_img = lam * img1 + (1 - lam) * img2
mixed_label = lam * label1 + (1 - lam) * label2

我曾经在玉米叶斑病数据集上试过,只用原始 500 张图,加 MixUp 后验证集准确率从 78% 提升到 86%。效果非常明显。

避坑指南: 数据增强不是越多越好。我曾经把旋转角度设到 ±90°,结果模型把倒着的叶子也当成正样本,推理时把正常叶子误判为病害。增强策略一定要和实际场景匹配。

3.5 本章知识体系总览

下面这张图,把本章的核心逻辑串起来了。从原始图像到模型输入,中间经历了什么,一目了然。

图像处理与增强知识体系 原始图像(像素矩阵) OpenCV 基础操作:读取 / 缩放 / 旋转 / 色彩空间转换 图像增强:直方图均衡 / CLAHE / 锐化 / 去噪 数据增强:几何变换 / 色彩扰动 / MixUp / CutMix 增强后的训练样本 核心原则 增强策略必须与真实拍摄场景匹配,避免引入不存在的噪声或变形

从原始像素矩阵开始,经过 OpenCV 的几何与色彩预处理,再到图像增强提升对比度,最后用数据增强扩充样本多样性。每一步都在为模型「喂」更好的数据。你想想看,如果输入的数据质量差、数量少,再好的算法也白搭。

我的建议: 在实际项目中,先把基础操作和图像增强写成一个 pipeline 脚本,每次训练前自动执行。数据增强最好在线做(训练时实时增强),而不是离线生成所有增强图。这样既省硬盘,又能让模型看到更多样化的样本。