mirror of
https://github.com/NanjingForestryUniversity/supermachine--tomato-passion_fruit.git
synced 2025-11-09 14:54:07 +00:00
fix:修改缺陷面积统计函数,使用背景为白色,缺陷区域为黑色的图像进行统计,排除像素面积大于20000(叶片)的连通域;
fix:修改部分类内的定义函数; feat:增加百香果表面图像处理步骤,使其与番茄一致化
This commit is contained in:
parent
1dcd908115
commit
53d7f680e0
@ -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
|
||||
@ -506,7 +564,6 @@ class Data_processing:
|
||||
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):
|
||||
if img is None:
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)}')
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
Loading…
Reference in New Issue
Block a user