diff --git a/20240529RGBtest3/classifer.py b/20240529RGBtest3/classifer.py index e53cb73..89dbedf 100644 --- a/20240529RGBtest3/classifer.py +++ b/20240529RGBtest3/classifer.py @@ -317,6 +317,40 @@ class Passion_fruit: return np.zeros_like(rgb_img) return result + def extract_green_pixels_cv(self,image): + ''' + 提取图像中的绿色像素。 + :param image: + :return: + ''' + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + # Define the HSV range for green + lower_green = np.array([setting.low_H, setting.low_S, setting.low_V]) + upper_green = np.array([setting.high_H, setting.high_S, setting.high_V]) + # Convert the image to HSV + hsv = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2HSV) + # Create the mask + mask = cv2.inRange(hsv, lower_green, upper_green) + # Bitwise-AND mask and original image + res = cv2.bitwise_and(image_rgb, image_rgb, mask=mask) + # Convert result to BGR for display + res_bgr = cv2.cvtColor(res, cv2.COLOR_RGB2BGR) + return mask + + def pixel_comparison(self, defect, mask): + ''' + 比较两幅图像的像素值,如果相同则赋值为0,不同则赋值为255。 + :param defect: + :param mask: + :return: + ''' + # 确保图像是二值图像 + _, defect_binary = cv2.threshold(defect, 127, 255, cv2.THRESH_BINARY) + _, mask_binary = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY) + # 执行像素比较 + green_img = np.where(defect_binary == mask_binary, 0, 255).astype(np.uint8) + return green_img + #糖度预测模型 class Spec_predict(object): def __init__(self, load_from=None, debug_mode=False): @@ -390,35 +424,28 @@ class Data_processing: return np.zeros_like(image_array) if image_array is not None else np.zeros((100, 100), dtype=np.uint8) # 应用中值滤波 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 analyze_ellipse(self, 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个点来拟合椭圆 @@ -586,10 +613,13 @@ class Data_processing: combined_mask = pf.create_mask(hsv_image) combined_mask = pf.apply_morphology(combined_mask) max_mask = pf.find_largest_component(combined_mask) - filled_img, defect = self.fill_holes(max_mask) - contour_mask = self.contour_process(max_mask) + fore = pf.bitwise_and_rgb_with_binary(img, contour_mask) + mask = pf.extract_green_pixels_cv(fore) + green_img = pf.pixel_comparison(defect, mask) + green_percentage = np.sum(green_img == 255) / np.sum(contour_mask == 255) + green_percentage = round(green_percentage, 2) long_axis, short_axis = self.analyze_ellipse(contour_mask) #重量单位为g,加上了一点随机数 weight_real = self.weight_estimates(long_axis, short_axis) @@ -608,12 +638,13 @@ class Data_processing: # print(f'直径:{diameter}') if diameter < 2.5: diameter = 0 + green_percentage = 0 weight = 0 number_defects = 0 total_pixels = 0 rp = cv2.cvtColor(np.ones((setting.n_rgb_rows, setting.n_rgb_cols, setting.n_rgb_bands), dtype=np.uint8), cv2.COLOR_BGR2RGB) - return diameter, weight, number_defects, total_pixels, rp + return diameter, green_percentage, weight, number_defects, total_pixels, rp def process_data(seif, cmd: str, images: list, spec: any, pipe: Pipe, detector: Spec_predict) -> bool: """ @@ -644,7 +675,7 @@ class Data_processing: elif cmd == 'PF': # 百香果 - diameter, weight, number_defects, total_pixels, rp = seif.analyze_passion_fruit(img) + diameter, green_percentage, weight, number_defects, total_pixels, rp = seif.analyze_passion_fruit(img) if i <= 2: diameter_axis_list.append(diameter) max_defect_num = max(max_defect_num, number_defects) @@ -652,6 +683,7 @@ class Data_processing: if i == 1: rp_result = rp weight = weight + gp = round(green_percentage, 2) else: logging.error(f'错误指令,指令为{cmd}') @@ -668,14 +700,12 @@ class Data_processing: defect_num=max_defect_num, total_defect_area=max_total_defect_area, rp=rp_result) return response elif cmd == 'PF': - green_percentage = 0 brix = detector.predict(spec) if diameter == 0: brix = 0 # print(f'预测的brix值为:{brix}; 预测的直径为:{diameter}; 预测的重量为:{weight}; 预测的绿色比例为:{green_percentage};' # f' 预测的缺陷数量为:{max_defect_num}; 预测的总缺陷面积为:{max_total_defect_area};') - response = pipe.send_data(cmd=cmd, brix=brix, green_percentage=green_percentage, diameter=diameter, - weight=weight, + response = pipe.send_data(cmd=cmd, brix=brix, green_percentage=gp, diameter=diameter, weight=weight, defect_num=max_defect_num, total_defect_area=max_total_defect_area, rp=rp_result) return response diff --git a/20240529RGBtest3/config.py b/20240529RGBtest3/config.py index c71f3e0..f5f2662 100644 --- a/20240529RGBtest3/config.py +++ b/20240529RGBtest3/config.py @@ -32,6 +32,14 @@ class Config: value_target = 25 value_delta = 10 + #提取绿色像素参数 + low_H = 0 + low_S = 100 + low_V = 0 + high_H = 60 + high_S = 180 + high_V = 60 + #spec_predict #筛选谱段并未使用,在qt取数据时已经筛选 selected_bands = [8, 9, 10, 48, 49, 50, 77, 80, 103, 108, 115, 143, 145] diff --git a/20240529RGBtest3/tg/config.py b/20240529RGBtest3/tg/config.py new file mode 100644 index 0000000..c71f3e0 --- /dev/null +++ b/20240529RGBtest3/tg/config.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# @Time : 2024/6/17 下午3:36 +# @Author : TG +# @File : config.py +# @Software: PyCharm + +from root_dir import ROOT_DIR + +class Config: + #文件相关参数 + #预热参数 + n_spec_rows, n_spec_cols, n_spec_bands = 25, 30, 13 + n_rgb_rows, n_rgb_cols, n_rgb_bands = 613, 800, 3 + tomato_img_dir = ROOT_DIR / 'models' / 'TO.bmp' + passion_fruit_img_dir = ROOT_DIR / 'models' / 'PF.bmp' + #模型路径 + #糖度模型 + brix_model_path = ROOT_DIR / 'models' / 'passion_fruit.joblib' + #图像分类模型 + imgclassifier_model_path = ROOT_DIR / 'models' / 'imgclassifier.joblib' + imgclassifier_class_indices_path = ROOT_DIR / 'models' / 'class_indices.json' + + + #classifer.py参数 + #tomato + find_reflection_threshold = 190 + extract_g_r_factor = 1.5 + + #passion_fruit + hue_value = 37 + hue_delta = 10 + value_target = 25 + value_delta = 10 + + #spec_predict + #筛选谱段并未使用,在qt取数据时已经筛选 + selected_bands = [8, 9, 10, 48, 49, 50, 77, 80, 103, 108, 115, 143, 145] + + #data_processing + #根据标定数据计算的参数,实际长度/像素长度,单位cm + pixel_length_ratio = 6.3/425 + #绿叶面积阈值,高于此阈值认为连通域是绿叶 + area_threshold = 20000 + #百香果密度(g/cm^3) + density = 0.652228972 + #百香果面积比例,每个像素代表的实际面积(cm^2) + area_ratio = 0.00021973702422145334 + + #def analyze_tomato + #s_l通道阈值 + threshold_s_l = 180 + threshold_fore_g_r_t = 20 + diff --git a/20240529RGBtest3/tg/passion_fruit_rgb.py b/20240529RGBtest3/tg/passion_fruit_rgb.py new file mode 100644 index 0000000..8e1e090 --- /dev/null +++ b/20240529RGBtest3/tg/passion_fruit_rgb.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- +# @Time : 2024/6/26 下午5:31 +# @Author : TG +# @File : passion_fruit_rgb.py +# @Software: PyCharm +import os +import cv2 +import numpy as np +import argparse +import logging +from config import Config as setting + +class Passion_fruit: + def __init__(self, hue_value=setting.hue_value, hue_delta=setting.hue_delta, + value_target=setting.value_target, value_delta=setting.value_delta): + # 初始化常用参数 + 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): + if mask is None or mask.size == 0 or np.all(mask == 0): + logging.warning("RGB 图像为空或全黑,返回一个全黑RGB图像。") + return np.zeros((setting.n_rgb_rows, setting.n_rgb_cols, setting.n_rgb_bands), dtype=np.uint8) \ + if mask is None else np.zeros_like(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: 按位与后的结果图像 + ''' + # 检查 RGB 图像是否为空或全黑 + if rgb_img is None or rgb_img.size == 0 or np.all(rgb_img == 0): + logging.warning("RGB 图像为空或全黑,返回一个全黑RGB图像。") + return np.zeros((setting.n_rgb_rows, setting.n_rgb_cols, setting.n_rgb_bands), dtype=np.uint8) \ + if rgb_img is None else np.zeros_like(rgb_img) + # 检查二值图像是否为空或全黑 + if bin_img is None or bin_img.size == 0 or np.all(bin_img == 0): + logging.warning("二值图像为空或全黑,返回一个全黑RGB图像。") + return np.zeros((setting.n_rgb_rows, setting.n_rgb_cols, setting.n_rgb_bands), dtype=np.uint8) \ + if bin_img is None else np.zeros_like(bin_img) + # 转换二值图像为三通道 + try: + bin_img_3channel = cv2.cvtColor(bin_img, cv2.COLOR_GRAY2BGR) + except cv2.error as e: + logging.error(f"转换二值图像时发生错误: {e}") + return np.zeros_like(rgb_img) + # 进行按位与操作 + try: + result = cv2.bitwise_and(rgb_img, bin_img_3channel) + except cv2.error as e: + logging.error(f"执行按位与操作时发生错误: {e}") + return np.zeros_like(rgb_img) + return result + + + +def fill_holes(bin_img): + + img_filled = bin_img.copy() + height, width = bin_img.shape + mask = np.zeros((height + 2, width + 2), np.uint8) + cv2.floodFill(img_filled, mask, (0, 0), 255) + img_filled_inv = cv2.bitwise_not(img_filled) + img_filled = cv2.bitwise_or(bin_img, img_filled) + img_defect = img_filled_inv[:height, :width] + return img_filled, img_defect + +def contour_process(image_array): + # 检查图像是否为空或全黑 + if image_array is None or image_array.size == 0 or np.all(image_array == 0): + logging.warning("输入的图像为空或全黑,返回一个全黑图像。") + return np.zeros_like(image_array) if image_array is not None else np.zeros((100, 100), dtype=np.uint8) + # 应用中值滤波 + 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 extract_green_pixels_cv(image): + """ + 使用 OpenCV 提取图像中的绿色像素,并可选择保存结果图像。 + + 参数: + image_path (str): 输入图像的文件路径。 + save_path (str, optional): 输出图像的保存路径,若提供此参数,则保存提取的绿色像素图像。 + + 返回: + 输出图像,绿色像素为白色,其他像素为黑色。 + """ + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + # Define the HSV range for green + lower_green = np.array([0, 100, 0]) + upper_green = np.array([60, 180, 60]) + # Convert the image to HSV + hsv = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2HSV) + # Create the mask + mask = cv2.inRange(hsv, lower_green, upper_green) + # Bitwise-AND mask and original image + res = cv2.bitwise_and(image_rgb, image_rgb, mask=mask) + # Convert result to BGR for display + res_bgr = cv2.cvtColor(res, cv2.COLOR_RGB2BGR) + return mask + + +def pixel_comparison(defect, mask): + """ + 比较两幅图像的像素值,如果相同则赋值为0,不同则赋值为255。 + 参数: + defect_path (str): 第一幅图像的路径。 + mask_path (str): 第二幅图像的路径。 + save_path (str, optional): 结果图像的保存路径。 + 返回: + numpy.ndarray: 处理后的图像数组。 + """ + # 确保图像是二值图像 + _, defect_binary = cv2.threshold(defect, 127, 255, cv2.THRESH_BINARY) + _, mask_binary = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY) + # 执行像素比较 + green_img = np.where(defect_binary == mask_binary, 0, 255).astype(np.uint8) + return green_img + + +def main(): + parser = argparse.ArgumentParser(description='Process some integers.') + parser.add_argument('--dir_path', type=str, + default=r'D:\project\supermachine--tomato-passion_fruit\20240529RGBtest3\tg\test', + help='the directory path of images') + parser.add_argument('--threshold_s_l', type=int, default=180, + help='the threshold for s_l') + parser.add_argument('--threshold_r_b', type=int, default=15, + help='the threshold for r_b') + + args = parser.parse_args() + pf = Passion_fruit() + + for img_file in os.listdir(args.dir_path): + if img_file.endswith('.bmp'): + img_path = os.path.join(args.dir_path, img_file) + img = cv2.imread(img_path) + cv2.imshow('img', img) + hsv_image = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) + cv2.imshow('hsv', hsv_image) + combined_mask = pf.create_mask(hsv_image) + cv2.imshow('combined_mask1', combined_mask) + combined_mask = pf.apply_morphology(combined_mask) + cv2.imshow('combined_mask2', combined_mask) + max_mask = pf.find_largest_component(combined_mask) + cv2.imshow('max_mask', max_mask) + + filled_img, defect = fill_holes(max_mask) + cv2.imshow('filled_img', filled_img) + cv2.imshow('defect', defect) + + contour_mask = contour_process(max_mask) + cv2.imshow('contour_mask', contour_mask) + + fore = pf.bitwise_and_rgb_with_binary(img, contour_mask) + cv2.imshow('fore', fore) + + mask = extract_green_pixels_cv(fore) + cv2.imshow('mask', mask) + + green_img = pixel_comparison(defect, mask) + cv2.imshow('green_img', green_img) + + green_percentage = np.sum(green_img == 255) / np.sum(contour_mask == 255) + green_percentage = round(green_percentage, 2) + + print(np.sum(green_img == 255)) + print(np.sum(contour_mask == 255)) + print(green_percentage) + + + + + edge = pf.draw_contours_on_image(img, contour_mask) + cv2.imshow('edge', edge) + org_defect = pf.bitwise_and_rgb_with_binary(edge, max_mask) + cv2.imshow('org_defect', org_defect) + + + cv2.waitKey(0) + cv2.destroyAllWindows() + + + + + + + + + + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/20240529RGBtest3/tg/t.py b/20240529RGBtest3/tg/t.py new file mode 100644 index 0000000..b400314 --- /dev/null +++ b/20240529RGBtest3/tg/t.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# @Time : 2024/6/26 下午6:15 +# @Author : TG +# @File : t.py +# @Software: PyCharm +import cv2 +import numpy as np + + +def nothing(x): + pass + + +# Create a window +cv2.namedWindow('Green Pixels Selector') + +# Create trackbars for color change +cv2.createTrackbar('Lower Hue', 'Green Pixels Selector', 0, 255, nothing) +cv2.createTrackbar('Lower Sat', 'Green Pixels Selector', 100, 255, nothing) +cv2.createTrackbar('Lower Val', 'Green Pixels Selector', 0, 255, nothing) +cv2.createTrackbar('Upper Hue', 'Green Pixels Selector', 60, 255, nothing) +cv2.createTrackbar('Upper Sat', 'Green Pixels Selector', 180, 255, nothing) +cv2.createTrackbar('Upper Val', 'Green Pixels Selector', 60, 255, nothing) + +# Load image +image = cv2.imread(r'D:\project\supermachine--tomato-passion_fruit\20240529RGBtest3\tg\test\23.bmp') +image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + +while (True): + # Get current positions of the trackbars + lh = cv2.getTrackbarPos('Lower Hue', 'Green Pixels Selector') + ls = cv2.getTrackbarPos('Lower Sat', 'Green Pixels Selector') + lv = cv2.getTrackbarPos('Lower Val', 'Green Pixels Selector') + uh = cv2.getTrackbarPos('Upper Hue', 'Green Pixels Selector') + us = cv2.getTrackbarPos('Upper Sat', 'Green Pixels Selector') + uv = cv2.getTrackbarPos('Upper Val', 'Green Pixels Selector') + + # Define the HSV range for green + lower_green = np.array([lh, ls, lv]) + upper_green = np.array([uh, us, uv]) + + # Convert the image to HSV + hsv = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2HSV) + + # Create the mask + mask = cv2.inRange(hsv, lower_green, upper_green) + + # Bitwise-AND mask and original image + res = cv2.bitwise_and(image_rgb, image_rgb, mask=mask) + + # Convert result to BGR for display + res_bgr = cv2.cvtColor(res, cv2.COLOR_RGB2BGR) + + # Display the resulting frame + cv2.imshow('Green Pixels Selector', res_bgr) + + if cv2.waitKey(1) & 0xFF == ord('q'): + break + +# When everything done, release the window +cv2.destroyAllWindows() diff --git a/20240529RGBtest3/utils.py b/20240529RGBtest3/utils.py index 0568b10..5a50c8e 100644 --- a/20240529RGBtest3/utils.py +++ b/20240529RGBtest3/utils.py @@ -209,8 +209,7 @@ class Pipe: defect_num + total_defect_area + height + width + img_bytes) elif cmd == 'PF': brix = int(brix * 1000).to_bytes(2, byteorder='big') - gp = 0 - gp = gp.to_bytes(1, byteorder='big') + gp = int(green_percentage * 100).to_bytes(1, byteorder='big') weight = weight.to_bytes(1, byteorder='big') send_message = (length + cmd_re + brix + gp + diameter + weight + defect_num + total_defect_area + height + width + img_bytes)