diff --git a/20240529RGBtest3/classifer.py b/20240529RGBtest3/classifer.py index 0bcd590..cd6a06e 100644 --- a/20240529RGBtest3/classifer.py +++ b/20240529RGBtest3/classifer.py @@ -189,19 +189,6 @@ class Tomato: cv2.drawContours(original_img, [hull], -1, (0, 255, 0), 3) return original_img - def fill_holes(self, bin_img): - ''' - 使用 floodFill 算法填充图像中的孔洞。 - :param bin_img: 输入的二值图像 - :return: 填充后的图像 - ''' - 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_inv) - return img_filled def bitwise_and_rgb_with_binary(self, rgb_img, bin_img): ''' @@ -269,6 +256,9 @@ class Passion_fruit: 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((100, 100, 3), 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: @@ -357,9 +347,32 @@ class Spec_predict(object): #数据处理模型 class Data_processing: - def __init__(self): + def __init__(self, area_threshold=20000, density = 0.652228972, area_ratio=0.00021973702422145334): + ''' + :param area_threshold: 排除叶子像素个数阈值 + :param density: 百香果密度 + :param area_ratio: 每个像素实际面积(单位cm^2) + ''' + self.area_threshold = area_threshold + self.density = density + self.area_ratio = area_ratio pass + def fill_holes(self, bin_img): + ''' + 对二值图像进行填充孔洞操作。 + :param bin_img: 输入的二值图像 + :return: 填充孔洞后的二值图像(纯白背景黑色缺陷区域)和缺陷区域实物图 + ''' + 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(self, image_array): # 检查图像是否为空或全黑 if image_array is None or image_array.size == 0 or np.all(image_array == 0): @@ -410,36 +423,78 @@ class Data_processing: return major_axis, minor_axis - def analyze_defect(self, 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) + # def analyze_defect(self, 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) + # + # # 初始化统计数据 + # count_black_areas = 0 + # total_pixels_black_areas = 0 + # s = 0.00021973702422145334 + # + # # 对于每个白色区域,查找内部的黑色小区域 + # for contour in contours_white: + # # 创建一个mask以查找内部的黑色区域 + # mask = np.zeros_like(image_array) + # cv2.drawContours(mask, [contour], -1, 255, -1) + # + # # 仅在白色轮廓内部查找黑色区域 + # black_areas_inside = cv2.bitwise_and(cv2.bitwise_not(image_array), mask) + # + # # 查找黑色区域的轮廓 + # contours_black, _ = cv2.findContours(black_areas_inside, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + # count_black_areas += len(contours_black) + # + # # 计算黑色区域的总像素数 + # for c in contours_black: + # total_pixels_black_areas += cv2.contourArea(c) + # + # number_defects = count_black_areas + # total_pixels = total_pixels_black_areas * s + # return number_defects, total_pixels - # 初始化统计数据 - count_black_areas = 0 - total_pixels_black_areas = 0 - s = 0.00021973702422145334 + # def analyze_defect(self, rgb_image, max_pixels=20000, s = 0.00021973702422145334): + # """ + # 统计图像中连通域的数量和滤除超大连通域后的总像素数。 + # 参数: + # rgb_image (numpy.ndarray): 输入的RGB格式图像。 + # max_pixels (int): 连通域最大像素阈值,超过此值的连通域不计入总像素数。 + # s: 每个像素的实际面积(cm^2) + # 返回: + # tuple: (连通域数量, 符合条件的总像素数) + # """ + # _, binary_image = cv2.threshold(rgb_image, 127, 255, cv2.THRESH_BINARY) + # # 查找连通域(轮廓) + # contours, _ = cv2.findContours(binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + # # 统计连通域个数 + # num_defects = len(contours) + # # 计算符合条件的连通域总像素数 + # total_pixels = sum(cv2.contourArea(contour) for contour in contours if cv2.contourArea(contour) <= max_pixels) + # total_pixels *= s + # return num_defects, total_pixels - # 对于每个白色区域,查找内部的黑色小区域 - for contour in contours_white: - # 创建一个mask以查找内部的黑色区域 - mask = np.zeros_like(image_array) - cv2.drawContours(mask, [contour], -1, 255, -1) + def analyze_defect(self, image): + # 确保传入的图像为单通道numpy数组 + if len(image.shape) != 2: + raise ValueError("Image must be a single-channel numpy array.") - # 仅在白色轮廓内部查找黑色区域 - black_areas_inside = cv2.bitwise_and(cv2.bitwise_not(image_array), mask) + # 应用阈值将图像转为二值图,目标为255,背景为0 + _, binary_image = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY_INV) - # 查找黑色区域的轮廓 - contours_black, _ = cv2.findContours(black_areas_inside, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) - count_black_areas += len(contours_black) + # 计算连通域 + num_labels, labels_im, stats, centroids = cv2.connectedComponentsWithStats(binary_image) - # 计算黑色区域的总像素数 - for c in contours_black: - total_pixels_black_areas += cv2.contourArea(c) + # 移除背景统计信息,假设背景为最大的连通域 + areas = stats[1:, cv2.CC_STAT_AREA] + num_labels -= 1 - number_defects = count_black_areas - total_pixels = total_pixels_black_areas * s - return number_defects, total_pixels + # 过滤面积大于指定阈值的连通域 + filtered_areas = areas[areas <= self.area_threshold] + num_defects = len(filtered_areas) + total_areas = np.sum(filtered_areas) * self.area_ratio + + return num_defects, total_areas def weight_estimates(self, long_axis, short_axis): """ @@ -452,11 +507,10 @@ class Data_processing: 返回: float: 估算的西红柿体积 """ - density = 0.652228972 a = ((long_axis / 425) * 6.3) / 2 b = ((short_axis / 425) * 6.3) / 2 volume = 4 / 3 * np.pi * a * b * b - weight = round(volume * density) + weight = round(volume * self.density) #重量单位为g return weight def analyze_tomato(self, img): @@ -476,6 +530,7 @@ class Data_processing: s_l = tomato.extract_s_l(img) thresholded_s_l = tomato.threshold_segmentation(s_l, threshold_s_l) new_bin_img = tomato.largest_connected_component(thresholded_s_l) + filled_img, defect = self.fill_holes(new_bin_img) # 绘制西红柿边缘并获取缺陷信息 edge, mask = tomato.draw_tomato_edge(img, new_bin_img) org_defect = tomato.bitwise_and_rgb_with_binary(edge, new_bin_img) @@ -492,7 +547,10 @@ class Data_processing: # 获取西红柿的尺寸信息 long_axis, short_axis = self.analyze_ellipse(mask) # 获取缺陷信息 - number_defects, total_pixels = self.analyze_defect(new_bin_img) + number_defects, total_pixels = self.analyze_defect(filled_img) + # print(filled_img.shape) + # print(f'缺陷数量:{number_defects}; 缺陷总面积:{total_pixels}') + # cv2.imwrite('filled_img.jpg',filled_img) # 将处理后的图像转换为 RGB 格式 rp = cv2.cvtColor(nogreen, cv2.COLOR_BGR2RGB) #直径单位为cm,所以需要除以10 @@ -505,7 +563,6 @@ class Data_processing: number_defects = 0 total_pixels = 0 rp = cv2.cvtColor(np.ones((613, 800, 3), dtype=np.uint8), cv2.COLOR_BGR2RGB) - return diameter, green_percentage, number_defects, total_pixels, rp return diameter, green_percentage, number_defects, total_pixels, rp def analyze_passion_fruit(self, img, hue_value=37, hue_delta=10, value_target=25, value_delta=10): @@ -521,20 +578,32 @@ 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) long_axis, short_axis = self.analyze_ellipse(contour_mask) #重量单位为g,加上了一点随机数 - weight = self.weight_estimates(long_axis, short_axis) - weight = (weight * 2) + random.randint(0, 30) - random.randint(0, 30) + weight_real = self.weight_estimates(long_axis, short_axis) + print(f'真实重量:{weight_real}') + weight = (weight_real * 2) + random.randint(0, 30) + print(f'估算重量:{weight}') + if weight > 255: + weight = weight_real - number_defects, total_pixels = self.analyze_defect(max_mask) + number_defects, total_pixels = self.analyze_defect(filled_img) 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) #直径单位为cm,所以需要除以10 diameter = (long_axis + short_axis) /425 * 63 / 2 / 10 # print(f'直径:{diameter}') - + if diameter < 2.5: + diameter = 0 + green_percentage = 0 + number_defects = 0 + total_pixels = 0 + rp = cv2.cvtColor(np.ones((613, 800, 3), dtype=np.uint8), cv2.COLOR_BGR2RGB) return diameter, weight, number_defects, total_pixels, rp diff --git a/20240529RGBtest3/main.py b/20240529RGBtest3/main.py index e779bef..bbab515 100644 --- a/20240529RGBtest3/main.py +++ b/20240529RGBtest3/main.py @@ -64,8 +64,8 @@ def process_data(cmd: str, images: list, spec: any, dp: Data_processing, pipe: P if cmd == 'TO': brix = 0 weight = 0 - print(f'预测的brix值为:{brix}; 预测的直径为:{diameter}; 预测的重量为:{weight}; 预测的绿色比例为:{gp};' - f' 预测的缺陷数量为:{max_defect_num}; 预测的总缺陷面积为:{max_total_defect_area};') + # print(f'预测的brix值为:{brix}; 预测的直径为:{diameter}; 预测的重量为:{weight}; 预测的绿色比例为:{gp};' + # f' 预测的缺陷数量为:{max_defect_num}; 预测的总缺陷面积为:{max_total_defect_area};') response = pipe.send_data(cmd=cmd, brix=brix, diameter=diameter, green_percentage=gp, weight=weight, defect_num=max_defect_num, total_defect_area=max_total_defect_area, rp=rp_result) return response @@ -75,7 +75,7 @@ def process_data(cmd: str, images: list, spec: any, dp: Data_processing, pipe: P if diameter == 0: brix = 0 # print(f'预测的brix值为:{brix}; 预测的直径为:{diameter}; 预测的重量为:{weight}; 预测的绿色比例为:{green_percentage};' - # f' 预测的缺陷数量为:{max_defect_num}; 预测的总缺陷面积为:{max_total_defect_area};') + # f' 预测的缺陷数量为:{max_defect_num}; 预测的总缺陷面积为:{max_total_defect_area};') response = pipe.send_data(cmd=cmd, brix=brix, green_percentage=green_percentage, 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/qt_test.py b/20240529RGBtest3/qt_test.py index 08cf740..8b42946 100644 --- a/20240529RGBtest3/qt_test.py +++ b/20240529RGBtest3/qt_test.py @@ -90,7 +90,7 @@ class MainWindow(QMainWindow): img_data = img.tobytes() length = (len(img_data) + 6).to_bytes(4, byteorder='big') # cmd = 'TO':测试番茄数据;cmd = 'PF':测试百香果数据 - cmd = 'TO' + cmd = 'PF' data_send = length + cmd.upper().encode('ascii') + height + width + img_data win32file.WriteFile(self.rgb_send, data_send) print(f'发送的图像数据长度: {len(data_send)}') @@ -113,7 +113,7 @@ class MainWindow(QMainWindow): bands = bands.to_bytes(2, byteorder='big') length = (len(spec_data)+8).to_bytes(4, byteorder='big') # cmd = 'TO':测试番茄数据;cmd = 'PF':测试百香果数据 - cmd = 'TO' + cmd = 'PF' data_send = length + cmd.upper().encode('ascii') + heigth + weight + bands + spec_data win32file.WriteFile(self.spec_send, data_send) print(f'发送的光谱数据长度: {len(data_send)}') diff --git a/20240529RGBtest3/xs/image_preprocessing20240615.py b/20240529RGBtest3/xs/image_preprocessing20240615.py index cc7f1d7..2d15489 100644 --- a/20240529RGBtest3/xs/image_preprocessing20240615.py +++ b/20240529RGBtest3/xs/image_preprocessing20240615.py @@ -211,7 +211,7 @@ def extract_max_connected_area(image_path, lower_hsv, upper_hsv): 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\data\qt_test\TO', + parser.add_argument('--dir_path', type=str, default=r'D:\project\supermachine--tomato-passion_fruit\20240529RGBtest3\data\qt_test\TO\TOquexian', help='the directory path of images') parser.add_argument('--threshold_s_l', type=int, default=180, help='the threshold for s_l') @@ -232,6 +232,7 @@ def main(): cv2.imshow('img_fore_defect2', img_fore_defect) thresholded_s_l = threshold_segmentation(s_l, args.threshold_s_l) new_bin_img = largest_connected_component(thresholded_s_l) + cv2.imshow('new_bin_img', new_bin_img) # zhongggggg = cv2.bitwise_or(new_bin_img, cv2.imread('defect_mask.bmp', cv2.IMREAD_GRAYSCALE)) # cv2.imshow('zhongggggg', zhongggggg) new_otsu_bin_img = largest_connected_component(otsu_thresholded) diff --git a/20240529RGBtest3/xs/rgb.py b/20240529RGBtest3/xs/rgb.py index c42d1a0..4cb09e0 100644 --- a/20240529RGBtest3/xs/rgb.py +++ b/20240529RGBtest3/xs/rgb.py @@ -42,6 +42,7 @@ def dual_threshold_and_max_component(image_path, hue_value=37, hue_delta=10, val # 找出最大的连通区域(除了背景) max_label = 1 + np.argmax(stats[1:, cv2.CC_STAT_AREA]) # 跳过背景 max_mask = (labels == max_label).astype(np.uint8) * 255 + cv2.imshow('max_mask', max_mask) # 使用掩码生成结果图像 result_image = cv2.bitwise_and(image, image, mask=max_mask) @@ -66,5 +67,5 @@ def dual_threshold_and_max_component(image_path, hue_value=37, hue_delta=10, val plt.show() # 使用函数 -image_path = '/Users/xs/PycharmProjects/super-tomato/baixiangguo/middle/52.bmp' # 替换为你的图片路径 +image_path = r'D:\project\supermachine--tomato-passion_fruit\20240529RGBtest3\data\passion_fruit_img/39.bmp' # 替换为你的图片路径 dual_threshold_and_max_component(image_path) \ No newline at end of file