From 6a063345c78735079f5bd4f09ee146def1551ae3 Mon Sep 17 00:00:00 2001 From: GG <905865530@qq.com> Date: Fri, 7 Jun 2024 01:48:52 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E6=96=B0=E5=A2=9E=E7=99=BE?= =?UTF-8?q?=E9=A6=99=E6=9E=9Crgb=E9=83=A8=E5=88=86=EF=BC=8C=E5=A4=84?= =?UTF-8?q?=E7=90=86=E6=AD=A5=E9=AA=A4=E5=86=99=E4=B8=BA=E7=B1=BB=EF=BC=8C?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BA=86main.py=E4=BB=A3=E7=A0=81=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=88=E7=9B=AE=E5=89=8D=E7=BC=BA=E5=B0=91=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=88=87=E6=8D=A2=E6=8C=87=E4=BB=A4=E9=83=A8=E5=88=86?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 7 ++ 20240529RGBtest3/classifer.py | 72 ++++++++++++ 20240529RGBtest3/main.py | 10 +- 20240529RGBtest3/utils.py | 212 ++++++++++++++++++++++++++-------- 4 files changed, 252 insertions(+), 49 deletions(-) diff --git a/.gitignore b/.gitignore index ea549ea..18249b7 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,10 @@ fabric.properties !/20240410RGBtest1/super-tomato/defect_big.bmp !/20240410RGBtest1/super-tomato/defect_mask.bmp !/20240410RGBtest1/defect_big.bmp +!/20240529RGBtest3/原图108测试过程图/ +!/20240529RGBtest3/测试1.png +!/20240529RGBtest3/测试2.png +!/20240410RGBtest1/super-tomato/defect.bmp +!/20240410RGBtest1/super-tomato/defect_big.bmp +!/20240410RGBtest1/super-tomato/defect_mask.bmp +!/20240410RGBtest1/super-tomato/prediction.png diff --git a/20240529RGBtest3/classifer.py b/20240529RGBtest3/classifer.py index ad2492a..f2f45e0 100644 --- a/20240529RGBtest3/classifer.py +++ b/20240529RGBtest3/classifer.py @@ -222,3 +222,75 @@ class Tomato: img_filled_inv = cv2.bitwise_not(img_filled) img_filled = cv2.bitwise_or(new_bin_img, img_filled_inv) return img_filled + + +class Passion_fruit: + def __init__(self, hue_value=37, hue_delta=10, value_target=25, value_delta=10): + # 初始化常用参数 + self.hue_value = hue_value + self.hue_delta = hue_delta + self.value_target = value_target + self.value_delta = value_delta + + def create_mask(self, hsv_image): + # 创建H通道阈值掩码 + lower_hue = np.array([self.hue_value - self.hue_delta, 0, 0]) + upper_hue = np.array([self.hue_value + self.hue_delta, 255, 255]) + hue_mask = cv2.inRange(hsv_image, lower_hue, upper_hue) + + # 创建V通道排除中心值的掩码 + lower_value_1 = np.array([0, 0, 0]) + upper_value_1 = np.array([180, 255, self.value_target - self.value_delta]) + lower_value_2 = np.array([0, 0, self.value_target + self.value_delta]) + upper_value_2 = np.array([180, 255, 255]) + + value_mask_1 = cv2.inRange(hsv_image, lower_value_1, upper_value_1) + value_mask_1 = cv2.bitwise_not(value_mask_1) + value_mask_2 = cv2.inRange(hsv_image, lower_value_2, upper_value_2) + value_mask = cv2.bitwise_and(value_mask_1, value_mask_2) + + # 合并H通道和V通道掩码 + return cv2.bitwise_and(hue_mask, value_mask) + + def apply_morphology(self, mask): + # 应用形态学操作 + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) + return cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) + + def find_largest_component(self, mask): + # 寻找最大连通组件 + num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(mask, 4, cv2.CV_32S) + if num_labels < 2: + return None # 没有找到显著的组件 + max_label = 1 + np.argmax(stats[1:, cv2.CC_STAT_AREA]) # 跳过背景 + return (labels == max_label).astype(np.uint8) * 255 + def draw_contours_on_image(self, original_image, mask_image): + """ + 在原图上绘制轮廓 + :param original_image: 原图的NumPy数组 + :param mask_image: 轮廓mask的NumPy数组 + :return: 在原图上绘制轮廓后的图像 + """ + # 确保mask_image是二值图像 + _, binary_mask = cv2.threshold(mask_image, 127, 255, cv2.THRESH_BINARY) + + # 查找mask图像中的轮廓 + contours, _ = cv2.findContours(binary_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + # 在原图上绘制轮廓 + cv2.drawContours(original_image, contours, -1, (0, 255, 0), 2) + + return original_image + + def bitwise_and_rgb_with_binary(self, rgb_img, bin_img): + ''' + 将 RGB 图像与二值图像进行按位与操作,用于将二值区域应用于原始图像。 + :param rgb_img: 原始 RGB 图像 + :param bin_img: 二值图像 + :return: 按位与后的结果图像 + ''' + bin_img_3channel = cv2.cvtColor(bin_img, cv2.COLOR_GRAY2BGR) + result = cv2.bitwise_and(rgb_img, bin_img_3channel) + return result + + diff --git a/20240529RGBtest3/main.py b/20240529RGBtest3/main.py index 6cef35a..2a254a9 100644 --- a/20240529RGBtest3/main.py +++ b/20240529RGBtest3/main.py @@ -18,7 +18,7 @@ import time import os from root_dir import ROOT_DIR import logging -from utils import create_pipes, receive_rgb_data, send_data, receive_spec_data, analyze_tomato +from utils import create_pipes, receive_rgb_data, send_data, receive_spec_data, analyze_tomato, analyze_passion_fruit from collections import deque import time import io @@ -36,9 +36,13 @@ def process_data(img: any) -> tuple: :param detector: 模型 :return: 是否处理成功 """ - # if cmd == 'IM': + if cmd == 'TO': + # 番茄 + long_axis, short_axis, number_defects, total_pixels, rp = analyze_tomato(img) + elif cmd == 'PF': + # 百香果 + long_axis, short_axis, number_defects, total_pixels, rp = analyze_passion_fruit(img) - long_axis, short_axis, number_defects, total_pixels, rp = analyze_tomato(img) return long_axis, short_axis, number_defects, total_pixels, rp diff --git a/20240529RGBtest3/utils.py b/20240529RGBtest3/utils.py index 5a73307..8eb80dd 100644 --- a/20240529RGBtest3/utils.py +++ b/20240529RGBtest3/utils.py @@ -17,7 +17,7 @@ import io from PIL import Image import select import msvcrt -from classifer import Tomato +from classifer import Tomato, Passion_fruit def receive_rgb_data(pipe): try: @@ -200,59 +200,153 @@ class Logger(object): print(content) +def contour_process(image_array): + # 应用中值滤波 + image_filtered = cv2.medianBlur(image_array, 5) + + # 形态学闭操作 + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15)) + image_closed = cv2.morphologyEx(image_filtered, cv2.MORPH_CLOSE, kernel) + + # 查找轮廓 + contours, _ = cv2.findContours(image_closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + # 创建空白图像以绘制轮廓 + image_contours = np.zeros_like(image_array) + + # 进行多边形拟合并填充轮廓 + for contour in contours: + epsilon = 0.001 * cv2.arcLength(contour, True) + approx = cv2.approxPolyDP(contour, epsilon, True) + if cv2.contourArea(approx) > 100: # 仅处理较大的轮廓 + cv2.drawContours(image_contours, [approx], -1, (255, 255, 255), -1) + + return image_contours + +# def get_tomato_dimensions(edge_img): +# """ +# 根据边缘二值化轮廓图,计算果子的长径、短径和长短径比值。 +# 使用最小外接矩形和最小外接圆两种方法。 +# +# 参数: +# edge_img (numpy.ndarray): 边缘二值化轮廓图,背景为黑色,番茄区域为白色。 +# +# 返回: +# tuple: (长径, 短径, 长短径比值) +# """ +# if edge_img is None or edge_img.any() == 0: +# return (0, 0) +# # 最小外接矩形 +# rect = cv2.minAreaRect(cv2.findContours(edge_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0][0]) +# major_axis, minor_axis = rect[1] +# # aspect_ratio = max(major_axis, minor_axis) / min(major_axis, minor_axis) +# +# # # 最小外接圆 +# # (x, y), radius = cv2.minEnclosingCircle( +# # cv2.findContours(edge_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0][0]) +# # diameter = 2 * radius +# # aspect_ratio_circle = 1.0 +# +# return (max(major_axis, minor_axis), min(major_axis, minor_axis)) + +# def get_defect_info(defect_img): +# """ +# 根据区域缺陷二值化轮廓图,计算缺陷区域的个数和总面积。 +# +# 参数: +# defect_img (numpy.ndarray): 番茄区域缺陷二值化轮廓图,背景为黑色,番茄区域为白色,缺陷区域为黑色连通域。 +# +# 返回: +# tuple: (缺陷区域个数, 缺陷区域像素面积,缺陷像素总面积) +# """ +# # 检查输入是否为空 +# if defect_img is None or defect_img.any() == 0: +# return (0, 0) +# +# nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(defect_img, connectivity=4) +# max_area = max(stats[i, cv2.CC_STAT_AREA] for i in range(1, nb_components)) +# areas = [] +# for i in range(1, nb_components): +# area = stats[i, cv2.CC_STAT_AREA] +# if area != max_area: +# areas.append(area) +# number_defects = len(areas) +# total_pixels = sum(areas) +# return number_defects, total_pixels + +def analyze_ellipse(image_array): + # 查找白色区域的轮廓 + _, binary_image = cv2.threshold(image_array, 127, 255, cv2.THRESH_BINARY) + contours, _ = cv2.findContours(binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + # 初始化变量用于存储最大轮廓的长径和短径 + major_axis = 0 + minor_axis = 0 + + # 对每个找到的轮廓,找出可以包围它的最小椭圆,并计算长径和短径 + for contour in contours: + if len(contour) >= 5: # 至少需要5个点来拟合椭圆 + ellipse = cv2.fitEllipse(contour) + (center, axes, orientation) = ellipse + major_axis0 = max(axes) + minor_axis0 = min(axes) + # 更新最大的长径和短径 + if major_axis0 > major_axis: + major_axis = major_axis0 + minor_axis = minor_axis0 + + return major_axis, minor_axis + +# 示例用法 +# image_array = cv2.imread('path_to_your_image.bmp', cv2.IMREAD_GRAYSCALE) +# major_axis, minor_axis = analyze_ellipse(image_array) +# print(f"Major Axis: {major_axis}, Minor Axis: {minor_axis}") + +# 加载新上传的图像进行分析 +new_ellipse_image_path = '/mnt/data/未标题-2.png' +new_ellipse_image = cv2.imread(new_ellipse_image_path, cv2.IMREAD_GRAYSCALE) + +# 使用上述函数进行分析 +analyze_ellipse(new_ellipse_image) -def get_tomato_dimensions(edge_img): - """ - 根据边缘二值化轮廓图,计算果子的长径、短径和长短径比值。 - 使用最小外接矩形和最小外接圆两种方法。 +def analyze_defect(image_array): + # 查找白色区域的轮廓 + _, binary_image = cv2.threshold(image_array, 127, 255, cv2.THRESH_BINARY) + contours_white, _ = cv2.findContours(binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) - 参数: - edge_img (numpy.ndarray): 边缘二值化轮廓图,背景为黑色,番茄区域为白色。 + # 初始化统计数据 + count_black_areas = 0 + total_pixels_black_areas = 0 - 返回: - tuple: (长径, 短径, 长短径比值) - """ - if edge_img is None or edge_img.any() == 0: - return (0, 0) - # 最小外接矩形 - rect = cv2.minAreaRect(cv2.findContours(edge_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0][0]) - major_axis, minor_axis = rect[1] - # aspect_ratio = max(major_axis, minor_axis) / min(major_axis, minor_axis) + # 对于每个白色区域,查找内部的黑色小区域 + for contour in contours_white: + # 创建一个mask以查找内部的黑色区域 + mask = np.zeros_like(image_array) + cv2.drawContours(mask, [contour], -1, 255, -1) - # # 最小外接圆 - # (x, y), radius = cv2.minEnclosingCircle( - # cv2.findContours(edge_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0][0]) - # diameter = 2 * radius - # aspect_ratio_circle = 1.0 + # 仅在白色轮廓内部查找黑色区域 + black_areas_inside = cv2.bitwise_and(cv2.bitwise_not(image_array), mask) - return (max(major_axis, minor_axis), min(major_axis, minor_axis)) + # 查找黑色区域的轮廓 + contours_black, _ = cv2.findContours(black_areas_inside, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + count_black_areas += len(contours_black) -def get_defect_info(defect_img): - """ - 根据区域缺陷二值化轮廓图,计算缺陷区域的个数和总面积。 + # 计算黑色区域的总像素数 + for c in contours_black: + total_pixels_black_areas += cv2.contourArea(c) - 参数: - defect_img (numpy.ndarray): 番茄区域缺陷二值化轮廓图,背景为黑色,番茄区域为白色,缺陷区域为黑色连通域。 - - 返回: - tuple: (缺陷区域个数, 缺陷区域像素面积,缺陷像素总面积) - """ - # 检查输入是否为空 - if defect_img is None or defect_img.any() == 0: - return (0, 0) - - nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(defect_img, connectivity=4) - max_area = max(stats[i, cv2.CC_STAT_AREA] for i in range(1, nb_components)) - areas = [] - for i in range(1, nb_components): - area = stats[i, cv2.CC_STAT_AREA] - if area != max_area: - areas.append(area) - number_defects = len(areas) - total_pixels = sum(areas) + number_defects = count_black_areas + total_pixels = total_pixels_black_areas return number_defects, total_pixels + +# 示例用法 +# image_array = cv2.imread('path_to_your_image.bmp', cv2.IMREAD_GRAYSCALE) +# black_areas_count, total_pixels = analyze_black_areas_in_white(image_array) +# print(f"Number of black areas: {black_areas_count}, Total pixels in black areas: { + + def analyze_tomato(img): """ 分析给定图像,提取和返回西红柿的长径、短径、缺陷数量和缺陷总面积,并返回处理后的图像。 @@ -272,9 +366,35 @@ def analyze_tomato(img): edge, mask = tomato.draw_tomato_edge(img, new_bin_img) org_defect = tomato.bitwise_and_rgb_with_binary(edge, new_bin_img) # 获取西红柿的尺寸信息 - long_axis, short_axis = get_tomato_dimensions(mask) + long_axis, short_axis = analyze_ellipse(mask) # 获取缺陷信息 - number_defects, total_pixels = get_defect_info(new_bin_img) + number_defects, total_pixels = analyze_defect(new_bin_img) # 将处理后的图像转换为 RGB 格式 rp = cv2.cvtColor(org_defect, cv2.COLOR_BGR2RGB) return long_axis, short_axis, number_defects, total_pixels, rp + + +def analyze_passion_fruit(img, hue_value=37, hue_delta=10, value_target=25, value_delta=10): + if img is None: + print("Error: 无图像数据.") + return None + + # 创建PassionFruit类的实例 + pf = Passion_fruit(hue_value=hue_value, hue_delta=hue_delta, value_target=value_target, value_delta=value_delta) + + hsv_image = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) + combined_mask = pf.create_mask(hsv_image) + combined_mask = pf.apply_morphology(combined_mask) + max_mask = pf.find_largest_component(combined_mask) + + # if max_mask is None: + # print("No significant components found.") + # return None + contour_mask = contour_process(max_mask) + long_axis, short_axis = analyze_ellipse(contour_mask) + number_defects, total_pixels = analyze_defect(max_mask) + edge = pf.draw_contours_on_image(img, contour_mask) + org_defect = pf.bitwise_and_rgb_with_binary(edge, max_mask) + rp = cv2.cvtColor(org_defect, cv2.COLOR_BGR2RGB) + + return long_axis, short_axis, number_defects, total_pixels, rp