基于图像处理和深度学习的黄豆数量检测实验报告

基于图像处理和深度学习的黄豆数量检测实验报告

目录

  • 一、图像处理-查找轮廓的数量检测

    • 1.1 图像增强
    • 1.2 HSV转换
    • 1.3 二值化处理
    • 1.4 中值滤波去噪
    • 1.5 膨胀处理
    • 1.6 腐蚀处理
    • 1.7 计算光影背景
    • 1.8 移除背景
    • 1.9 检测轮廓
    • 1.10 更换图片测试
  • 二、深度学习-YOLO端到端的数量检测(扩展)

一、图像处理-查找轮廓的数量检测

import cv2
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
font_path = './MSYH.TTC'    # 设置中文字体
font_prop = FontProperties(fname=font_path, size=14)

1.1 图像增强

图像增强是图像处理中的一个重要步骤,旨在改善图像的质量、增强图像的细节以及提高图像的视觉效果。本实验中采用了基于LAB颜色空间的图像增强方法,具体步骤如下:

  1. 加载图像:首先使用OpenCV库加载输入图像,将其从默认的BGR格式转换为RGB格式,以便后续处理。

  2. 颜色空间转换:将RGB格式的图像转换为LAB颜色空间,LAB颜色空间由三个通道组成:L表示亮度(Lightness),a表示从绿色到红色的范围,b表示从蓝色到黄色的范围。

  3. 通道分离:在LAB颜色空间中,将图像的L通道、a通道和b通道分离开来,以便分别处理。

  4. 应用CLAHE:对亮度通道(L通道)应用CLAHE算法,即对比度限制自适应直方图均衡化。CLAHE能够增强图像的局部对比度,使细节更加清晰。

  5. 通道合并:将CLAHE增强后的L通道与原始的a通道和b通道合并,形成增强后的LAB图像。

  6. 颜色空间转换:最后将增强后的LAB图像转换回BGR格式,再转换为RGB格式,以便后续显示和保存。

通过对增强图片的观察,显示了对输入图像的增强明显提高了图像的视觉质量和细节清晰度,这能为后续的目标数量检测提供了更好的输入图像。

def enhance_image(image_path):
    # 加载图像
    image = cv2.imread(image_path)
    image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

    # 将图像转换到LAB颜色空间
    lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)

    # 将LAB图像分离成不同的通道
    l, a, b = cv2.split(lab)

    # 对L通道应用CLAHE(对比度限制自适应直方图均衡化)
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
    cl = clahe.apply(l)

    # 将CLAHE增强后的L通道与a和b通道合并
    limg = cv2.merge((cl, a, b))

    # 将LAB图像转换回BGR格式
    enhanced_image = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)
    enhanced_image_rgb = cv2.cvtColor(enhanced_image, cv2.COLOR_BGR2RGB)

    # 临时保存增强后的图像
    enhanced_image_path = "enhanced_bin.jpg"
    # cv2.imwrite(enhanced_image_path, enhanced_image)

    # 返回原始RGB图像、增强后的RGB图像和增强图像的路径
    return image_rgb, enhanced_image_rgb, enhanced_image_path

1.2 HSV颜色空间转换

在图像处理中,HSV(色调、饱和度、明度)颜色空间是一种常用的颜色表示方式,它将颜色的属性分解为三个独立的通道:色调(H)、饱和度(S)和明度(V)。在本实验中,将增强后的图像转换为HSV颜色空间,并分别提取其H、S、V通道,以便进一步分析图像的颜色特性。

  1. 色调(H)通道

色调通道表示颜色的种类或者说是色彩的纯度,以角度度量(通常在0°到360°之间)。在H通道图像中,能够观察到图像中的颜色分布情况。通过直方图可以了解图像中各个颜色的分布密度,从而对图像的色调特征有更深入的认识。

  1. 饱和度(S)通道

饱和度通道表示颜色的浓淡程度,越高代表颜色越饱和,越低代表颜色越灰。在S通道图像中,能够观察到图像中颜色的饱和度分布情况。通过直方图可以了解图像中各个颜色饱和度的分布密度,进而分析图像的色彩鲜艳程度。

  1. 明度(V)通道

明度通道表示颜色的亮度,越高代表颜色越亮,越低代表颜色越暗。在V通道图像中,能够观察到图像中各个像素的亮度分布情况。通过直方图可以了解图像中各个像素亮度的分布密度,有助于分析图像的明暗对比情况。

通过对HSV颜色空间的转换和通道分析,可以更全面地了解图像的颜色特性,并可以选择特征更为显著的通道图像,来作为后续图像处理的输入图像。

# 定义图像的路径
image_path = 'bin.jpg'
img_data = cv2.imread(image_path)

# 增强图像
original_image, enhanced_image, enhanced_image_path = enhance_image(image_path)

# 将增强后的图像转换为HSV颜色空间
img_hsv = cv2.cvtColor(enhanced_image, cv2.COLOR_BGR2HSV)
plt.figure(figsize=(20, 20))

plt.subplot(131)
plt.title('原始图像', fontproperties=font_prop)  # 使用中文标题
plt.imshow(cv2.cvtColor(img_data, cv2.COLOR_BGR2RGB))

plt.subplot(132)
plt.title('增强后的图像', fontproperties=font_prop)  # 使用中文标题
plt.imshow(enhanced_image)

plt.subplot(133)
plt.title('HSV 图像', fontproperties=font_prop)  # 使用中文标题
plt.imshow(img_hsv, cmap='hsv')

plt.show()

在这里插入图片描述

# 分别提取H、S、V通道
h_channel = img_hsv[:, :, 0]
s_channel = img_hsv[:, :, 1]
v_channel = img_hsv[:, :, 2]

# 绘制H通道的图像和直方图
plt.figure(figsize=(12, 6))

plt.subplot(231)
plt.title('H 通道图像', fontproperties=font_prop)
plt.imshow(h_channel, cmap='hsv')

plt.subplot(234)
plt.title('H 通道直方图', fontproperties=font_prop)
arr_h = h_channel.flatten()
plt.hist(arr_h, bins=256, density=True, facecolor='r', edgecolor='r')

# 绘制S通道的图像和直方图
plt.subplot(232)
plt.title('S 通道图像', fontproperties=font_prop) 
plt.imshow(s_channel, cmap='hsv')

plt.subplot(235)
plt.title('S 通道直方图', fontproperties=font_prop) 
arr_s = s_channel.flatten()
plt.hist(arr_s, bins=256, density=True, facecolor='g', edgecolor='g')

# 绘制V通道的图像和直方图
plt.subplot(233)
plt.title('V 通道图像', fontproperties=font_prop) 
plt.imshow(v_channel, cmap='hsv')

plt.subplot(236)
plt.title('V 通道直方图', fontproperties=font_prop) 
arr_v = v_channel.flatten()
plt.hist(arr_v, bins=256, density=True, facecolor='b', edgecolor='b')
plt.tight_layout()
plt.show()

在这里插入图片描述

1.3 二值化处理

在图像处理中,二值化是将图像转换为只有两种颜色的过程,通常用来突出图像中的目标或者减少图像的复杂度。在这里使用V通道的图像进行二值化处理,以便更好地分割出图像中的目标。这里使用了基于阈值的二值化方法:

  1. 计算直方图:首先统计图像V通道的像素值分布情况,生成对应的直方图。

  2. 阈值判定:根据V通道直方图数值的分布情况,选择一个合适的阈值来进行二值化处理。在这里选定阈值为200,该值可以根据具体图像的特性进行调整。

  3. 二值化:对于每个像素,将其V通道的数值与计算得到的阈值进行比较。如果像素的V通道数值大于阈值,则将该像素标记为背景(0),否则标记为目标(1)。

def binarize_v_channel(v_channel, threshold=200):
    rows, cols = v_channel.shape
    labels = np.zeros([rows, cols])
    for i in range(rows):
        for j in range(cols):
            if v_channel[i, j] > threshold:
                labels[i, j] = 1
            else:
                labels[i, j] = 0
    return labels
binary_image = binarize_v_channel(v_channel, threshold=200)
plt.title('V通道二值化后的图像', fontproperties=font_prop)
plt.imshow(binary_image, cmap='gray')
plt.show()

在这里插入图片描述

1.4 中值滤波去噪

在图像处理过程中,噪声是不可避免的,尤其是在二值化处理后,可能会出现一些孤立的噪声点。为了提高图像的质量,应该使用中值滤波进行去噪处理。中值滤波是一种非线性滤波技术,主要用于去除图像中的椒盐噪声。其基本原理是用像素邻域的中值代替当前像素值,从而能够有效地保留边缘信息,同时去除噪声。

具体步骤如下:

  1. 类型转换:首先将二值化后的图像标签矩阵转换为浮点数类型,以便后续处理。

  2. 中值滤波:使用OpenCV库的cv2.medianBlur函数对图像进行中值滤波处理。滤波器的窗口大小(ksize)设为5,表示使用5x5的窗口进行滤波操作。窗口大小应为奇数,以确保中值滤波的正确性。

  3. 显示去噪后的图像:通过灰度图展示去噪后的图像,可以直观地观察到经过中值滤波处理后,图像中的噪声点得到了有效的去除,图像质量得到了明显的提升。

def median_blur(image, ksize=5):
    image = image.astype(np.float32)
    blurred_image = cv2.medianBlur(image, ksize=ksize)

    return blurred_image
    
new_img = median_blur(binary_image, ksize=5)

# 显示去噪后的图像
plt.title('中值滤波去噪后的图像', fontproperties=font_prop)
plt.imshow(new_img, cmap='gray')
plt.show()

在这里插入图片描述

1.5 膨胀处理

图像膨胀是形态学操作中的一种,用于扩展图像中的白色区域(前景),从而填补前景中的小孔并连接相邻的前景区域。在本实验中,我们对去噪后的图像进行了膨胀处理,以进一步增强前景区域的连通性和完整性。

膨胀处理步骤

  1. 定义膨胀核:首先,我们定义一个膨胀核,大小为5x5,元素全为1。膨胀核的大小会影响膨胀效果,通常选择奇数大小的核。

  2. 膨胀操作:使用OpenCV库的cv2.dilate函数对去噪后的图像进行膨胀处理。膨胀操作会将膨胀核覆盖范围内的所有像素设为1(白色),从而扩展前景区域。

  3. 显示膨胀后的图像:通过灰度图展示膨胀后的图像,我们可以直观地观察到经过膨胀处理后,前景区域变得更加连通和完整,填补了小孔并连接了相邻的前景区域,使目标区域更加明显和完整。

def dilate_image(image, kernel_size=(3, 3)):
    dilate_kernel = np.ones(kernel_size, np.uint8)
    dilated_image = cv2.dilate(image, dilate_kernel)
    return dilated_image

img3 = dilate_image(new_img, kernel_size=(3, 3))
# 显示并保存图片
plt.title('膨胀处理后的图像', fontproperties=font_prop)
plt.imshow(img3, cmap='gray')
plt.show()

在这里插入图片描述

1.6 腐蚀处理

在图像处理中,腐蚀是一种形态学操作,用于缩小图像中的白色区域(前景),从而去除前景中的小尖刺和孤立的像素点,使前景区域变得更加紧凑和均匀。

腐蚀处理步骤

  1. 定义腐蚀核:首先,我们定义一个腐蚀核,大小为3x3,元素全为1。腐蚀核的大小会影响腐蚀的程度,通常选择适当大小的核来满足实际需求。

  2. 腐蚀操作:使用OpenCV库的cv2.erode函数对经过膨胀处理后的图像进行腐蚀操作。腐蚀操作会将腐蚀核覆盖范围内的所有像素设为0(黑色),从而缩小前景区域。

  3. 腐蚀结果展示:通过灰度图展示腐蚀处理后的图像,可以直观地观察到前景区域变得更加紧凑和均匀,去除不必要的细节,从而得到更加清晰和连通的目标区域

def erode_image(image, kernel_size=(3, 3)):
    erode_kernel = np.ones(kernel_size, np.uint8)
    eroded_image = cv2.erode(image, erode_kernel)
    return eroded_image
    
img4 = erode_image(img3, kernel_size=(3, 3))

# 显示图片
plt.title('腐蚀处理后的图像', fontproperties=font_prop)
plt.imshow(img4, cmap='gray')
plt.show()

在这里插入图片描述

#开运算

先进行膨胀操作以扩展目标区域,然后进行腐蚀操作以去除不必要的细节,从而达到对目标区域形态的调整和优化的效果。这种组合操作称为开运算(Opening),在图像处理中被广泛应用于去噪、分割和形态学重建等领域。

1.7 计算光影背景

在图像处理中,光影背景是指由于光照条件不均匀而产生的背景亮度变化。在某些场景下,光影背景可能会对目标检测和分割造成影响,因此需要对其进行有效的处理。在本实验中,我们通过计算光影背景的方法,对图像进行了背景亮度的平滑化处理。

计算光影背景步骤:

  1. 定义计算光影背景函数:首先,我们定义了一个名为calculateLightPattern的函数,用于计算图像的光影背景。该函数接收一个图像作为输入,并返回光影背景图像。

  2. 平滑化处理:在计算光影背景时,我们采用了均值滤波的方法对图像进行平滑化处理。通过对图像进行模糊处理,可以有效地减少图像中的高频噪声,从而得到背景亮度的大致分布。

  3. 调整滤波核大小:为了适应不同大小的图像,我们将滤波核的大小设置为图像宽度的1/3,以保证滤波效果的充分覆盖。

  4. 光影背景计算结果展示:通过灰度图展示光影背景,可以观察到图像中背景亮度的大致分布情况。

# 计算光影背景
def calculateLightPattern(img4):
    h, w = img4.shape[0], img4.shape[1]
    img5 = cv2.blur(img4, (int(w/3), int(w/3)))
    return img5

# 第五步:计算光影背景
img5 = calculateLightPattern(img4)

# 显示图片
plt.title('光影背景', fontproperties=font_prop)
plt.imshow(img5, cmap='gray')
plt.tight_layout()
plt.show()

在这里插入图片描述

1.8 移除背景

在目标检测和分割任务中,背景的存在常常会影响到对目标的准确检测。因此,需要对图像进行背景移除处理,以便更好地突出目标区域。在本实验采用了一种基于光影背景的移除方法,通过对原始图像和光影背景的比例或差值计算,得到了移除背景后的图像。

背景移除步骤:

  1. 定义移除背景函数:定义了一个名为removeLight的函数,用于移除图像的光影背景。该函数接收原始图像和光影背景图像作为输入,并返回移除背景后的图像。

  2. 反转图像:首先将原始图像和光影背景图像进行反转,即将目标区域变为白色,背景区域变为黑色,以便后续的计算。

  3. 计算比例或差值:根据所选的移除背景方法,选择计算原始图像与光影背景图像的比例或差值。具体而言,如果方法为1,则计算原始图像与光影背景图像的比例,并根据比例计算出移除背景后的图像;如果方法为2,则直接计算两者之间的差值。

  4. 背景移除结果展示:通过灰度图展示移除背景后的图像,我们可以观察到图像中背景被有效地去除,目标区域得到了突出。

# 移除背景
def removeLight(img4, img5, method):
    # 反转图像,使目标变为白色,背景变为黑色
    img4_inverted = cv2.bitwise_not(img4)
    img5_inverted = cv2.bitwise_not(img5)

    if method == 1:
        img4_32 = np.float32(img4_inverted)
        img5_32 = np.float32(img5_inverted)
        ratio = img4_32 / img5_32
        ratio[ratio > 1] = 1
        aux = 1 - ratio

        # 按比例转换为8bit格式
        aux = aux * 255
        aux = np.uint8(aux)
    else:
        aux = img5_inverted - img4_inverted
    
    # # 反转结果图像,使目标和背景恢复原始颜色
    # aux = cv2.bitwise_not(aux)
    
    return aux

# 第六步:移除背景
aux = removeLight(img4, img5, 1)

plt.title('移除背景后的图像', fontproperties=font_prop)
plt.imshow(aux, cmap='gray')
plt.show()

在这里插入图片描述

1.9 检测轮廓

在图像处理中,检测图像中的轮廓用于识别和分割图像中的目标并计算数量是一种常用的方法。下面采用了两种不同的方法来检测图像中的轮廓:

  1. ConnectedComponents

    • 这种方法利用了图像的连通性质,将图像中的像素按照连通性进行分组,形成了连通组件。
    • 通过 cv2.connectedComponents 函数,我们得到了图像中连通组件的数量以及每个像素所属的连通组件标签。
    • 最后,我们为每个连通组件的像素绘制了随机颜色,从而形成了检测到的连通组件的可视化结果。视化结果。
  2. FindContours

    • 这种方法通过查找图像中的轮廓,来检测图像中的物体边界。
    • 使用 cv2.findContours 函数,我们得到了图像中所有轮廓的列表,每个轮廓是一组表示闭合曲线的点。
    • 最后,通过在图像上绘制这些轮廓,我们可以直观地展示出图像中检测到的物体轮廓。
def ConnectedComponents(aux):
    # 使用cv2.connectedComponents函数检测连通组件
    num_objects, labels = cv2.connectedComponents(aux)

    if num_objects < 2:
        print("connectedComponents未检测到黄豆")
        return
    else:
        print("connectedComponents检测到黄豆的数量为:", num_objects - 1)

    output = np.zeros((aux.shape[0], aux.shape[1], 3), np.uint8)

    for i in range(1, num_objects):
        # 创建与当前对象相对应的掩码
        mask = labels == i
        # 为对象随机分配颜色
        output[:, :, 0][mask] = np.random.randint(0, 255)
        output[:, :, 1][mask] = np.random.randint(0, 255)
        output[:, :, 2][mask] = np.random.randint(0, 255)
    return output

def FindContours(aux):
    # 使用cv2.findContours函数查找轮廓
    contours, hierarchy = cv2.findContours(aux, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if len(contours) == 0:
        print("findContours未检测到黄豆")
        return
    else:
        print("findContours检测到黄豆的数量为:", len(contours))

    output = np.zeros((aux.shape[0], aux.shape[1], 3), np.uint8)
    for i in range(len(contours)):
        # 绘制检测到的轮廓
        cv2.drawContours(
            output,
            contours,
            i,
            (np.random.randint(0, 255),
             np.random.randint(0, 255),
             np.random.randint(0, 255)), 2)
    return output
# 检测轮廓
output1 = ConnectedComponents(aux)
output2 = FindContours(aux)

在这里插入图片描述

def display_images(images, cmap='gray', figsize=(15, 5)):
    
    titles = ['原始图像', 'connectedComponents', 'findContours']
    num_images = len(images)
    
    plt.figure(figsize=figsize)
    for i in range(num_images):
        plt.subplot(1, num_images, i+1)
        if titles:
            plt.title(titles[i], fontproperties=font_prop)
        plt.imshow(images[i], cmap=cmap)
        plt.axis('off')

    plt.tight_layout()
    plt.show()

images = [original_image, output1, output2]

display_images(images)

在这里插入图片描述

1.10 更换图片测试

def process_image(image_path):
    original_image, enhanced_image, enhanced_image_path = enhance_image(image_path)# 图像增强
    img_hsv = cv2.cvtColor(enhanced_image, cv2.COLOR_BGR2HSV)  # 将增强后的图像转换为HSV颜色空间
    v_channel = img_hsv[:, :, 2] # 使用V通道
    binary_image = binarize_v_channel(v_channel, threshold=200) # V通道二值化
    # plt.title('V通道二值化后的图像', fontproperties=font_prop)
    # plt.imshow(binary_image, cmap='gray')
    # plt.show()
    new_img = median_blur(binary_image, ksize=5) #中值滤波去噪
    # plt.title('中值滤波去噪后的图像', fontproperties=font_prop)
    # plt.imshow(binary_image, cmap='gray')
    # plt.show()    
    img3 = dilate_image(new_img, kernel_size=(3, 3)) # 膨胀处理
    img4 = erode_image(img3, kernel_size=(5, 5)) # 腐蚀处理
    img5 = calculateLightPattern(img4) #计算光影背景
    aux = removeLight(img4, img5, 1) # 移除背景
    output1 = ConnectedComponents(aux)
    output2 = FindContours(aux)
    images = [original_image, output1, output2]
    display_images(images)

# 使用函数进行图像处理
image_path_1 = 'bin1.jpg'
process_image(image_path_1)

image_path_2 = 'bin2.jpg'
process_image(image_path_2)

image_path_3 = 'bin3.jpg'
process_image(image_path_3)

在这里插入图片描述

二、深度学习-YOLO端到端的数量检测(扩展)

实验过程

  1. 模型加载:加载YOLOv8官方的模型(yolov8s.pt)。

  2. 目标检测:利用加载的模型对输入图像(bin.jpg)进行了目标检测。因为使用的是官方的模型,并未对其在特定数据集上进行训练微调,所以在这里设置一个较低的置信度阈值(0.04),以便检测到更多的目标。

  3. 结果输出:检测完成后输出检测到的黄豆数量以及检测到的类别名称。根据输出结果,模型成功检测到了18个目标。因为官方模型只会从它自己设定的82个标签中挑选使用,所以这里显示的标签类别为orange。

  4. 结果展示:最后展示检测结果图像,并在图像上显示了检测到的目标及其数量。

from ultralytics import YOLO
from IPython.display import Image
import shutil, os; shutil.rmtree("runs/detect/", ignore_errors=True) if os.path.exists("runs/detect/") else None

# Load the custom model
model = YOLO("yolov8s.pt")

# Set the confidence threshold to a low value to detect more objects
results = model.predict(source="bin.jpg", conf=0.04, save=True,show_labels=False, show_conf=False)

# Output the number of detected objects
print(f"图片中黄豆的数量为: {len(results[0].boxes)}")

# Output the detected categories
categories = [model.names[int(cls)] for cls in results[0].boxes.cls]
print(f"检测到的类别名称为: {categories}")

img_path = 'runs/detect/predict/bin.jpg'
display(Image(filename=img_path))

在这里插入图片描述
下面用的YOLOv8官方的分割模型,实验过程与上面的目标检测近似。

import shutil, os; shutil.rmtree("runs/segment/", ignore_errors=True) if os.path.exists("runs/segment/") else None

model = YOLO("yolov8n-seg.pt")

results = model.predict(source="bin.jpg", conf=0.04, save=True, show_labels=False, show_conf=False, agnostic_nms=True)

print(f"图片中黄豆的数量为: {len(results[0].masks)}")

# Output the detected categories
categories = [model.names[int(cls)] for cls in results[0].boxes.cls]
print(f"检测到的类别名称为: {categories}")

img_path_2 = 'runs/segment/predict/bin.jpg'
display(Image(filename=img_path_2))

在这里插入图片描述
以下是文件结构:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值