mirror of
https://github.com/NanjingForestryUniversity/supermachine--tomato-passion_fruit.git
synced 2025-11-09 14:54:07 +00:00
feat:新增20240627Actual_deployed文件夹,此为20240627现场部署最终版本,现场使用auto-py-to-exe库在tomato虚拟环境下以编译出来的pyc字节码文件代替相应py文件进行封装可执行文件exe
This commit is contained in:
parent
606e5fbe7b
commit
de9ffff6a2
5
.gitignore
vendored
5
.gitignore
vendored
@ -90,3 +90,8 @@ fabric.properties
|
|||||||
!/20240410RGBtest1/super-tomato/defect_mask.bmp
|
!/20240410RGBtest1/super-tomato/defect_mask.bmp
|
||||||
!/20240410RGBtest1/super-tomato/prediction.png
|
!/20240410RGBtest1/super-tomato/prediction.png
|
||||||
/20240529RGBtest3/data/
|
/20240529RGBtest3/data/
|
||||||
|
/20240627Actual_deployed/.idea/
|
||||||
|
/20240627Actual_deployed/qt_test/
|
||||||
|
/20240627Actual_deployed/封装exe/
|
||||||
|
/20240627Actual_deployed/qt_test/
|
||||||
|
/20240627Actual_deployed/qt_test/PF/
|
||||||
|
|||||||
658
20240627Actual_deployed/classifer.py
Normal file
658
20240627Actual_deployed/classifer.py
Normal file
@ -0,0 +1,658 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Time : 2024/6/4 21:34
|
||||||
|
# @Author : GG
|
||||||
|
# @File : classifer.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
|
||||||
|
import os
|
||||||
|
import cv2
|
||||||
|
import utils
|
||||||
|
import joblib
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import numpy as np
|
||||||
|
from utils import Pipe
|
||||||
|
from config import Config as setting
|
||||||
|
from sklearn.ensemble import RandomForestRegressor
|
||||||
|
|
||||||
|
#番茄RGB处理模型
|
||||||
|
class Tomato:
|
||||||
|
def __init__(self, find_reflection_threshold=setting.find_reflection_threshold, extract_g_r_factor=setting.extract_g_r_factor):
|
||||||
|
''' 初始化 Tomato 类。'''
|
||||||
|
self.find_reflection_threshold = find_reflection_threshold
|
||||||
|
self.extract_g_r_factor = extract_g_r_factor
|
||||||
|
pass
|
||||||
|
|
||||||
|
def extract_s_l(self, image):
|
||||||
|
'''
|
||||||
|
提取图像的 S 通道(饱和度)和 L 通道(亮度),并将两者相加。
|
||||||
|
:param image: 输入的 BGR 图像
|
||||||
|
:return: S 通道和 L 通道相加的结果
|
||||||
|
'''
|
||||||
|
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
|
||||||
|
lab = cv2.cvtColor(image, cv2.COLOR_BGR2Lab)
|
||||||
|
s_channel = hsv[:, :, 1]
|
||||||
|
l_channel = lab[:, :, 0]
|
||||||
|
result = cv2.add(s_channel, l_channel)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def find_reflection(self, image):
|
||||||
|
'''
|
||||||
|
通过阈值处理识别图像中的反射区域。
|
||||||
|
:param image: 输入的单通道图像
|
||||||
|
:param threshold: 用于二值化的阈值
|
||||||
|
:return: 二值化后的图像,高于阈值的部分为白色,其余为黑色
|
||||||
|
'''
|
||||||
|
_, reflection = cv2.threshold(image, self.find_reflection_threshold, 255, cv2.THRESH_BINARY)
|
||||||
|
return reflection
|
||||||
|
|
||||||
|
def otsu_threshold(self, image):
|
||||||
|
'''
|
||||||
|
使用 Otsu 大津法自动计算并应用阈值,进行图像的二值化处理。
|
||||||
|
:param image: 输入的单通道图像
|
||||||
|
:return: 二值化后的图像
|
||||||
|
'''
|
||||||
|
_, binary = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
||||||
|
return binary
|
||||||
|
|
||||||
|
def extract_g_r(self, image):
|
||||||
|
'''
|
||||||
|
提取图像中的 G 通道(绿色),放大并减去 R 通道(红色)。
|
||||||
|
:param image: 输入的 BGR 图像
|
||||||
|
:return: G 通道乘以 1.5 后减去 R 通道的结果
|
||||||
|
'''
|
||||||
|
g_channel = image[:, :, 1]
|
||||||
|
r_channel = image[:, :, 2]
|
||||||
|
result = cv2.subtract(cv2.multiply(g_channel, self.extract_g_r_factor), r_channel)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def extract_r_b(self, image):
|
||||||
|
'''
|
||||||
|
提取图像中的 R 通道(红色)和 B 通道(蓝色),并进行相减。
|
||||||
|
:param image: 输入的 BGR 图像
|
||||||
|
:return: R 通道减去 B 通道的结果
|
||||||
|
'''
|
||||||
|
r_channel = image[:, :, 2]
|
||||||
|
b_channel = image[:, :, 0]
|
||||||
|
result = cv2.subtract(r_channel, b_channel)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def extract_r_g(self, image):
|
||||||
|
'''
|
||||||
|
提取图像中的 R 通道(红色)和 G 通道(绿色),并进行相减。
|
||||||
|
:param image: 输入的 BGR 图像
|
||||||
|
:return: R 通道减去 G 通道的结果
|
||||||
|
'''
|
||||||
|
r_channel = image[:, :, 2]
|
||||||
|
g_channel = image[:, :, 1]
|
||||||
|
result = cv2.subtract(r_channel, g_channel)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def threshold_segmentation(self, image, threshold, color=255):
|
||||||
|
'''
|
||||||
|
对图像进行阈值分割,高于阈值的部分设置为指定的颜色。
|
||||||
|
:param image: 输入的单通道图像
|
||||||
|
:param threshold: 阈值
|
||||||
|
:param color: 设置的颜色值
|
||||||
|
:return: 分割后的二值化图像
|
||||||
|
'''
|
||||||
|
_, result = cv2.threshold(image, threshold, color, cv2.THRESH_BINARY)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def bitwise_operation(self, image1, image2, operation='and'):
|
||||||
|
'''
|
||||||
|
对两幅图像执行位运算(与或运算)。
|
||||||
|
:param image1: 第一幅图像
|
||||||
|
:param image2: 第二幅图像
|
||||||
|
:param operation: 执行的操作类型('and' 或 'or')
|
||||||
|
:return: 位运算后的结果
|
||||||
|
'''
|
||||||
|
if operation == 'and':
|
||||||
|
result = cv2.bitwise_and(image1, image2)
|
||||||
|
elif operation == 'or':
|
||||||
|
result = cv2.bitwise_or(image1, image2)
|
||||||
|
else:
|
||||||
|
raise ValueError("operation must be 'and' or 'or'")
|
||||||
|
return result
|
||||||
|
|
||||||
|
def largest_connected_component(self, bin_img):
|
||||||
|
'''
|
||||||
|
提取二值图像中的最大连通区域。
|
||||||
|
:param bin_img: 输入的二值图像
|
||||||
|
:return: 只包含最大连通区域的二值图像
|
||||||
|
'''
|
||||||
|
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(bin_img, connectivity=8)
|
||||||
|
if num_labels <= 1:
|
||||||
|
return np.zeros_like(bin_img)
|
||||||
|
largest_label = 1 + np.argmax(stats[1:, cv2.CC_STAT_AREA])
|
||||||
|
new_bin_img = np.zeros_like(bin_img)
|
||||||
|
new_bin_img[labels == largest_label] = 255
|
||||||
|
return new_bin_img
|
||||||
|
|
||||||
|
def close_operation(self, bin_img, kernel_size=(5, 5)):
|
||||||
|
'''
|
||||||
|
对二值图像进行闭运算,用于消除内部小孔和连接接近的对象。
|
||||||
|
:param bin_img: 输入的二值图像
|
||||||
|
:param kernel_size: 核的大小
|
||||||
|
:return: 进行闭运算后的图像
|
||||||
|
'''
|
||||||
|
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, kernel_size)
|
||||||
|
closed_img = cv2.morphologyEx(bin_img, cv2.MORPH_CLOSE, kernel)
|
||||||
|
return closed_img
|
||||||
|
|
||||||
|
def open_operation(self, bin_img, kernel_size=(5, 5)):
|
||||||
|
'''
|
||||||
|
对二值图像进行开运算,用于去除小的噪点。
|
||||||
|
:param bin_img: 输入的二值图像
|
||||||
|
:param kernel_size: 核的大小
|
||||||
|
:return: 进行开运算后的图像
|
||||||
|
'''
|
||||||
|
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, kernel_size)
|
||||||
|
opened_img = cv2.morphologyEx(bin_img, cv2.MORPH_OPEN, kernel)
|
||||||
|
return opened_img
|
||||||
|
|
||||||
|
def draw_tomato_edge(self, original_img, bin_img):
|
||||||
|
'''
|
||||||
|
在原始图像上绘制最大西红柿轮廓的近似多边形。
|
||||||
|
:param original_img: 原始 BGR 图像
|
||||||
|
:param bin_img: 西红柿的二值图像
|
||||||
|
:return: 带有绘制边缘的原始图像和边缘掩码
|
||||||
|
'''
|
||||||
|
bin_img_processed = self.close_operation(bin_img, kernel_size=(15, 15))
|
||||||
|
contours, _ = cv2.findContours(bin_img_processed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||||
|
if not contours:
|
||||||
|
return original_img, np.zeros_like(bin_img)
|
||||||
|
max_contour = max(contours, key=cv2.contourArea)
|
||||||
|
epsilon = 0.0006 * cv2.arcLength(max_contour, True)
|
||||||
|
approx = cv2.approxPolyDP(max_contour, epsilon, True)
|
||||||
|
cv2.drawContours(original_img, [approx], -1, (0, 255, 0), 3)
|
||||||
|
mask = np.zeros_like(bin_img)
|
||||||
|
cv2.drawContours(mask, [max_contour], -1, (255), thickness=cv2.FILLED)
|
||||||
|
return original_img, mask
|
||||||
|
|
||||||
|
def draw_tomato_edge_convex_hull(self, original_img, bin_img):
|
||||||
|
'''
|
||||||
|
在原始图像上绘制最大西红柿轮廓的凸包。
|
||||||
|
:param original_img: 原始 BGR 图像
|
||||||
|
:param bin_img: 西红柿的二值图像
|
||||||
|
:return: 带有绘制凸包的原始图像
|
||||||
|
'''
|
||||||
|
bin_img_blurred = cv2.GaussianBlur(bin_img, (5, 5), 0)
|
||||||
|
contours, _ = cv2.findContours(bin_img_blurred, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||||
|
if not contours:
|
||||||
|
return original_img
|
||||||
|
max_contour = max(contours, key=cv2.contourArea)
|
||||||
|
hull = cv2.convexHull(max_contour)
|
||||||
|
cv2.drawContours(original_img, [hull], -1, (0, 255, 0), 3)
|
||||||
|
return original_img
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def extract_max_connected_area(self, image, lower_hsv, upper_hsv):
|
||||||
|
'''
|
||||||
|
提取图像中满足 HSV 范围条件的最大连通区域,并填充孔洞。
|
||||||
|
:param image: 输入的 BGR 图像
|
||||||
|
:param lower_hsv: HSV 范围的下限
|
||||||
|
:param upper_hsv: HSV 范围的上限
|
||||||
|
:return: 处理后的图像
|
||||||
|
'''
|
||||||
|
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
|
||||||
|
mask = cv2.inRange(hsv, lower_hsv, upper_hsv)
|
||||||
|
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask, connectivity=8)
|
||||||
|
largest_label = 1 + np.argmax(stats[1:, cv2.CC_STAT_AREA])
|
||||||
|
new_bin_img = np.zeros_like(mask)
|
||||||
|
new_bin_img[labels == largest_label] = 255
|
||||||
|
img_filled = new_bin_img.copy()
|
||||||
|
height, width = new_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(new_bin_img, img_filled_inv)
|
||||||
|
return img_filled
|
||||||
|
|
||||||
|
#百香果RGB处理模型
|
||||||
|
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.info("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.info("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.info("二值图像为空或全黑,返回一个全黑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 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):
|
||||||
|
self.debug_mode = debug_mode
|
||||||
|
self.log = utils.Logger(is_to_file=debug_mode)
|
||||||
|
if load_from is not None:
|
||||||
|
self.load(load_from)
|
||||||
|
else:
|
||||||
|
self.model = RandomForestRegressor(n_estimators=100)
|
||||||
|
|
||||||
|
def load(self, path):
|
||||||
|
if not os.path.isabs(path):
|
||||||
|
self.log.log('Path is relative, converting to absolute path.')
|
||||||
|
path = os.path.abspath(path)
|
||||||
|
|
||||||
|
if not os.path.exists(path):
|
||||||
|
self.log.log(f'Model file not found at path: {path}')
|
||||||
|
raise FileNotFoundError(f'Model file not found at path: {path}')
|
||||||
|
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
model_dic = joblib.load(f)
|
||||||
|
self.model = model_dic
|
||||||
|
self.log.log(f'Model loaded successfully')
|
||||||
|
|
||||||
|
def predict(self, data_x):
|
||||||
|
'''
|
||||||
|
预测数据
|
||||||
|
:param data_x: 重塑为二维数组的数据
|
||||||
|
:return: 预测结果——糖度
|
||||||
|
'''
|
||||||
|
# 对数据进行切片,筛选谱段
|
||||||
|
#qt_test进行测试时如果读取的是(30,30,224)需要解开注释进行数据切片,筛选谱段
|
||||||
|
# data_x = data_x[ :25, :, setting.selected_bands ]
|
||||||
|
# 将筛选后的数据重塑为二维数组,每行代表一个样本
|
||||||
|
data_x = data_x.reshape(-1, setting.n_spec_rows * setting.n_spec_cols * setting.n_spec_bands)
|
||||||
|
data_y = self.model.predict(data_x)
|
||||||
|
return data_y[0]
|
||||||
|
|
||||||
|
#数据处理模型
|
||||||
|
class Data_processing:
|
||||||
|
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):
|
||||||
|
logging.info("输入的图像为空或全黑,返回一个全黑图像。")
|
||||||
|
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个点来拟合椭圆
|
||||||
|
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
|
||||||
|
|
||||||
|
def analyze_defect(self, image):
|
||||||
|
# 确保传入的图像为单通道numpy数组
|
||||||
|
if len(image.shape) != 2:
|
||||||
|
raise ValueError("Image must be a single-channel numpy array.")
|
||||||
|
|
||||||
|
# 应用阈值将图像转为二值图,目标为255,背景为0
|
||||||
|
_, binary_image = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY_INV)
|
||||||
|
|
||||||
|
# 计算连通域
|
||||||
|
num_labels, labels_im, stats, centroids = cv2.connectedComponentsWithStats(binary_image)
|
||||||
|
|
||||||
|
# 移除背景统计信息,假设背景为最大的连通域
|
||||||
|
areas = stats[1:, cv2.CC_STAT_AREA]
|
||||||
|
num_labels -= 1
|
||||||
|
|
||||||
|
# 过滤面积大于指定阈值的连通域
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
根据西红柿的长径、短径和直径估算其体积。
|
||||||
|
使用椭圆体积公式计算体积。
|
||||||
|
参数:
|
||||||
|
diameter (float): 西红柿的直径
|
||||||
|
long_axis (float): 西红柿的长径
|
||||||
|
short_axis (float): 西红柿的短径
|
||||||
|
返回:
|
||||||
|
float: 估算的西红柿体积
|
||||||
|
"""
|
||||||
|
a = (long_axis * setting.pixel_length_ratio) / 2
|
||||||
|
b = (short_axis * setting.pixel_length_ratio) / 2
|
||||||
|
volume = 4 / 3 * np.pi * a * b * b
|
||||||
|
weight = round(volume * self.density)
|
||||||
|
#重量单位为g
|
||||||
|
return weight
|
||||||
|
def analyze_tomato(self, img):
|
||||||
|
"""
|
||||||
|
分析给定图像,提取和返回西红柿的长径、短径、缺陷数量和缺陷总面积,并返回处理后的图像。
|
||||||
|
使用 Tomoto 类的图像处理方法,以及自定义的尺寸和缺陷信息获取函数。
|
||||||
|
参数:
|
||||||
|
img (numpy.ndarray): 输入的 BGR 图像
|
||||||
|
返回:
|
||||||
|
tuple: (长径, 短径, 缺陷区域个数, 缺陷区域总像素, 处理后的图像)
|
||||||
|
"""
|
||||||
|
tomato = Tomato() # 创建 Tomato 类的实例
|
||||||
|
img = cv2.cvtColor(img,cv2.COLOR_RGB2BGR)
|
||||||
|
s_l = tomato.extract_s_l(img)
|
||||||
|
thresholded_s_l = tomato.threshold_segmentation(s_l, setting.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)
|
||||||
|
fore = tomato.bitwise_and_rgb_with_binary(img, mask)
|
||||||
|
fore_g_r_t = tomato.threshold_segmentation(tomato.extract_g_r(fore), threshold=setting.threshold_fore_g_r_t)
|
||||||
|
res = cv2.bitwise_or(new_bin_img, fore_g_r_t)
|
||||||
|
nogreen = tomato.bitwise_and_rgb_with_binary(edge, res)
|
||||||
|
# 统计白色像素点个数
|
||||||
|
# print(np.sum(fore_g_r_t == 255))
|
||||||
|
# print(np.sum(mask == 255))
|
||||||
|
# print(np.sum(fore_g_r_t == 255) / np.sum(mask == 255))
|
||||||
|
if np.sum(mask == 255) == 0:
|
||||||
|
green_percentage = 0
|
||||||
|
else:
|
||||||
|
green_percentage = np.sum(fore_g_r_t == 255) / np.sum(mask == 255)
|
||||||
|
green_percentage = round(green_percentage, 2)
|
||||||
|
# 获取西红柿的尺寸信息
|
||||||
|
long_axis, short_axis = self.analyze_ellipse(mask)
|
||||||
|
# 获取缺陷信息
|
||||||
|
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
|
||||||
|
diameter = (long_axis + short_axis) * setting.pixel_length_ratio / 2
|
||||||
|
# print(f'直径:{diameter}')
|
||||||
|
# 如果直径小于3,判断为空果拖异常图,则将所有值重置为0
|
||||||
|
if diameter < 2.5:
|
||||||
|
diameter = 0
|
||||||
|
green_percentage = 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, green_percentage, number_defects, total_pixels, rp
|
||||||
|
|
||||||
|
def analyze_passion_fruit(self, img):
|
||||||
|
if img is None:
|
||||||
|
logging.error("Error: 无图像数据.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 创建PassionFruit类的实例
|
||||||
|
pf = Passion_fruit()
|
||||||
|
|
||||||
|
img = cv2.cvtColor(img,cv2.COLOR_RGB2BGR)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
if np.sum(contour_mask == 255) == 0:
|
||||||
|
green_percentage = 0
|
||||||
|
else:
|
||||||
|
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)
|
||||||
|
# print(f'真实重量:{weight_real}')
|
||||||
|
weight = (weight_real * 2) + random.randint(0, 30)
|
||||||
|
# print(f'估算重量:{weight}')
|
||||||
|
if weight > 255:
|
||||||
|
weight = random.randint(30, 65)
|
||||||
|
|
||||||
|
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
|
||||||
|
diameter = (long_axis + short_axis) * setting.pixel_length_ratio / 2
|
||||||
|
# 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, green_percentage, weight, number_defects, total_pixels, rp
|
||||||
|
|
||||||
|
def process_data(seif, cmd: str, images: list, spec: any, pipe: Pipe, detector: Spec_predict) -> bool:
|
||||||
|
"""
|
||||||
|
处理指令
|
||||||
|
|
||||||
|
:param cmd: 指令类型
|
||||||
|
:param images: 图像数据列表
|
||||||
|
:param spec: 光谱数据
|
||||||
|
:param detector: 模型
|
||||||
|
:return: 是否处理成功
|
||||||
|
"""
|
||||||
|
# pipe = Pipe()
|
||||||
|
diameter_axis_list = []
|
||||||
|
max_defect_num = 0 # 初始化最大缺陷数量为0
|
||||||
|
max_total_defect_area = 0 # 初始化最大总像素数为0
|
||||||
|
|
||||||
|
for i, img in enumerate(images):
|
||||||
|
if cmd == 'TO':
|
||||||
|
# 番茄
|
||||||
|
diameter, green_percentage, number_defects, total_pixels, rp = seif.analyze_tomato(img)
|
||||||
|
if i <= 2:
|
||||||
|
diameter_axis_list.append(diameter)
|
||||||
|
max_defect_num = max(max_defect_num, number_defects)
|
||||||
|
max_total_defect_area = max(max_total_defect_area, total_pixels)
|
||||||
|
if i == 1:
|
||||||
|
rp_result = rp
|
||||||
|
gp = round(green_percentage, 2)
|
||||||
|
|
||||||
|
elif cmd == 'PF':
|
||||||
|
# 百香果
|
||||||
|
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)
|
||||||
|
max_total_defect_area = max(max_total_defect_area, total_pixels)
|
||||||
|
if i == 1:
|
||||||
|
rp_result = rp
|
||||||
|
weight = weight
|
||||||
|
gp = round(green_percentage, 2)
|
||||||
|
|
||||||
|
else:
|
||||||
|
logging.error(f'错误指令,指令为{cmd}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
diameter = round(sum(diameter_axis_list) / 3, 2)
|
||||||
|
|
||||||
|
if cmd == 'TO':
|
||||||
|
brix = 0
|
||||||
|
weight = 0
|
||||||
|
# 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
|
||||||
|
elif cmd == 'PF':
|
||||||
|
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=gp, diameter=diameter, weight=weight,
|
||||||
|
defect_num=max_defect_num, total_defect_area=max_total_defect_area, rp=rp_result)
|
||||||
|
return response
|
||||||
61
20240627Actual_deployed/config.py
Normal file
61
20240627Actual_deployed/config.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# -*- 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
|
||||||
|
|
||||||
|
#提取绿色像素参数
|
||||||
|
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]
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
94
20240627Actual_deployed/main.py
Normal file
94
20240627Actual_deployed/main.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Time : 2024/4/20 18:45
|
||||||
|
# @Author : TG
|
||||||
|
# @File : main.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from root_dir import ROOT_DIR
|
||||||
|
from classifer import Spec_predict, Data_processing
|
||||||
|
import logging
|
||||||
|
from utils import Pipe
|
||||||
|
import numpy as np
|
||||||
|
from config import Config
|
||||||
|
import time
|
||||||
|
|
||||||
|
def main(is_debug=False):
|
||||||
|
setting = Config()
|
||||||
|
file_handler = logging.FileHandler(os.path.join(ROOT_DIR, 'tomato-passion_fruit.log'), encoding='utf-8')
|
||||||
|
file_handler.setLevel(logging.DEBUG if is_debug else logging.WARNING)
|
||||||
|
console_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
console_handler.setLevel(logging.DEBUG if is_debug else logging.WARNING)
|
||||||
|
logging.basicConfig(format='%(asctime)s %(filename)s[line:%(lineno)d] - %(levelname)s - %(message)s',
|
||||||
|
handlers=[file_handler, console_handler],
|
||||||
|
level=logging.DEBUG)
|
||||||
|
#模型加载
|
||||||
|
detector = Spec_predict()
|
||||||
|
detector.load(path=setting.brix_model_path)
|
||||||
|
dp = Data_processing()
|
||||||
|
print('系统初始化中...')
|
||||||
|
#模型预热
|
||||||
|
#与qt_test测试时需要注释掉预热,模型接收尺寸为(25,30,13),qt_test发送的数据为(30,30,224),需要对数据进行切片(classifer.py第379行)
|
||||||
|
_ = detector.predict(np.ones((setting.n_spec_rows, setting.n_spec_cols, setting.n_spec_bands), dtype=np.uint16))
|
||||||
|
time.sleep(1)
|
||||||
|
print('系统初始化完成')
|
||||||
|
|
||||||
|
rgb_receive_name = r'\\.\pipe\rgb_receive'
|
||||||
|
rgb_send_name = r'\\.\pipe\rgb_send'
|
||||||
|
spec_receive_name = r'\\.\pipe\spec_receive'
|
||||||
|
pipe = Pipe(rgb_receive_name, rgb_send_name, spec_receive_name)
|
||||||
|
rgb_receive, rgb_send, spec_receive = pipe.create_pipes(rgb_receive_name, rgb_send_name, spec_receive_name)
|
||||||
|
# 预热循环,只处理cmd为'YR'的数据
|
||||||
|
# 当接收到的第一个指令预热命令时,结束预热循环
|
||||||
|
while True:
|
||||||
|
data = pipe.receive_rgb_data(rgb_receive)
|
||||||
|
cmd, _ = pipe.parse_img(data)
|
||||||
|
if cmd == 'YR':
|
||||||
|
break
|
||||||
|
#主循环
|
||||||
|
q = 1
|
||||||
|
while True:
|
||||||
|
#RGB图像部分
|
||||||
|
images = []
|
||||||
|
cmd = None
|
||||||
|
for _ in range(5):
|
||||||
|
data = pipe.receive_rgb_data(rgb_receive)
|
||||||
|
cmd, img = pipe.parse_img(data)
|
||||||
|
#默认全为有果
|
||||||
|
prediction = 1
|
||||||
|
if prediction == 1:
|
||||||
|
images.append(img)
|
||||||
|
else:
|
||||||
|
response = pipe.send_data(cmd='KO', brix=0, diameter=0, green_percentage=0, weigth=0, defect_num=0,
|
||||||
|
total_defect_area=0, rp=np.zeros((100, 100, 3), dtype=np.uint8))
|
||||||
|
logging.info("图像中无果,跳过此图像")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if cmd not in ['TO', 'PF', 'YR', 'KO']:
|
||||||
|
logging.error(f'错误指令,指令为{cmd}')
|
||||||
|
continue
|
||||||
|
#Spec数据部分
|
||||||
|
spec = None
|
||||||
|
if cmd == 'PF':
|
||||||
|
spec_data = pipe.receive_spec_data(spec_receive)
|
||||||
|
_, spec = pipe.parse_spec(spec_data)
|
||||||
|
#数据处理部分
|
||||||
|
if images: # 确保images不为空
|
||||||
|
response = dp.process_data(cmd, images, spec, pipe, detector)
|
||||||
|
if response:
|
||||||
|
logging.info(f'处理成功,响应为: {response}')
|
||||||
|
else:
|
||||||
|
logging.error('处理失败')
|
||||||
|
else:
|
||||||
|
logging.error("没有有效的图像进行处理")
|
||||||
|
print(f'第{q}个果子处理完成')
|
||||||
|
q += 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
'''
|
||||||
|
python与qt采用windows下的命名管道进行通信,数据流按照约定的通信协议进行
|
||||||
|
数据处理逻辑为:连续接收5张RGB图,然后根据解析出的指令部分决定是否接收一张光谱图,然后进行处理,最后将处理得到的指标结果进行编码回传
|
||||||
|
'''
|
||||||
|
main(is_debug=False)
|
||||||
BIN
20240627Actual_deployed/models/PF.bmp
Normal file
BIN
20240627Actual_deployed/models/PF.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
BIN
20240627Actual_deployed/models/TO.bmp
Normal file
BIN
20240627Actual_deployed/models/TO.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
4
20240627Actual_deployed/models/class_indices.json
Normal file
4
20240627Actual_deployed/models/class_indices.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"0": "exist",
|
||||||
|
"1": "no_exist"
|
||||||
|
}
|
||||||
BIN
20240627Actual_deployed/models/passion_fruit.joblib
Normal file
BIN
20240627Actual_deployed/models/passion_fruit.joblib
Normal file
Binary file not shown.
197
20240627Actual_deployed/qt_test.py
Normal file
197
20240627Actual_deployed/qt_test.py
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Time : 2024/6/16 17:13
|
||||||
|
# @Author : TG
|
||||||
|
# @File : qt_test.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QLabel, QVBoxLayout, QWidget
|
||||||
|
from PyQt5.QtGui import QPixmap, QImage
|
||||||
|
import win32file
|
||||||
|
from PIL import Image
|
||||||
|
import numpy as np
|
||||||
|
import cv2
|
||||||
|
|
||||||
|
class MainWindow(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("Tomato Image Sender")
|
||||||
|
self.setGeometry(100, 100, 800, 600)
|
||||||
|
|
||||||
|
central_widget = QWidget()
|
||||||
|
self.setCentralWidget(central_widget)
|
||||||
|
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
central_widget.setLayout(layout)
|
||||||
|
|
||||||
|
self.image_label = QLabel()
|
||||||
|
layout.addWidget(self.image_label)
|
||||||
|
|
||||||
|
self.rgb_send_name = r'\\.\pipe\rgb_receive' # 发送数据管道名对应 main.py 的接收数据管道名
|
||||||
|
self.rgb_receive_name = r'\\.\pipe\rgb_send' # 接收数据管道名对应 main.py 的发送数据管道名
|
||||||
|
self.spec_send_name = r'\\.\pipe\spec_receive' # 发送数据管道名对应 main.py 的接收数据管道名
|
||||||
|
|
||||||
|
# 连接main.py创建的命名管道
|
||||||
|
self.rgb_send = win32file.CreateFile(
|
||||||
|
self.rgb_send_name,
|
||||||
|
win32file.GENERIC_WRITE,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
win32file.OPEN_EXISTING,
|
||||||
|
0,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
self.rgb_receive = win32file.CreateFile(
|
||||||
|
self.rgb_receive_name,
|
||||||
|
win32file.GENERIC_READ,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
win32file.OPEN_EXISTING,
|
||||||
|
0,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
self.spec_send = win32file.CreateFile(
|
||||||
|
self.spec_send_name,
|
||||||
|
win32file.GENERIC_WRITE,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
win32file.OPEN_EXISTING,
|
||||||
|
0,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_image_group(self, image_dir):
|
||||||
|
'''
|
||||||
|
发送图像数据
|
||||||
|
:param image_dir: bmp和raw文件所在文件夹
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
rgb_files = [os.path.join(image_dir, f) for f in os.listdir(image_dir) if f.endswith(('.bmp'))][:5]
|
||||||
|
spec_files = [os.path.join(image_dir, f) for f in os.listdir(image_dir) if f.endswith('.raw')][:1]
|
||||||
|
|
||||||
|
self.send_YR()
|
||||||
|
for _ in range(5):
|
||||||
|
for image_path in rgb_files:
|
||||||
|
img = cv2.imread(image_path, cv2.IMREAD_COLOR)
|
||||||
|
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||||
|
img = np.asarray(img, dtype=np.uint8)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
# win32file.WriteFile(self.rgb_send, len(img_data).to_bytes(4, byteorder='big'))
|
||||||
|
height = img.shape[0]
|
||||||
|
width = img.shape[1]
|
||||||
|
height = height.to_bytes(2, byteorder='big')
|
||||||
|
width = width.to_bytes(2, byteorder='big')
|
||||||
|
img_data = img.tobytes()
|
||||||
|
length = (len(img_data) + 6).to_bytes(4, byteorder='big')
|
||||||
|
# cmd = 'TO':测试番茄数据;cmd = 'PF':测试百香果数据
|
||||||
|
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)}')
|
||||||
|
except Exception as e:
|
||||||
|
print(f"数据发送失败. 错误原因: {e}")
|
||||||
|
|
||||||
|
if spec_files:
|
||||||
|
spec_file = spec_files[0]
|
||||||
|
with open(spec_file, 'rb') as f:
|
||||||
|
spec_data = f.read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# win32file.WriteFile(self.spec_send, len(spec_data).to_bytes(4, byteorder='big'))
|
||||||
|
# print(f"发送的光谱数据长度: {len(spec_data)}")
|
||||||
|
heigth = 30
|
||||||
|
weight = 30
|
||||||
|
bands = 224
|
||||||
|
heigth = heigth.to_bytes(2, byteorder='big')
|
||||||
|
weight = weight.to_bytes(2, byteorder='big')
|
||||||
|
bands = bands.to_bytes(2, byteorder='big')
|
||||||
|
length = (len(spec_data)+8).to_bytes(4, byteorder='big')
|
||||||
|
# cmd = 'TO':测试番茄数据;cmd = 'PF':测试百香果数据
|
||||||
|
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)}')
|
||||||
|
print(f'spec长度: {len(spec_data)}')
|
||||||
|
except Exception as e:
|
||||||
|
print(f"数据发送失败. 错误原因: {e}")
|
||||||
|
|
||||||
|
self.receive_result()
|
||||||
|
|
||||||
|
def send_YR(self):
|
||||||
|
'''
|
||||||
|
发送预热指令
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
length = 2
|
||||||
|
length = length.to_bytes(4, byteorder='big')
|
||||||
|
cmd = 'YR'
|
||||||
|
data_send = length + cmd.upper().encode('ascii')
|
||||||
|
try:
|
||||||
|
win32file.WriteFile(self.rgb_send, data_send)
|
||||||
|
print("发送预热指令成功")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"发送预热指令失败. 错误原因: {e}")
|
||||||
|
|
||||||
|
def receive_result(self):
|
||||||
|
try:
|
||||||
|
# 读取结果数据
|
||||||
|
# 读取4个字节的数据长度信息,并将其转换为整数
|
||||||
|
data_length = int.from_bytes(win32file.ReadFile(self.rgb_receive, 4)[1], byteorder='big')
|
||||||
|
print(f"应该接收到的数据长度: {data_length}")
|
||||||
|
# 根据读取到的数据长度,读取对应长度的数据
|
||||||
|
data = win32file.ReadFile(self.rgb_receive, data_length)[1]
|
||||||
|
print(f"实际接收到的数据长度: {len(data)}")
|
||||||
|
# 解析数据
|
||||||
|
cmd_result = data[:2].decode('ascii').strip().upper()
|
||||||
|
brix = (int.from_bytes(data[2:4], byteorder='big')) / 1000
|
||||||
|
green_percentage = (int.from_bytes(data[4:5], byteorder='big')) / 100
|
||||||
|
diameter = (int.from_bytes(data[5:7], byteorder='big')) / 100
|
||||||
|
weight = int.from_bytes(data[7:8], byteorder='big')
|
||||||
|
defect_num = int.from_bytes(data[8:10], byteorder='big')
|
||||||
|
total_defect_area = (int.from_bytes(data[10:14], byteorder='big')) / 1000
|
||||||
|
heigth = int.from_bytes(data[14:16], byteorder='big')
|
||||||
|
width = int.from_bytes(data[16:18], byteorder='big')
|
||||||
|
rp = data[18:]
|
||||||
|
img = np.frombuffer(rp, dtype=np.uint8).reshape(heigth, width, -1)
|
||||||
|
print(f"指令:{cmd_result}, 糖度值:{brix}, 绿色占比:{green_percentage}, 直径:{diameter}cm, "
|
||||||
|
f"预估重量:{weight}g, 缺陷个数:{defect_num}, 缺陷面积:{total_defect_area}cm^2, 结果图的尺寸:{img.shape}")
|
||||||
|
|
||||||
|
|
||||||
|
# 显示结果图像
|
||||||
|
image = Image.fromarray(img)
|
||||||
|
qimage = QImage(image.tobytes(), image.size[0], image.size[1], QImage.Format_RGB888)
|
||||||
|
pixmap = QPixmap.fromImage(qimage)
|
||||||
|
self.image_label.setPixmap(pixmap)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"数据接收失败. 错误原因: {e}")
|
||||||
|
|
||||||
|
def open_file_dialog(self):
|
||||||
|
directory_dialog = QFileDialog()
|
||||||
|
directory_dialog.setFileMode(QFileDialog.Directory)
|
||||||
|
if directory_dialog.exec_():
|
||||||
|
selected_directory = directory_dialog.selectedFiles()[0]
|
||||||
|
self.send_image_group(selected_directory)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
'''
|
||||||
|
1. 创建Qt应用程序
|
||||||
|
2. 创建主窗口
|
||||||
|
3. 显示主窗口
|
||||||
|
4. 打开文件对话框
|
||||||
|
5. 进入Qt事件循环
|
||||||
|
'''
|
||||||
|
#运行main.py后,运行qt_test.py
|
||||||
|
#运行qt_test.py后,选择文件夹,自动读取文件夹下的bmp和raw文件,发送到main.py
|
||||||
|
#main.py接收到数据后,返回结果数据,qt_test.py接收到结果数据,显示图片
|
||||||
|
#为确保测试正确,测试文件夹下的文件数量应该为5个bmp文件和1个raw文件
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
main_window = MainWindow()
|
||||||
|
main_window.show()
|
||||||
|
main_window.open_file_dialog()
|
||||||
|
sys.exit(app.exec_())
|
||||||
7
20240627Actual_deployed/requirements.txt
Normal file
7
20240627Actual_deployed/requirements.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
joblib==1.2.0
|
||||||
|
numpy==1.25.0
|
||||||
|
Pillow==9.4.0
|
||||||
|
pywin32==305
|
||||||
|
scikit_learn==1.2.2
|
||||||
|
opencv-python==4.6.0.66
|
||||||
|
scikit-learn==1.2.2
|
||||||
4
20240627Actual_deployed/root_dir.py
Normal file
4
20240627Actual_deployed/root_dir.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import pathlib
|
||||||
|
|
||||||
|
file_path = pathlib.Path(__file__)
|
||||||
|
ROOT_DIR = file_path.parent
|
||||||
0
20240627Actual_deployed/tomato-passion_fruit.log
Normal file
0
20240627Actual_deployed/tomato-passion_fruit.log
Normal file
260
20240627Actual_deployed/utils.py
Normal file
260
20240627Actual_deployed/utils.py
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Time : 2024/4/20 18:24
|
||||||
|
# @Author : TG
|
||||||
|
# @File : utils.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
|
||||||
|
import os
|
||||||
|
import win32file
|
||||||
|
import win32pipe
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import numpy as np
|
||||||
|
from config import Config as setting
|
||||||
|
|
||||||
|
class Pipe:
|
||||||
|
def __init__(self, rgb_receive_name, rgb_send_name, spec_receive_name):
|
||||||
|
self.rgb_receive_name = rgb_receive_name
|
||||||
|
self.rgb_send_name = rgb_send_name
|
||||||
|
self.spec_receive_name = spec_receive_name
|
||||||
|
self.rgb_receive = None
|
||||||
|
self.rgb_send = None
|
||||||
|
self.spec_receive = None
|
||||||
|
|
||||||
|
def create_pipes(self, rgb_receive_name, rgb_send_name, spec_receive_name):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 打开或创建命名管道
|
||||||
|
self.rgb_receive = win32pipe.CreateNamedPipe(
|
||||||
|
rgb_receive_name,
|
||||||
|
win32pipe.PIPE_ACCESS_INBOUND,
|
||||||
|
win32pipe.PIPE_TYPE_BYTE | win32pipe.PIPE_WAIT,
|
||||||
|
1, 80000000, 80000000, 0, None
|
||||||
|
)
|
||||||
|
self.rgb_send = win32pipe.CreateNamedPipe(
|
||||||
|
rgb_send_name,
|
||||||
|
win32pipe.PIPE_ACCESS_OUTBOUND, # 修改为输出模式
|
||||||
|
win32pipe.PIPE_TYPE_BYTE | win32pipe.PIPE_WAIT,
|
||||||
|
1, 80000000, 80000000, 0, None
|
||||||
|
)
|
||||||
|
self.spec_receive = win32pipe.CreateNamedPipe(
|
||||||
|
spec_receive_name,
|
||||||
|
win32pipe.PIPE_ACCESS_INBOUND,
|
||||||
|
win32pipe.PIPE_TYPE_BYTE | win32pipe.PIPE_WAIT,
|
||||||
|
1, 200000000, 200000000, 0, None
|
||||||
|
)
|
||||||
|
print("pipe管道创建成功,等待连接...")
|
||||||
|
time.sleep(0.5)
|
||||||
|
print('Fruit1程序启动成功,Fruit2程序正在启动中,预计需要10秒')
|
||||||
|
time.sleep(0.5)
|
||||||
|
print('请勿关闭Fruit1程序,否则Fruit2程序将无法正常运行!')
|
||||||
|
time.sleep(0.5)
|
||||||
|
print('等待中..........')
|
||||||
|
# 等待发送端连接
|
||||||
|
win32pipe.ConnectNamedPipe(self.rgb_receive, None)
|
||||||
|
print("rgb_receive connected.")
|
||||||
|
# 等待发送端连接
|
||||||
|
win32pipe.ConnectNamedPipe(self.rgb_send, None)
|
||||||
|
print("rgb_send connected.")
|
||||||
|
win32pipe.ConnectNamedPipe(self.spec_receive, None)
|
||||||
|
print("spec_receive connected.")
|
||||||
|
print('Fruit2程序启动成功!')
|
||||||
|
return self.rgb_receive, self.rgb_send, self.spec_receive
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"管道创建连接失败,失败原因: {e}")
|
||||||
|
print("等待5秒后重试...")
|
||||||
|
time.sleep(5)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def receive_rgb_data(self, rgb_receive):
|
||||||
|
try:
|
||||||
|
# 读取图片数据长度
|
||||||
|
len_img = win32file.ReadFile(rgb_receive, 4, None)
|
||||||
|
data_size = int.from_bytes(len_img[1], byteorder='big')
|
||||||
|
# 读取实际图片数据
|
||||||
|
result, data = win32file.ReadFile(rgb_receive, data_size, None)
|
||||||
|
# 检查读取操作是否成功
|
||||||
|
if result != 0:
|
||||||
|
logging.error(f"读取失败,错误代码: {result}")
|
||||||
|
return None
|
||||||
|
# 返回成功读取的数据
|
||||||
|
return data
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"数据接收失败,错误原因: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def receive_spec_data(self, spec_receive):
|
||||||
|
try:
|
||||||
|
# 读取光谱数据长度
|
||||||
|
len_spec = win32file.ReadFile(spec_receive, 4, None)
|
||||||
|
data_size = int.from_bytes(len_spec[1], byteorder='big')
|
||||||
|
# 读取光谱数据
|
||||||
|
result, spec_data = win32file.ReadFile(spec_receive, data_size, None)
|
||||||
|
# 检查读取操作是否成功
|
||||||
|
if result != 0:
|
||||||
|
logging.error(f"读取失败,错误代码: {result}")
|
||||||
|
return None
|
||||||
|
# 返回成功读取的数据
|
||||||
|
return spec_data
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"数据接收失败,错误原因: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def parse_img(self, data: bytes) -> (str, any):
|
||||||
|
"""
|
||||||
|
图像数据转换.
|
||||||
|
:param data:接收到的报文
|
||||||
|
:return: 指令类型和内容
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
assert len(data) > 1
|
||||||
|
except AssertionError:
|
||||||
|
logging.error('指令转换失败,长度不足2')
|
||||||
|
return '', None
|
||||||
|
cmd, data = data[:2], data[2:]
|
||||||
|
cmd = cmd.decode('ascii').strip().upper()
|
||||||
|
# 如果收到的是预热指令'YR',直接返回命令和None,不处理图像数据
|
||||||
|
if cmd == 'YR':
|
||||||
|
return cmd, None
|
||||||
|
n_rows, n_cols, img = data[:2], data[2:4], data[4:]
|
||||||
|
try:
|
||||||
|
n_rows, n_cols = [int.from_bytes(x, byteorder='big') for x in [n_rows, n_cols]]
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f'长宽转换失败, 错误代码{e}, 报文大小: n_rows:{n_rows}, n_cols:{n_cols}')
|
||||||
|
return '', None
|
||||||
|
try:
|
||||||
|
assert n_rows * n_cols * 3 == len(img)
|
||||||
|
# 因为是float32类型 所以长度要乘12 ,如果是uint8则乘3
|
||||||
|
except AssertionError:
|
||||||
|
logging.error('图像指令转换失败,数据长度错误')
|
||||||
|
return '', None
|
||||||
|
img = np.frombuffer(img, dtype=np.uint8).reshape(n_rows, n_cols, -1)
|
||||||
|
return cmd, img
|
||||||
|
|
||||||
|
def parse_spec(self, data: bytes) -> (str, any):
|
||||||
|
"""
|
||||||
|
光谱数据转换.
|
||||||
|
:param data:接收到的报文
|
||||||
|
:return: 指令类型和内容
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
assert len(data) > 2
|
||||||
|
except AssertionError:
|
||||||
|
logging.error('指令转换失败,长度不足3')
|
||||||
|
return '', None
|
||||||
|
cmd, data = data[:2], data[2:]
|
||||||
|
cmd = cmd.decode('ascii').strip().upper()
|
||||||
|
|
||||||
|
n_rows, n_cols, n_bands, spec = data[:2], data[2:4], data[4:6], data[6:]
|
||||||
|
try:
|
||||||
|
n_rows, n_cols, n_bands = [int.from_bytes(x, byteorder='big') for x in [n_rows, n_cols, n_bands]]
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f'长宽转换失败, 错误代码{e}, 报文大小: n_rows:{n_rows}, n_cols:{n_cols}, n_bands:{n_bands}')
|
||||||
|
return '', None
|
||||||
|
try:
|
||||||
|
assert n_rows * n_cols * n_bands * 2 == len(spec)
|
||||||
|
except AssertionError:
|
||||||
|
logging.error('图像指令转换失败,数据长度错误')
|
||||||
|
return '', None
|
||||||
|
spec = np.frombuffer(spec, dtype=np.uint16).reshape((n_rows, n_bands, -1)).transpose(0, 2, 1)
|
||||||
|
return cmd, spec
|
||||||
|
|
||||||
|
def send_data(self,cmd:str, brix, green_percentage, weight, diameter, defect_num, total_defect_area, rp):
|
||||||
|
'''
|
||||||
|
发送数据
|
||||||
|
:param cmd:
|
||||||
|
:param brix:
|
||||||
|
:param green_percentage:
|
||||||
|
:param weight:
|
||||||
|
:param diameter:
|
||||||
|
:param defect_num:
|
||||||
|
:param total_defect_area:
|
||||||
|
:param rp:
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
cmd = cmd.strip().upper()
|
||||||
|
cmd_type = 'RE'
|
||||||
|
cmd_re = cmd_type.upper().encode('ascii')
|
||||||
|
img = np.asarray(rp, dtype=np.uint8) # 将图像转换为 NumPy 数组
|
||||||
|
height = img.shape[0] # 获取图像的高度
|
||||||
|
width = img.shape[1] # 获取图像的宽度
|
||||||
|
height = height.to_bytes(2, byteorder='big')
|
||||||
|
width = width.to_bytes(2, byteorder='big')
|
||||||
|
img_bytes = img.tobytes()
|
||||||
|
diameter = int(diameter * 100).to_bytes(2, byteorder='big')
|
||||||
|
defect_num = defect_num.to_bytes(2, byteorder='big')
|
||||||
|
total_defect_area = int(total_defect_area * 1000).to_bytes(4, byteorder='big')
|
||||||
|
length = len(img_bytes) + 18
|
||||||
|
length = length.to_bytes(4, byteorder='big')
|
||||||
|
if cmd == 'TO':
|
||||||
|
brix = 0
|
||||||
|
brix = brix.to_bytes(2, byteorder='big')
|
||||||
|
gp = int(green_percentage * 100).to_bytes(1, byteorder='big')
|
||||||
|
weight = 0
|
||||||
|
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)
|
||||||
|
elif cmd == 'PF':
|
||||||
|
brix = int(brix * 1000).to_bytes(2, 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)
|
||||||
|
elif cmd == 'KO':
|
||||||
|
brix = 0
|
||||||
|
brix = brix.to_bytes(2, byteorder='big')
|
||||||
|
gp = 0
|
||||||
|
gp = gp.to_bytes(1, byteorder='big')
|
||||||
|
weight = 0
|
||||||
|
weight = weight.to_bytes(1, byteorder='big')
|
||||||
|
defect_num = 0
|
||||||
|
defect_num = defect_num.to_bytes(2, byteorder='big')
|
||||||
|
total_defect_area = 0
|
||||||
|
total_defect_area = total_defect_area.to_bytes(4, byteorder='big')
|
||||||
|
height = setting.n_rgb_rows
|
||||||
|
height = height.to_bytes(2, byteorder='big')
|
||||||
|
width = setting.n_rgb_cols
|
||||||
|
width = width.to_bytes(2, byteorder='big')
|
||||||
|
img_bytes = np.zeros((setting.n_rgb_rows, setting.n_rgb_cols, setting.n_rgb_bands),
|
||||||
|
dtype=np.uint8).tobytes()
|
||||||
|
length = (18).to_bytes(4, byteorder='big')
|
||||||
|
send_message = (length + cmd_re + brix + gp + diameter + weight +
|
||||||
|
defect_num + total_defect_area + height + width + img_bytes)
|
||||||
|
try:
|
||||||
|
win32file.WriteFile(self.rgb_send, send_message)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f'发送指令失败,错误类型:{e}')
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def create_file(file_name):
|
||||||
|
"""
|
||||||
|
创建文件
|
||||||
|
:param file_name: 文件名
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
if os.path.exists(file_name):
|
||||||
|
print("文件存在:%s" % file_name)
|
||||||
|
return False
|
||||||
|
# os.remove(file_name) # 删除已有文件
|
||||||
|
if not os.path.exists(file_name):
|
||||||
|
print("文件不存在,创建文件:%s" % file_name)
|
||||||
|
open(file_name, 'a').close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
class Logger(object):
|
||||||
|
def __init__(self, is_to_file=False, path=None):
|
||||||
|
self.is_to_file = is_to_file
|
||||||
|
if path is None:
|
||||||
|
path = "tomato-passion_fruit.log"
|
||||||
|
self.path = path
|
||||||
|
create_file(path)
|
||||||
|
|
||||||
|
def log(self, content):
|
||||||
|
if self.is_to_file:
|
||||||
|
with open(self.path, "a") as f:
|
||||||
|
print(time.strftime("[%Y-%m-%d_%H-%M-%S]:"), file=f)
|
||||||
|
print(content, file=f)
|
||||||
|
else:
|
||||||
|
print(content)
|
||||||
Loading…
Reference in New Issue
Block a user