【数字图像处理】基于Python语言的玉米小斑病图像分析
1:项目背景
玉米作为全球重要的粮食作物,其产量和质量直接关系到粮食安全与经济发展。玉米小斑病是由半知菌亚门丝孢纲丝孢目长蠕孢菌属真菌引起的世界性玉米病害,具有发病迅速、传播广泛、危害严重的特点。在适宜的温湿度条件下,小斑病可导致玉米减产 20%-30%,严重时甚至绝收,同时还会降低玉米的营养价值和商品价值,给农业生产带来巨大损失。玉米小斑病的两种状态,如图1所示。
图1:玉米小斑病的两种状态
传统的玉米小斑病诊断主要依赖人工田间调查,通过农业技术人员肉眼观察玉米叶片的病斑形态、颜色和分布情况进行判断。这种方法存在明显局限性:一是主观性强,不同人员的经验差异会导致诊断结果不一致;二是效率低下,难以满足大面积农田快速检测的需求;三是时效性差,病害早期症状不明显时容易漏检,错过最佳防治时期。此外,传统化学防治手段因缺乏精准性,往往导致农药过度使用,不仅增加生产成本,还会造成环境污染和农药残留问题,威胁生态安全和人类健康。
随着计算机视觉和机器学习技术的发展,基于图像分析的作物病害诊断技术为玉米小斑病防治提供了新的解决方案。Python 语言凭借其简洁高效的语法、丰富的第三方库(如 OpenCV、Scikit-learn等)以及强大的数据处理和算法实现能力,成为图像分析领域的首选工具。利用 Python 对玉米小斑病图像进行处理和分析,能够快速、准确地识别病斑,提取病斑的特征参数,实现病害的早期检测和严重程度评估,为精准施药和科学防治提供数据支持。这不仅有助于提高玉米小斑病诊断的效率和准确性,还能推动农业病虫害防治向智能化、精准化方向发展,对保障玉米生产安全、促进农业可持续发展具有重要意义。
2:技术路线
如图2所示,本项目的技术路线可概述如下:
(1)借助 Python 的 OpenCV、NumPy 等库对玉米叶片图像进行预处理,采用直方图均衡化、滤波等手段增强图像,通过阈值化方法分割出病斑。
(2)进行特征提取,涵盖纹理的多方面特征。
图2:基于Python语言的玉米小斑病图像分析的技术路线
3:实验过程
本项目的实验运行环境配置如下:操作系统采用 Windows 10,Python 版本为 3.12.1,同时基于 Jupyter Notebook 在 VSCode 平台进行调试,辅以 OpenCV、Scikit-learn 等第三方库实现图像分析功能。
3.1:图像预处理
(1)灰度处理
读取一张玉米叶片的RGB图像,并将其转化为灰度图像,结果如图3所示。
图3:玉米叶片样本的原图和灰度处理图像
源代码:
import cv2 import matplotlib.pyplot as plt example_img = r'C:\Users\86158\Desktop\disease\11.0rgb.jpg' # 读取图像并转换为灰度图像 image = cv2.imread(example_img, cv2.IMREAD_GRAYSCALE) image_origin = cv2.imread(example_img, cv2.IMREAD_COLOR) # 将 BGR 转换为 RGB image_rgb = cv2.cvtColor(image_origin, cv2.COLOR_BGR2RGB) plt.figure(figsize=(15, 10)) plt.subplot(1, 2, 1) plt.imshow(image_rgb) plt.title('Original Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(image, cmap='gray') plt.title('Grayscale Image') plt.axis('off') plt.show() |
(2)线性灰度变换
线性灰度变换公式为:I_out =a * I_in +b。其中,I_out是变换后的像素值,I_in是原始图像的像素值,a控制对比度,b控制亮度。
设置对比度调整因子(a = 0.5)和亮度调整因子(b = 5),对灰度处理图像进行线性灰度变换,结果如图4所示。
图4:玉米叶片样本的灰度处理图像和线性灰度变换图像
源代码:
# 线性灰度变换 a = 0.5 # 对比度调整因子 b = 5 # 亮度调整因子 linear_transformed_image = cv2.convertScaleAbs(image, alpha=a, beta=b) plt.figure(figsize=(15, 10)) plt.subplot(1, 2, 1) plt.imshow(image, cmap='gray') plt.title('Grayscale Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(linear_transformed_image, cmap='gray') plt.title('Linear Transformed Image') plt.axis('off') plt.show() |
修改对比度调整因子a,得到高对比度调整因子(a = 2)和低对比度调整因子(a = 0.01)的线性灰度变换图像,结果如图5所示。
当采用高对比度调整因子时,图像中亮的区域更亮,暗的区域更暗,整体对比度增强。最终,亮部更亮且亮部范围增大,暗部细节在一定程度上被压缩。
当采用低对比度调整因子时,图像整体趋于平缓,亮部和暗部的差异变小,对比度降低。最终,图像偏暗且整体比较模糊,细节区分度低。
图5:玉米叶片样本的高对比度调整因子(a = 2)和低对比度调整因子(a = 0.01)的线性灰度变换图像
源代码:
a = 2 # 对比度调整因子 b = 5 # 亮度调整因子 linear_transformed_image_high_a = cv2.convertScaleAbs(image, alpha=a, beta=b) a = 0.01 # 对比度调整因子 b = 5 # 亮度调整因子 linear_transformed_image_low_a = cv2.convertScaleAbs(image, alpha=a, beta=b) plt.figure(figsize=(15, 10)) plt.subplot(1, 2, 1) plt.imshow(linear_transformed_image_high_a, cmap='gray') plt.title('High-a Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(linear_transformed_image_low_a, cmap='gray') plt.title('Low-a Image') plt.axis('off') plt.show() |
修改亮度调整因子b,得到高亮度调整因子(b = 170)和低亮度调整因子(b = 0.01)的线性灰度变换图像,结果如图6所示。
当采用高亮度调整因子时,在计算变换后的像素值过程中,会给原始图像的每个像素值都加上一个较大的数值。最终,亮部区域更亮,甚至可能出现部分区域过曝。
当采用低亮度调整因子时,给原始图像像素值增加的数值非常小,对图像整体亮度的提升作用微乎其微。最终,图像基本维持原始的亮度水平,或者只是略微变亮一点。
图6:玉米叶片样本的高亮度调整因子(b = 170)和低亮度调整因子(b = 0.01)的线性灰度变换图像
源代码:
a = 0.5 # 对比度调整因子 b = 170 # 亮度调整因子 linear_transformed_image_high_b = cv2.convertScaleAbs(image, alpha=a, beta=b) a = 0.5 # 对比度调整因子 b = 0.01 # 亮度调整因子 linear_transformed_image_low_b = cv2.convertScaleAbs(image, alpha=a, beta=b) plt.figure(figsize=(15, 10)) plt.subplot(1, 2, 1) plt.imshow(linear_transformed_image_high_b, cmap='gray') plt.title('High-b Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(linear_transformed_image_low_b, cmap='gray') plt.title('Low-b Image') plt.axis('off') plt.show() |
(3)非线性灰度变换
对数变换可以拓展图像中的低灰度区域,压缩高灰度区域。对灰度处理图像进行对数变换,结果如图7所示。
图7:玉米叶片样本的灰度处理图像和对数变换图像
源代码:
# 对数变换 import numpy as np # 计算常数 c c = 255 / np.log(1 + np.max(image)) # 对数变换 log_transformed = c * np.log(1 + image.astype(np.float64)) log_transformed = np.uint8(log_transformed) # 显示原始图像和变换后的图像 plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.imshow(image, cmap='gray') plt.title('Grayscale Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(log_transformed, cmap='gray') plt.title('Log Transformed Image') plt.axis('off') plt.show() |
指数变换能够扩展图像的高灰度区域,压缩低灰度区域。对灰度处理图像进行指数变换,结果如图8所示。
图8:玉米叶片样本的灰度处理图像和指数变换图像
源代码:
# 指数变换 a = 1.01 c = 255 / (np.power(a, 255) - 1) b = 0 exponential_transformed = c * (np.power(a, image.astype(np.float64)) - b) exponential_transformed = np.uint8(np.clip(exponential_transformed, 0, 255)) # 显示原始图像和变换后的图像 plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.imshow(image, cmap='gray') plt.title('Grayscale Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(exponential_transformed, cmap='gray') plt.title('Exponential Transformed Image') plt.axis('off') plt.show() |
分段线性变换可根据不同的灰度范围采用不同的线性变换函数,从而有针对性地增强图像的某些灰度区域。对灰度处理图像进行分段线性变换,结果如图9所示。
图9:玉米叶片样本的灰度处理图像和分段线性变换图像
源代码:
# 分段线性变换 r1, s1 = 50, 20 r2, s2 = 200, 230 # 计算斜率 m1 = s1 / r1 m2 = (s2 - s1) / (r2 - r1) m3 = (255 - s2) / (255 - r2) # 分段线性变换 piecewise_transformed = np.zeros_like(image) piecewise_transformed[image < r1] = m1 * image[image < r1] piecewise_transformed[(image >= r1) & (image < r2)] = s1 + m2 * (image[(image >= r1) & (image < r2)] - r1) piecewise_transformed[image >= r2] = s2 + m3 * (image[image >= r2] - r2) # 显示原始图像和变换后的图像 plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.imshow(image, cmap='gray') plt.title('Grayscale Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(piecewise_transformed, cmap='gray') plt.title('Piecewise Linear Transformed Image') plt.axis('off') plt.show() |
(4)直方图
直方图反映了图像的亮度、对比度等特征。对玉米叶片图像的彩色原图和灰度图像分别进行直方图提取操作,结果如图10所示。其中,彩色直方图中红色、绿色、蓝色的曲线分别表示红、绿、蓝三个颜色通道的像素值分布情况。
图10:玉米叶片样本的彩色直方图和灰度直方图
源代码:
# 直方图 image = cv2.imread(example_img) gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) colors = ('b', 'g', 'r') plt.figure(figsize=(15, 10)) # 绘制彩色图像各通道的直方图 plt.subplot(2, 1, 1) for i, color in enumerate(colors): hist = cv2.calcHist([image], [i], None, [256], [0, 256]) plt.plot(hist, color=color) plt.title('Color Image Histogram') plt.xlabel('Pixel Value') plt.ylabel('Frequency') # 绘制灰度图像的直方图 plt.subplot(2, 1, 2) gray_hist = cv2.calcHist([gray_image], [0], None, [256], [0, 256]) plt.plot(gray_hist, color='black') plt.title('Grayscale Image Histogram') plt.xlabel('Pixel Value') plt.ylabel('Frequency') plt.tight_layout() plt.show() |
分离彩色原图的R、G、B三个通道,结果如图11所示。
图11:玉米叶片样本的R通道、G通道、B通道图像
源代码:
# 分离 R、G、B 通道 image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) r_channel = image[:, :, 0] g_channel = image[:, :, 1] b_channel = image[:, :, 2]
plt.figure(figsize=(15, 10)) # 显示原始图像 plt.subplot(2, 2, 1) plt.imshow(image) plt.title('Original Image') plt.axis('off') # 显示 R 通道图像 plt.subplot(2, 2, 2) plt.imshow(r_channel, cmap='gray') plt.title('Red Channel') plt.axis('off') # 显示 G 通道图像 plt.subplot(2, 2, 3) plt.imshow(g_channel, cmap='gray') plt.title('Green Channel') plt.axis('off') # 显示 B 通道图像 plt.subplot(2, 2, 4) plt.imshow(b_channel, cmap='gray') plt.title('Blue Channel') plt.axis('off') plt.tight_layout() plt.show() |
3.2:图像平滑
(1)均值滤波
均值滤波通过计算邻域内像素的平均值来平滑图像。对玉米叶片图像的彩色原图进行均值滤波处理,结果如图12所示。
图12:玉米叶片样本的彩色原图和均值滤波平滑图像
源代码:
# 均值滤波 image = cv2.imread(example_img) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 均值滤波核大小为 3x3 mean_blur = cv2.blur(image, (3, 3)) # 创建一个 2x3 的子图布局 plt.figure(figsize=(15, 10)) # 显示原始图像 plt.subplot(1, 2, 1) plt.imshow(image) plt.title('Original Image') plt.axis('off') # 显示均值滤波后的图像 plt.subplot(1, 2, 2) plt.imshow(mean_blur) plt.title('Mean Blur') plt.axis('off') plt.tight_layout() plt.show() |
(2)高斯滤波
高斯滤波根据高斯分布对邻域像素进行加权平均,能有效去除高斯噪声。对玉米叶片图像的彩色原图进行高斯滤波处理,结果如图13所示。
图13:玉米叶片样本的彩色原图和高斯滤波平滑图像
源代码:
# 高斯滤波,核大小为 3x3,标准差为 0 gaussian_blur = cv2.GaussianBlur(image, (3, 3), 0) plt.figure(figsize=(15, 10)) # 显示原始图像 plt.subplot(1, 2, 1) plt.imshow(image) plt.title('Original Image') plt.axis('off') # 显示均值滤波后的图像 plt.subplot(1, 2, 2) plt.imshow(gaussian_blur) plt.title('Gaussian Blur') plt.axis('off') plt.tight_layout() plt.show() |
(3)中值滤波
中值滤波邻域内像素的中值作为当前像素的值,对椒盐噪声有很好的抑制作用。对玉米叶片图像的彩色原图进行中值滤波处理,结果如图14所示。
图14:玉米叶片样本的彩色原图和中值滤波平滑图像
源代码:
# 中值滤波,核大小为 3 median_blur = cv2.medianBlur(image, 3) plt.figure(figsize=(15, 10)) # 显示原始图像 plt.subplot(1, 2, 1) plt.imshow(image) plt.title('Original Image') plt.axis('off') # 显示均值滤波后的图像 plt.subplot(1, 2, 2) plt.imshow(median_blur) plt.title('Median Blur') plt.axis('off') plt.tight_layout() plt.show() |
(4)边缘保持滤波
边缘保持滤波是一类在平滑图像的同时,尽可能保留图像边缘信息的滤波方法。这样既可以去除图像中的噪声,又能避免边缘模糊,使得图像的细节和轮廓依然清晰可辨。常见的边缘保持滤波算法有双边滤波、引导滤波等。对玉米叶片图像的彩色原图进行边缘保持滤波处理(双边滤波),结果如图15所示。
图15:玉米叶片样本的彩色原图和双边滤波平滑图像
源代码:
# 双边滤波,直径为 9,空间高斯分布标准差为 75,灰度值相似性标准差为 75 bilateral_filter = cv2.bilateralFilter(image, 9, 75, 75) plt.figure(figsize=(15, 10)) # 显示原始图像 plt.subplot(1, 2, 1) plt.imshow(image) plt.title('Original Image') plt.axis('off') # 显示均值滤波后的图像 plt.subplot(1, 2, 2) plt.imshow(bilateral_filter) plt.title('Bilateral Filter Blur') plt.axis('off') plt.tight_layout() plt.show() |
3.3:图像锐化
(1)Roberts梯度法
Roberts 梯度法基于对角线上的交叉差分来计算图像中像素的梯度。对玉米叶片图像的灰度图像进行Roberts锐化处理,结果如图16所示。
图16:玉米叶片样本的灰度图像和Roberts锐化图像
源代码:
# Roberts 算子边缘检测 image = cv2.imread(example_img, cv2.IMREAD_GRAYSCALE) # 定义 Roberts 算子的卷积核 roberts_x = np.array([[1, 0], [0, -1]], dtype=np.float32) roberts_y = np.array([[0, 1], [-1, 0]], dtype=np.float32) # 使用卷积操作计算 Roberts 梯度 gradient_x = cv2.filter2D(image, -1, roberts_x) gradient_y = cv2.filter2D(image, -1, roberts_y) # 计算 Roberts 梯度幅值 roberts_gradient = np.sqrt(np.square(gradient_x) + np.square(gradient_y)).astype(np.uint8) plt.figure(figsize=(10, 5)) # 显示原始图像 plt.subplot(1, 2, 1) plt.imshow(image, cmap='gray') plt.title('Original Image') plt.axis('off') # 显示 Roberts 梯度图像 plt.subplot(1, 2, 2) plt.imshow(roberts_gradient, cmap='gray') plt.title('Roberts Gradient Image') plt.axis('off') plt.show() |
(2)Sobel算法
Sobel算法结合高斯平滑和微分求导来计算图像灰度函数的近似梯度。对玉米叶片图像的灰度图像进行Sobel锐化处理,结果如图17所示。
图17:玉米叶片样本的灰度图像和Sobel锐化图像
源代码:
# Sobel 算子边缘检测 image = cv2.imread(example_img, cv2.IMREAD_GRAYSCALE) # 计算x和y方向的Sobel梯度 sobelx = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=3) sobely = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=3) # 取绝对值 sobelx = np.absolute(sobelx) sobely = np.absolute(sobely) # 将结果转换为8位无符号整数 sobelx = np.uint8(sobelx) sobely = np.uint8(sobely) # 合并x和y方向的梯度 sobel_combined = cv2.bitwise_or(sobelx, sobely) plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.imshow(image, cmap='gray') plt.title('Original Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(sobel_combined, cmap='gray') plt.title('Sobel Edge Detection') plt.axis('off') plt.show() |
(3)Prewitt算法
Prewitt算法通过计算图像在水平和垂直方向的一阶导数来检测边缘。对玉米叶片图像的灰度图像进行Prewitt锐化处理,结果如图18所示。
图18:玉米叶片样本的灰度图像和Prewitt锐化图像
源代码:
# Prewitt 算子边缘检测 image = cv2.imread(example_img, cv2.IMREAD_GRAYSCALE) # 定义 Prewitt 算子的水平和垂直卷积核 prewitt_x = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=np.float32) prewitt_y = np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]], dtype=np.float32) # 使用卷积操作计算水平和垂直方向的梯度 gradient_x = cv2.filter2D(image, -1, prewitt_x) gradient_y = cv2.filter2D(image, -1, prewitt_y) # 计算梯度幅值 prewitt_gradient = np.sqrt(np.square(gradient_x) + np.square(gradient_y)).astype(np.uint8) plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.imshow(image, cmap='gray') plt.title('Original Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(prewitt_gradient, cmap='gray') plt.title('Prewitt Edge Detection') plt.axis('off') plt.show() |
(4)Laplacian算法
Laplacian算法基于二阶导数进行图像边缘检测。对玉米叶片图像的灰度图像进行Laplacian锐化处理,结果如图19所示。
图19:玉米叶片样本的灰度图像和Laplacian锐化图像
源代码:
# Laplacian 算子边缘检测 image = cv2.imread(example_img, cv2.IMREAD_GRAYSCALE) # 应用拉普拉斯算子 laplacian = cv2.Laplacian(image, cv2.CV_64F) # 取绝对值,因为拉普拉斯变换的结果可能包含负值 laplacian = np.absolute(laplacian) laplacian = np.uint8(laplacian) plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.imshow(image, cmap='gray') plt.title('Original Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(laplacian, cmap='gray') plt.title('Laplacian Edge Detection') plt.axis('off') plt.show() |
(5)LoG算法
LoG算法先使用高斯滤波对图像进行平滑处理以减少噪声的影响,再利用拉普拉斯算子进行边缘检测。对玉米叶片图像的灰度图像进行LoG锐化处理,结果如图20所示。
图20:玉米叶片样本的灰度图像和LoG锐化图像
源代码:
# log 算子边缘检测 image = cv2.imread(example_img, cv2.IMREAD_GRAYSCALE) # 高斯核大小为5x5,标准差为0 blurred = cv2.GaussianBlur(image, (5, 5), 0) # 应用拉普拉斯算子 laplacian = cv2.Laplacian(blurred, cv2.CV_64F) # 取绝对值,因为拉普拉斯变换结果可能包含负值 laplacian = np.absolute(laplacian) laplacian = np.uint8(laplacian) plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.imshow(image, cmap='gray') plt.title('Original Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(laplacian, cmap='gray') plt.title('LoG Edge Detection') plt.axis('off') plt.show() |
3.4:频域处理
(1)频域平滑(低通)滤波
低通滤波器可以通过衰减高频分量来平滑图像,去除噪声。对玉米叶片图像的灰度图像进行理想低通滤波器的滤波处理,结果如图21所示。
图21:玉米叶片样本的灰度图像和频域平滑(低通)滤波图像
源代码:
# 低通 image = cv2.imread(example_img, cv2.IMREAD_GRAYSCALE) # 傅里叶变换 f = np.fft.fft2(image) fshift = np.fft.fftshift(f) # 获取图像的行数和列数 rows, cols = image.shape # 中心位置 crow, ccol = rows // 2, cols // 2 # 理想低通滤波器半径 D0 = 30 # 创建理想低通滤波器掩膜 mask = np.zeros((rows, cols), np.uint8) mask[crow - D0:crow + D0, ccol - D0:ccol + D0] = 1 # 应用低通滤波器 fshift_filtered = fshift * mask # 逆傅里叶变换 ishift = np.fft.ifftshift(fshift_filtered) img_back = np.fft.ifft2(ishift) img_back = np.abs(img_back) # 显示原始图像和滤波后的图像 plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.imshow(image, cmap='gray') plt.title('Original Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(img_back, cmap='gray') plt.title('Low - Pass Filtered Image') plt.axis('off') plt.show() |
(2)频域锐化(高通)滤波
高通滤波器通过衰减低频分量来增强图像的边缘和细节,起到锐化图像的作用。对玉米叶片图像的灰度图像进行理想高通滤波器的滤波处理,结果如图22所示。
图22:玉米叶片样本的灰度图像和频域锐化(高通)滤波图像
源代码:
# 高通 image = cv2.imread(example_img, cv2.IMREAD_GRAYSCALE) # 傅里叶变换 f = np.fft.fft2(image) fshift = np.fft.fftshift(f) # 获取图像的行数和列数 rows, cols = image.shape # 中心位置 crow, ccol = rows // 2, cols // 2 # 理想高通滤波器半径 D0 = 30 # 创建理想高通滤波器掩膜 mask = np.ones((rows, cols), np.uint8) mask[crow - D0:crow + D0, ccol - D0:ccol + D0] = 0 # 应用高通滤波器 fshift_filtered = fshift * mask # 逆傅里叶变换 ishift = np.fft.ifftshift(fshift_filtered) img_back = np.fft.ifft2(ishift) img_back = np.abs(img_back) # 显示原始图像和滤波后的图像 plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.imshow(image, cmap='gray') plt.title('Original Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(img_back, cmap='gray') plt.title('High - Pass Filtered Image') plt.axis('off') plt.show() |
3.5:图像分割
(1)阈值分割法
根据之前3.1~3.4部分的分析,此处采用高斯滤波 + Sobel锐化的方法进行预处理。
把RGB图像转换到HSV空间,分别提取H、S、V通道的图像,结果如图23所示。可以发现V通道中,玉米叶片上的病斑非常明显,几乎和预处理后的彩色图像中的黄色病斑区域一模一样。
图23:玉米叶片样本的彩色原图、平滑处理后的图像、锐化+平滑处理后的图像、预处理完成后的H通道图像、预处理完成后的S通道图像、预处理完成后的V通道图像
源代码:
image = cv2.imread(example_img) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 图像预处理:高斯滤波 smoothed_image = cv2.GaussianBlur(image, (5, 5), 0) # 图像预处理:Sobel 锐化 sobelx = cv2.Sobel(smoothed_image, cv2.CV_64F, 1, 0, ksize=3) sobely = cv2.Sobel(smoothed_image, cv2.CV_64F, 0, 1, ksize=3) # 取绝对值 sobelx = np.absolute(sobelx) sobely = np.absolute(sobely) sobelx = np.uint8(sobelx) sobely = np.uint8(sobely) # 合并 x 和 y 方向的梯度 sobel_combined = cv2.bitwise_or(sobelx, sobely) # 与原始图像相加实现锐化 sharpened_image = cv2.addWeighted(smoothed_image, 1, sobel_combined, 0.5, 0) # 将预处理后的 RGB 图像转换到 HSV 空间 hsv_image = cv2.cvtColor(sharpened_image, cv2.COLOR_RGB2HSV) # 提取 H、S、V 三个通道的图像 h_channel, s_channel, v_channel = cv2.split(hsv_image) plt.figure(figsize=(15, 10)) # 显示原始图像 plt.subplot(2, 3, 1) plt.imshow(image) plt.title('Original Image') plt.axis('off') # 显示高斯滤波后的图像 plt.subplot(2, 3, 2) plt.imshow(smoothed_image) plt.title('Gaussian Blurred Image') plt.axis('off') # 显示 Sobel 锐化后的图像 plt.subplot(2, 3, 3) plt.imshow(sharpened_image) plt.title('Sobel Sharpened Image') plt.axis('off') # 显示 H 通道图像 plt.subplot(2, 3, 4) plt.imshow(h_channel, cmap='gray') plt.title('H Channel') plt.axis('off') # 显示 S 通道图像 plt.subplot(2, 3, 5) plt.imshow(s_channel, cmap='gray') plt.title('S Channel') plt.axis('off') # 显示 V 通道图像 plt.subplot(2, 3, 6) plt.imshow(v_channel, cmap='gray') plt.title('V Channel') plt.axis('off') # 调整子图布局 plt.tight_layout() # 显示图像 plt.show() |
对HSV图像进行通道分离,计算H、S、V三个通道的直方图,结果如图24所示。
据此可知,H值在15~50之间的部分大概率是玉米叶片及其病斑的区域。
图23:玉米叶片样本预处理完成后的彩色图像的H、S、V通道的直方图
源代码:
# 分离HSV通道 h_channel, s_channel, v_channel = cv2.split(hsv_image) # 计算H通道的直方图 hist_h = cv2.calcHist([h_channel], [0], None, [180], [0, 180]) # 计算S通道的直方图 hist_s = cv2.calcHist([s_channel], [0], None, [256], [0, 256]) # 计算V通道的直方图 hist_v = cv2.calcHist([v_channel], [0], None, [256], [0, 256]) # 创建一个1x3的子图布局 plt.figure(figsize=(15, 5)) # 绘制H通道的直方图 plt.subplot(1, 3, 1) plt.plot(hist_h) plt.title('Hue Histogram') plt.xlabel('Hue Value') plt.ylabel('Frequency') # 绘制S通道的直方图 plt.subplot(1, 3, 2) plt.plot(hist_s) plt.title('Saturation Histogram') plt.xlabel('Saturation Value') plt.ylabel('Frequency') # 绘制V通道的直方图 plt.subplot(1, 3, 3) plt.plot(hist_v) plt.title('Value Histogram') plt.xlabel('Value') plt.ylabel('Frequency') plt.tight_layout() plt.show() |
观察到V通道的图像病斑较为明显,对灰度处理后的V通道图像进行阈值分割,结果如图24所示。
图24:玉米叶片样本预处理完成后的V通道直方图和阈值分割图像
源代码:
threshold_value = 225 # 设定阈值 _, binary_image = cv2.threshold(v_channel, threshold_value, 255, cv2.THRESH_BINARY) plt.figure(figsize=(15, 5)) plt.subplot(1, 2, 1) plt.imshow(v_channel, cmap='gray') plt.title('V Channel') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(binary_image, cmap='gray') plt.title('Segmented Lesion Image in V Channel') plt.axis('off') plt.show() |
将上述通过阈值分割法得到的掩码还原到彩色图像中,结果如图25所示。虽然玉米叶片中的黄色病斑能够很好的分割出来,但是分割过程中包含了其他背景像素(例如图像的下侧和右侧),因此仅仅通过V通道进行阈值分割的效果不是很好。
图25:玉米叶片样本的预处理后彩色图像和阈值分割图像
源代码:
overlay = np.zeros_like(image) overlay[:, :, 0] = binary_image # 这里假设病斑区域用蓝色显示,可根据需要调整通道 # 叠加显示 alpha = 0.5 # 透明度 result_image = cv2.addWeighted(image, 1 - alpha, overlay, alpha, 0) plt.figure(figsize=(10, 10)) plt.subplot(1, 2, 1) plt.imshow(sharpened_image) plt.title('Sobel Sharpened Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(result_image) plt.title('Image with Lesion Overlay') plt.axis('off') plt.show() |
调整黄色在 HSV 空间的阈值范围,直接在彩色图像上进行分割,结果如图26所示。可以发现双阈值分割情况下,分割区域集中在玉米叶片上,比单独在V通道图像上进行阈值分割的效果更好。但是对于一些玉米叶片上较小的病斑,本方法仍然无法很好对其进行分割操作,同时分割的区域中间有空洞。
图26:玉米叶片样本的预处理后彩色图像、病斑掩码图像、双阈值分割图像
源代码:
# 调整黄色在 HSV 空间的阈值范围 lower_yellow = np.array([15, 50, 80]) upper_yellow = np.array([27, 255, 255]) # 应用阈值分割,创建掩码 mask = cv2.inRange(hsv_image, lower_yellow, upper_yellow) # 提取黄色病斑区域 yellow_spot_region = cv2.bitwise_and(sharpened_image, sharpened_image, mask=mask) plt.figure(figsize=(15, 10)) # 显示 Sobel 锐化后的图像 plt.subplot(1, 3, 1) plt.imshow(sharpened_image) plt.title('Sobel Sharpened Image') plt.axis('off') # 显示掩码图像 plt.subplot(1, 3, 2) plt.imshow(mask, cmap='gray') plt.title('Mask for Yellow Spots') plt.axis('off') # 显示分割出的黄色病斑区域 plt.subplot(1, 3, 3) plt.imshow(yellow_spot_region) plt.title('Yellow Spot Region') plt.axis('off') plt.tight_layout() plt.show() |
通过形态学处理(闭运算填充孔洞,开运算去除噪声),得到较为实心的玉米叶片病斑区域,结果如图27所示。第四个图中的绿色标记为算法检测到的病斑位置,上方标记了病斑的面积总和,总共大约为10384个像素大小。
图27:经过形态学处理后的双阈值分割图像
源代码:
# 调整黄色在 HSV 空间的阈值范围 lower_yellow = np.array([15, 50, 80]) upper_yellow = np.array([27, 255, 255]) # 应用阈值分割,创建掩码 mask = cv2.inRange(hsv_image, lower_yellow, upper_yellow) # 形态学操作:填充孔洞和去除噪声 # 定义结构元素 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (4, 4)) # 闭运算填充孔洞 mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 开运算去除噪声 mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 提取黄色病斑区域 yellow_spot_region = cv2.bitwise_and(sharpened_image, sharpened_image, mask=mask) # 查找病斑轮廓 contours, _ = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 绘制轮廓并计算病斑面积 total_area = 0 contour_image = sharpened_image.copy() for contour in contours: area = cv2.contourArea(contour) total_area += area cv2.drawContours(contour_image, [contour], -1, (0, 255, 0), 2) plt.figure(figsize=(15, 10)) # 显示 Sobel 锐化后的图像 plt.subplot(1, 4, 1) plt.imshow(sharpened_image) plt.title('Sobel Sharpened Image') plt.axis('off') # 显示掩码图像 plt.subplot(1, 4, 2) plt.imshow(mask, cmap='gray') plt.title('Mask for Yellow Spots') plt.axis('off') # 显示分割出的黄色病斑区域 plt.subplot(1, 4, 3) plt.imshow(yellow_spot_region) plt.title('Yellow Spot Region') plt.axis('off') # 显示绘制轮廓后的图像并标注总面积 plt.subplot(1, 4, 4) plt.imshow(contour_image) plt.title(f'Yellow Spot Region with Contours\nTotal Area: {total_area:.2f} pixels') plt.axis('off') plt.tight_layout() plt.show() |
(2)区域生长法
由于区域生长法依赖于种子的位置选取,本实验中病斑仅占像素的一小部分,多个种子刚好选到病斑像素的概率较低,通过代码实现的效果并不好,因此该部分不进行详细阐述。
3.6:纹理分析
(1)灰度共生矩阵
灰度共生矩阵统计了图像中具有特定空间位置关系的灰度对出现的频率,描述了图像中灰度级的空间分布关系。设计代码计算玉米叶片病斑图像的灰度共生矩阵,结果如图28所示。
图28:玉米叶片样本的彩色原图及其灰度共生矩阵计算结果
源代码:
import numpy as np from skimage.feature import graycomatrix import cv2 # 计算灰度共生矩阵 def calculate_glcm(image, distances, angles): glcm = graycomatrix(image, distances=distances, angles=angles, symmetric=True, normed=True) return glcm example_img = r'C:\Users\86158\Desktop\disease\116.0rgb.jpg' image = cv2.imread(example_img) gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 定义距离和角度 distances = [1, 2, 3] angles = [0, np.pi / 4, np.pi / 2, 3 * np.pi / 4] # 计算灰度共生矩阵 glcm = calculate_glcm(gray_image, distances, angles) print("灰度共生矩阵的形状:", glcm.shape) |
针对灰度共生矩阵,设计算法计算常见统计量,例如:角二阶矩(能量)、对比度、相关性、熵、逆差距,结果如图29所示。
图29:玉米叶片样本的灰度统计量计算结果
角二阶矩(能量)反映了图像纹理的均匀性和规则性。在计算结果中,能量值在不同距离和角度下相对较小,均在 0.0006 到 0.0014 之间,表明图像的纹理相对较为复杂,灰度分布不太均匀。
对比度衡量了图像中灰度差异的程度,即图像中明暗变化的剧烈程度。在计算结果中,对比度值范围较大,从 10.46 到 134.81,表明图像中存在明显的灰度变化,可能包含不同的纹理区域,如病斑区域和健康区域之间的灰度差异。
相关性用于衡量图像中灰度分布的线性依赖程度,反映了图像中像素灰度值之间的相关性。在计算结果中,相关性值都比较高,均在 0.92 到 0.99 之间,说明图像中的像素灰度值之间存在较强的线性关系,纹理具有一定的规则性。
熵表示图像纹理的复杂程度和随机性。在计算结果中,熵值在 7.09 到 8.01 之间,相对较大,表明图像的纹理比较复杂,灰度分布较为均匀,存在较多不同的灰度组合。
逆差距反映了图像中灰度分布的均匀性。在计算结果中,逆差距值在 0.21 到 0.42 之间,相对较小,说明图像的灰度分布不太均匀,纹理可能比较粗糙。
综合来看,这些特征值表明该玉米病斑叶片图像的纹理较为复杂,存在明显的灰度差异和变化,不同方向和距离上的纹理特性也有所不同。
源代码:
def calculate_features(glcm): from skimage.feature import graycoprops
if glcm is None: return {} features = {} # 角二阶矩(能量) asm = np.sum(glcm ** 2, axis=(0, 1)) features['角二阶矩(能量)'] = asm # 对比度 contrast = graycoprops(glcm, 'contrast') features['对比度'] = contrast # 相关性 correlation = graycoprops(glcm, 'correlation') features['相关性'] = correlation # 熵 entropy = -np.sum(glcm * np.ma.log(glcm).filled(0), axis=(0, 1)) features['熵'] = entropy # 逆差距 divisor = 1 + np.square(np.arange(glcm.shape[0])[:, None] - np.arange(glcm.shape[1])) # 扩展维度以匹配 glcm 的形状 divisor = divisor[:, :, np.newaxis, np.newaxis] inverse_difference_moment = np.sum(glcm / divisor, axis=(0, 1)) features['逆差距'] = inverse_difference_moment return features features = calculate_features(glcm) for feature_name, feature_value in features.items(): print(f"{feature_name}: {feature_value}") |
(2)分形维
采用盒计数法来计算玉米病斑叶片图像经过灰度处理后的分形维数,结果如图30所示。
图30:分形维度计算 - 对数-对数曲线图
对于灰度图像而言,分形维数的取值通常在 1 到 2 之间。1 代表非常规则、简单的几何形状(例如直线),而2 代表完全填充的平面。本实验的结果为1.85左右,说明图像中的几何形状分布占比较小,大范围的背景占比较大。
源代码:
def box_counting(image, min_box_size=2, max_box_size=None): if max_box_size is None: max_box_size = min(image.shape) box_sizes = 2 ** np.arange(np.ceil(np.log2(min_box_size)), np.floor(np.log2(max_box_size)) + 1).astype(int) box_counts = [] for box_size in box_sizes: non_empty_boxes = 0 for i in range(0, image.shape[0], box_size): for j in range(0, image.shape[1], box_size): box = image[i:i + box_size, j:j + box_size] if np.any(box): non_empty_boxes += 1 box_counts.append(non_empty_boxes) return box_sizes, box_counts def calculate_fractal_dimension(box_sizes, box_counts): log_box_sizes = np.log(1 / np.array(box_sizes)) log_box_counts = np.log(box_counts) slope, _ = np.polyfit(log_box_sizes, log_box_counts, 1) return slope # 灰度图 image = cv2.imread(example_img, cv2.IMREAD_GRAYSCALE) _, binary_image = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY) # 计算盒子尺寸和盒子数量 box_sizes, box_counts = box_counting(binary_image) # 计算分形维数 fractal_dimension = calculate_fractal_dimension(box_sizes, box_counts) print(f"玉米叶片病斑图像的分形维数: {fractal_dimension}") # 绘制双对数图 plt.figure(figsize=(10, 6)) plt.plot(np.log(1 / np.array(box_sizes)), np.log(box_counts), 'o-') plt.xlabel('log(1/box_size)') plt.ylabel('log(box_count)') plt.title('Fractal Dimension Calculation - Log-Log Plot') plt.grid(True) plt.show() |
(3)局部二值模式
局部二值模式是一种用来描述图像局部纹理特征的算子,具有旋转不变性和灰度不变性等显著优点。先对玉米病斑叶片的图像进行二值化,而后计算其灰度图像的局部二值模式,结果如图31所示。
图31:局部二值模式的结果
源代码:
import cv2 import numpy as np import matplotlib.pyplot as plt from skimage.feature import local_binary_pattern def calculate_lbp(image, radius=1, n_points=8): lbp = local_binary_pattern(image, n_points, radius, method='uniform') return lbp image = cv2.imread(example_img, cv2.IMREAD_GRAYSCALE) # 计算 LBP 特征 lbp_image = calculate_lbp(image) # 显示原始图像和 LBP 特征图像 plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.imshow(image, cmap='gray') plt.title('Original Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(lbp_image, cmap='gray') plt.title('Local Binary Pattern Image') plt.axis('off') plt.show() |
(4)基于边界的分割
通过LoG算法提取边缘后,可以基于这些边缘进行边界分割,结果如图32所示。
图32:基于LoG算法的边界分割
可以看出,基于边界分割方法对于病斑区域的分割效果不如基于区域分割方法的分割效果。
源代码:
import cv2 import numpy as np import matplotlib.pyplot as plt from skimage import filters def log_edge_detection(image, sigma=1.0): # 高斯平滑 blurred = cv2.GaussianBlur(image, (0, 0), sigma) # 拉普拉斯算子 log = cv2.Laplacian(blurred, cv2.CV_64F) # 零交叉检测 edges = filters.apply_hysteresis_threshold(log, 0.1 * log.max(), 0.2 * log.max()) edges = edges.astype(np.uint8) * 255 return edges def boundary_based_segmentation(edges): # 查找轮廓 contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 创建空白图像 segmented_image = np.zeros_like(edges) # 绘制轮廓 cv2.drawContours(segmented_image, contours, -1, 255, thickness=cv2.FILLED) return segmented_image image = cv2.imread(example_img, cv2.IMREAD_GRAYSCALE) # 进行LoG边缘检测 edges = log_edge_detection(image) # 基于边界进行分割 segmented_image = boundary_based_segmentation(edges) # 显示结果 plt.figure(figsize=(15, 5)) plt.subplot(1, 3, 1) plt.imshow(image, cmap='gray') plt.title('Original Image') plt.axis('off') plt.subplot(1, 3, 2) plt.imshow(edges, cmap='gray') plt.title('LoG Edge Detection') plt.axis('off') plt.subplot(1, 3, 3) plt.imshow(segmented_image, cmap='gray') plt.title('Boundary-based Segmentation') plt.axis('off') plt.show() |
4:GUI界面设计
(1)设计思路
本实验通过gradio进行可视化系统界面的设计,主要功能为提取用户所上传的玉米叶片图像上的病斑。
用户可以在jupyter notebook中使用,也可以在浏览器中使用。启动Gradio服务后生成临时公共链接(支持HTTPS),用户可通过任意设备浏览器访问,无需配置本地环境。
(2)使用流程
页面运行加载过程的界面,如图33所示。
图33:可视化系统页面运行加载过程的界面
页面加载成功的初始界面,如图34所示。
图34:页面加载成功的初始界面
点击左侧的框,选择上传待检测的玉米叶片图片,成功加载上传图片的界面,如图35所示。
图35:成功加载上传图片的界面
点击submit按钮,即可调用后端算法进行检测,算法检测结果的界面,如图36所示。从上至下依次是:经过滤波和平滑处理后的图像、黄色病斑区域的掩码图像、黄色区域病斑的分割图像、病斑在原图上的绿色标注图像。
图36:算法检测结果的界面
如果需要下载的算法检测后的图像,可移动到每个图片右上角的下载按钮,点击后即可下载到本地,如图37所示。
图37:图像下载过程的示意图
如果需要清空当前界面的结果,可以点击clear按钮进行清除。
系统源代码:
import cv2 import numpy as np import matplotlib.pyplot as plt import gradio as gr def process_image(image): # 图像预处理:高斯滤波 smoothed_image = cv2.GaussianBlur(image, (5, 5), 0) # 图像预处理:Sobel 锐化 sobelx = cv2.Sobel(smoothed_image, cv2.CV_64F, 1, 0, ksize=3) sobely = cv2.Sobel(smoothed_image, cv2.CV_64F, 0, 1, ksize=3) # 取绝对值 sobelx = np.absolute(sobelx) sobely = np.absolute(sobely) sobelx = np.uint8(sobelx) sobely = np.uint8(sobely) # 合并 x 和 y 方向的梯度 sobel_combined = cv2.bitwise_or(sobelx, sobely) # 与原始图像相加实现锐化 sharpened_image = cv2.addWeighted(smoothed_image, 1, sobel_combined, 0.5, 0) # 将预处理后的 RGB 图像转换到 HSV 空间 hsv_image = cv2.cvtColor(sharpened_image, cv2.COLOR_RGB2HSV) # 提取 H、S、V 三个通道的图像 h_channel, s_channel, v_channel = cv2.split(hsv_image) # 调整黄色在 HSV 空间的阈值范围 lower_yellow = np.array([15, 50, 80]) upper_yellow = np.array([27, 255, 255]) # 应用阈值分割,创建掩码 mask = cv2.inRange(hsv_image, lower_yellow, upper_yellow) # 形态学操作:填充孔洞和去除噪声 # 定义结构元素 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (4, 4)) # 闭运算填充孔洞 mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 开运算去除噪声 mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 提取黄色病斑区域 yellow_spot_region = cv2.bitwise_and(sharpened_image, sharpened_image, mask=mask)
# 查找病斑轮廓 contours, _ = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 绘制轮廓并计算病斑面积 total_area = 0 contour_image = sharpened_image.copy() for contour in contours: area = cv2.contourArea(contour) total_area += area cv2.drawContours(contour_image, [contour], -1, (0, 255, 0), 2) sharpened_image_array = cv2.cvtColor(sharpened_image, cv2.COLOR_BGR2RGB) mask_array = cv2.cvtColor(mask, cv2.COLOR_GRAY2RGB) yellow_spot_region_array = cv2.cvtColor(yellow_spot_region, cv2.COLOR_BGR2RGB) contour_image_array = cv2.cvtColor(contour_image, cv2.COLOR_BGR2RGB) return sharpened_image_array, mask_array, yellow_spot_region_array, contour_image_array
if __name__ == "__main__": # 创建 Gradio 界面 iface = gr.Interface( fn=process_image, inputs=gr.Image(type="numpy"), outputs=[ gr.Image(type="numpy", label="Sobel Sharpened Image"), gr.Image(type="numpy", label="Mask for Yellow Spots"), gr.Image(type="numpy", label="Yellow Spot Region"), gr.Image(type="numpy", label="Yellow Spot Region with Contours") ], title="Disease Spot Detection in Images" )
# 启动 Gradio 界面 iface.launch() |
5:课程学习心得
(1)整体心得
在《数据图像处理》课程的学习中,我通过系统的实验深入掌握了多种图像处理技术,从基础的预处理到复杂的纹理分析与分割方法,逐步构建起对数字图像处理的全方位理解。
实践是检验真理的唯一标准,在课程实践过程中,我遇到了不少问题,也积累了许多宝贵经验。像进行高斯滤波的过程中,起初我仅从理论上知道它是为了平滑图像、减少噪声,但在实际操作中,通过调整不同的参数值,亲眼看到图像从噪点较多变得平滑,才真正理解了其原理和作用。以及将图像从 RGB 转换到 HSV 空间,然后在 HSV 空间中利用特定阈值分割出黄色病斑区域,让我对颜色空间的转换和应用有了全新的认识。此外,对于阈值的分割也较为讲究,可以一开始设置一个较大的范围,然后通过二分法的思想进行范围的缩小,最终合到合适的上边界和下边界的HSV边界。
回顾整个课程学习过程,我认为有效的学习方法至关重要。一方面,要注重理论与实践相结合。先通过理论学习搭建起知识框架,再迅速投入实践,在实践中加深对理论的理解,遇到问题再回归理论找答案,形成良性循环。另一方面,遇到问题多尝试自主解决。像这次代码调试过程中,我通过各种方式,逐步排查问题,不仅解决了当下的难题,还拓宽了自己的知识面和解决问题的能力。
(2)一些尝试和未解决的问题
虽然本实验内容大部分已经完成,但还有一些问题和中间尝试尚未解决,如图38和图39所示。
在图38中,起初是想分割出叶片健康部分后,计算病斑的面积占比,但是最后效果不是很好,就舍弃了。
在图39中,尝试过固定阈值分割、Otsu分割、自适应阈值分割,并得到了一些中间结果。最后权衡方法的效果,主干实验中选择了Otsu分割算法。
图38:中间尝试(1)
图39:中间尝试(2)
PS:这门课会献祭大四的学生作为满绩的分母┑( ̄Д  ̄)┍