v1.1.5
This commit is contained in:
353
vision.py
353
vision.py
@@ -12,6 +12,241 @@ from maix import image
|
||||
import config
|
||||
from logger_manager import logger_manager
|
||||
|
||||
def check_laser_point_sharpness(frame, laser_point=None, roi_size=30, threshold=100.0, ellipse_params=None):
|
||||
"""
|
||||
检测激光点本身的清晰度(不是整个靶子)
|
||||
|
||||
Args:
|
||||
frame: 图像帧对象
|
||||
laser_point: 激光点坐标 (x, y),如果为None则自动查找
|
||||
roi_size: ROI区域大小(像素),默认30x30
|
||||
threshold: 清晰度阈值
|
||||
ellipse_params: 椭圆参数 ((center_x, center_y), (width, height), angle),用于限制激光点必须在椭圆内
|
||||
|
||||
Returns:
|
||||
(is_sharp, sharpness_score, laser_pos): (是否清晰, 清晰度分数, 激光点坐标)
|
||||
"""
|
||||
try:
|
||||
# 1. 如果没有提供激光点,先查找
|
||||
if laser_point is None:
|
||||
from laser_manager import laser_manager
|
||||
laser_point = laser_manager.find_red_laser(frame, ellipse_params=ellipse_params)
|
||||
if laser_point is None:
|
||||
logger_manager.logger.debug(f"未找到激光点")
|
||||
return False, 0.0, None
|
||||
|
||||
x, y = laser_point
|
||||
|
||||
# 2. 转换为 OpenCV 格式
|
||||
img_cv = image.image2cv(frame, False, False)
|
||||
h, w = img_cv.shape[:2]
|
||||
|
||||
# 3. 提取 ROI 区域(激光点周围)
|
||||
roi_half = roi_size // 2
|
||||
x_min = max(0, int(x) - roi_half)
|
||||
x_max = min(w, int(x) + roi_half)
|
||||
y_min = max(0, int(y) - roi_half)
|
||||
y_max = min(h, int(y) + roi_half)
|
||||
|
||||
roi = img_cv[y_min:y_max, x_min:x_max]
|
||||
|
||||
if roi.size == 0:
|
||||
return False, 0.0, laser_point
|
||||
|
||||
# 4. 转换为灰度图(用于清晰度检测)
|
||||
gray_roi = cv2.cvtColor(roi, cv2.COLOR_RGB2GRAY)
|
||||
|
||||
# 5. 方法1:检测点的扩散程度(能量集中度)
|
||||
# 计算中心区域的能量集中度
|
||||
center_x, center_y = roi.shape[1] // 2, roi.shape[0] // 2
|
||||
center_radius = min(5, roi.shape[0] // 4) # 中心区域半径
|
||||
|
||||
# 创建中心区域的掩码
|
||||
y_coords, x_coords = np.ogrid[:roi.shape[0], :roi.shape[1]]
|
||||
center_mask = (x_coords - center_x)**2 + (y_coords - center_y)**2 <= center_radius**2
|
||||
|
||||
# 计算中心区域和周围区域的亮度
|
||||
center_brightness = gray_roi[center_mask].mean()
|
||||
outer_mask = ~center_mask
|
||||
outer_brightness = gray_roi[outer_mask].mean() if np.any(outer_mask) else 0
|
||||
|
||||
# 对比度(清晰的点对比度高)
|
||||
contrast = abs(center_brightness - outer_brightness)
|
||||
|
||||
# 6. 方法2:检测点的边缘锐度(使用拉普拉斯)
|
||||
laplacian = cv2.Laplacian(gray_roi, cv2.CV_64F)
|
||||
edge_sharpness = abs(laplacian).var()
|
||||
|
||||
# 7. 方法3:检测点的能量集中度(方差)
|
||||
# 清晰的点:能量集中在中心,方差小
|
||||
# 模糊的点:能量分散,方差大
|
||||
# 但我们需要的是:清晰的点中心亮度高,周围低,所以梯度大
|
||||
sobel_x = cv2.Sobel(gray_roi, cv2.CV_64F, 1, 0, ksize=3)
|
||||
sobel_y = cv2.Sobel(gray_roi, cv2.CV_64F, 0, 1, ksize=3)
|
||||
gradient = np.sqrt(sobel_x**2 + sobel_y**2)
|
||||
gradient_sharpness = gradient.var()
|
||||
|
||||
# 8. 组合多个指标
|
||||
# 对比度权重0.3,边缘锐度权重0.4,梯度权重0.3
|
||||
sharpness_score = (contrast * 0.3 + edge_sharpness * 0.4 + gradient_sharpness * 0.3)
|
||||
|
||||
is_sharp = sharpness_score >= threshold
|
||||
|
||||
logger = logger_manager.logger
|
||||
if logger:
|
||||
logger.debug(f"[VISION] 激光点清晰度: 位置=({x}, {y}), 对比度={contrast:.2f}, 边缘={edge_sharpness:.2f}, 梯度={gradient_sharpness:.2f}, 综合={sharpness_score:.2f}, 是否清晰={is_sharp}")
|
||||
|
||||
return is_sharp, sharpness_score, laser_point
|
||||
|
||||
except Exception as e:
|
||||
logger = logger_manager.logger
|
||||
if logger:
|
||||
logger.error(f"[VISION] 激光点清晰度检测失败: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
return False, 0.0, laser_point
|
||||
|
||||
def check_image_sharpness(frame, threshold=100.0, save_debug_images=False):
|
||||
"""
|
||||
检查图像清晰度(针对圆形靶子优化,基于圆形边缘检测)
|
||||
检测靶心的圆形边缘,计算边缘区域的梯度清晰度
|
||||
|
||||
Args:
|
||||
frame: 图像帧对象
|
||||
threshold: 清晰度阈值,低于此值认为图像模糊(默认100.0)
|
||||
可以根据实际情况调整:
|
||||
- 清晰图像通常 > 200
|
||||
- 模糊图像通常 < 100
|
||||
- 中等清晰度 100-200
|
||||
save_debug_images: 是否保存调试图像(原始图和边缘图),默认False
|
||||
|
||||
Returns:
|
||||
(is_sharp, sharpness_score): (是否清晰, 清晰度分数)
|
||||
"""
|
||||
try:
|
||||
logger_manager.logger.debug(f"begin")
|
||||
# 转换为 OpenCV 格式
|
||||
img_cv = image.image2cv(frame, False, False)
|
||||
logger_manager.logger.debug(f"after image2cv")
|
||||
|
||||
# 转换为 HSV 颜色空间
|
||||
hsv = cv2.cvtColor(img_cv, cv2.COLOR_RGB2HSV)
|
||||
h, s, v = cv2.split(hsv)
|
||||
logger_manager.logger.debug(f"after HSV conversion")
|
||||
|
||||
# 检测黄色区域(靶心)
|
||||
# 调整饱和度策略:稍微增强,不要过度
|
||||
s_enhanced = np.clip(s * 1.1, 0, 255).astype(np.uint8)
|
||||
hsv_enhanced = cv2.merge((h, s_enhanced, v))
|
||||
|
||||
# HSV 阈值范围(与 detect_circle_v3 保持一致)
|
||||
lower_yellow = np.array([7, 80, 0])
|
||||
upper_yellow = np.array([32, 255, 255])
|
||||
mask_yellow = cv2.inRange(hsv_enhanced, lower_yellow, upper_yellow)
|
||||
|
||||
# 形态学操作,填充小孔洞
|
||||
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
|
||||
mask_yellow = cv2.morphologyEx(mask_yellow, cv2.MORPH_CLOSE, kernel)
|
||||
logger_manager.logger.debug(f"after yellow mask detection")
|
||||
|
||||
# 计算边缘区域:扩展黄色区域,然后减去原始区域,得到边缘区域
|
||||
mask_dilated = cv2.dilate(mask_yellow, kernel, iterations=2)
|
||||
mask_edge = cv2.subtract(mask_dilated, mask_yellow) # 边缘区域
|
||||
|
||||
# 计算边缘区域的像素数量
|
||||
edge_pixel_count = np.sum(mask_edge > 0)
|
||||
logger_manager.logger.debug(f"edge pixel count: {edge_pixel_count}")
|
||||
|
||||
# 如果检测不到边缘区域,使用全局梯度作为后备方案
|
||||
if edge_pixel_count < 100:
|
||||
logger_manager.logger.debug(f"edge region too small, using global gradient")
|
||||
# 使用 V 通道计算全局梯度
|
||||
sobel_v_x = cv2.Sobel(v, cv2.CV_64F, 1, 0, ksize=3)
|
||||
sobel_v_y = cv2.Sobel(v, cv2.CV_64F, 0, 1, ksize=3)
|
||||
gradient = np.sqrt(sobel_v_x**2 + sobel_v_y**2)
|
||||
sharpness_score = gradient.var()
|
||||
logger_manager.logger.debug(f"global gradient variance: {sharpness_score:.2f}")
|
||||
else:
|
||||
# 在边缘区域计算梯度清晰度
|
||||
# 使用 V(亮度)通道计算梯度,因为边缘在亮度上通常很明显
|
||||
sobel_v_x = cv2.Sobel(v, cv2.CV_64F, 1, 0, ksize=3)
|
||||
sobel_v_y = cv2.Sobel(v, cv2.CV_64F, 0, 1, ksize=3)
|
||||
gradient = np.sqrt(sobel_v_x**2 + sobel_v_y**2)
|
||||
|
||||
# 只在边缘区域计算清晰度
|
||||
edge_gradient = gradient[mask_edge > 0]
|
||||
|
||||
if len(edge_gradient) > 0:
|
||||
# 计算边缘梯度的方差(清晰图像的边缘梯度变化大)
|
||||
sharpness_score = edge_gradient.var()
|
||||
# 也可以使用均值作为补充指标(清晰图像的边缘梯度均值也较大)
|
||||
gradient_mean = edge_gradient.mean()
|
||||
logger_manager.logger.debug(f"edge gradient: mean={gradient_mean:.2f}, var={sharpness_score:.2f}, pixels={len(edge_gradient)}")
|
||||
else:
|
||||
# 如果边缘区域没有有效梯度,使用全局梯度
|
||||
sharpness_score = gradient.var()
|
||||
logger_manager.logger.debug(f"no edge gradient, using global: {sharpness_score:.2f}")
|
||||
|
||||
# 保存调试图像(如果启用)
|
||||
if save_debug_images:
|
||||
try:
|
||||
debug_dir = config.PHOTO_DIR
|
||||
if debug_dir not in os.listdir("/root"):
|
||||
try:
|
||||
os.mkdir(debug_dir)
|
||||
except:
|
||||
pass
|
||||
|
||||
# 生成文件名
|
||||
try:
|
||||
all_images = [f for f in os.listdir(debug_dir) if f.endswith(('.bmp', '.jpg', '.jpeg'))]
|
||||
img_count = len(all_images)
|
||||
except:
|
||||
img_count = 0
|
||||
|
||||
# 保存原始图像
|
||||
img_orig = image.cv2image(img_cv, False, False)
|
||||
orig_filename = f"{debug_dir}/sharpness_debug_orig_{img_count:04d}.bmp"
|
||||
img_orig.save(orig_filename)
|
||||
|
||||
# # 保存边缘检测结果(可视化)
|
||||
# # 创建可视化图像:原始图像 + 黄色区域 + 边缘区域
|
||||
# debug_img = img_cv.copy()
|
||||
# # 在黄色区域绘制绿色
|
||||
# debug_img[mask_yellow > 0] = [0, 255, 0] # RGB格式,绿色
|
||||
# # 在边缘区域绘制红色
|
||||
# debug_img[mask_edge > 0] = [255, 0, 0] # RGB格式,红色
|
||||
|
||||
# debug_img_maix = image.cv2image(debug_img, False, False)
|
||||
# debug_filename = f"{debug_dir}/sharpness_debug_edge_{img_count:04d}.bmp"
|
||||
# debug_img_maix.save(debug_filename)
|
||||
|
||||
# logger = logger_manager.logger
|
||||
# if logger:
|
||||
# logger.info(f"[VISION] 保存调试图像: {orig_filename}, {debug_filename}")
|
||||
except Exception as e:
|
||||
logger = logger_manager.logger
|
||||
if logger:
|
||||
logger.warning(f"[VISION] 保存调试图像失败: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
is_sharp = sharpness_score >= threshold
|
||||
|
||||
logger = logger_manager.logger
|
||||
if logger:
|
||||
logger.debug(f"[VISION] 清晰度检测: 分数={sharpness_score:.2f}, 边缘像素数={edge_pixel_count}, 是否清晰={is_sharp}, 阈值={threshold}")
|
||||
|
||||
return is_sharp, sharpness_score
|
||||
except Exception as e:
|
||||
logger = logger_manager.logger
|
||||
if logger:
|
||||
logger.error(f"[VISION] 清晰度检测失败: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
# 出错时返回 False,避免使用模糊图像
|
||||
return False, 0.0
|
||||
|
||||
def save_calibration_image(frame, laser_pos, photo_dir=None):
|
||||
"""
|
||||
保存激光校准图像(带标注)
|
||||
@@ -278,22 +513,24 @@ def estimate_distance(pixel_radius):
|
||||
return 0.0
|
||||
return (config.REAL_RADIUS_CM * config.FOCAL_LENGTH_PIX) / pixel_radius / 100.0
|
||||
|
||||
|
||||
def compute_laser_position(circle_center, laser_point, radius, method):
|
||||
"""计算激光相对于靶心的偏移量(单位:厘米)"""
|
||||
if not all([circle_center, radius, method]):
|
||||
return None, None
|
||||
cx, cy = circle_center
|
||||
lx, ly = 320, 230
|
||||
# 根据检测方法动态调整靶心物理半径(简化模型)
|
||||
circle_r = (radius / 4.0) * 20.0 if method == "模糊" else (68 / 16.0) * 20.0
|
||||
dx = lx - cx
|
||||
dy = ly - cy
|
||||
return dx / (circle_r / 100.0), -dy / (circle_r / 100.0)
|
||||
|
||||
def estimate_pixel(physical_distance_cm, target_distance_m):
|
||||
"""
|
||||
根据物理距离和目标距离计算对应的像素偏移
|
||||
|
||||
Args:
|
||||
physical_distance_cm: 物理世界中的距离(厘米),例如激光与摄像头的距离
|
||||
target_distance_m: 目标距离(米),例如到靶心的距离
|
||||
|
||||
Returns:
|
||||
float: 对应的像素偏移
|
||||
"""
|
||||
if not target_distance_m or target_distance_m <= 0:
|
||||
return 0.0
|
||||
# 公式:像素偏移 = (物理距离_米) * 焦距_像素 / 目标距离_米
|
||||
return (physical_distance_cm / 100.0) * config.FOCAL_LENGTH_PIX / target_distance_m
|
||||
|
||||
def save_shot_image(result_img, center, radius, method, ellipse_params,
|
||||
laser_point, distance_m, photo_dir=None):
|
||||
laser_point, distance_m, shot_id=None, photo_dir=None):
|
||||
"""
|
||||
保存射击图像(带标注)
|
||||
即使没有检测到靶心也会保存图像,文件名会标注 "no_target"
|
||||
@@ -307,6 +544,7 @@ def save_shot_image(result_img, center, radius, method, ellipse_params,
|
||||
ellipse_params: 椭圆参数 ((center, (width, height), angle)) 或 None
|
||||
laser_point: 激光点坐标 (x, y)
|
||||
distance_m: 距离(米),可能为 None(未检测到靶心)
|
||||
shot_id: 射箭ID,如果提供则用作文件名,否则使用旧的文件名格式
|
||||
photo_dir: 照片存储目录,如果为None则使用 config.PHOTO_DIR
|
||||
|
||||
Returns:
|
||||
@@ -327,28 +565,39 @@ def save_shot_image(result_img, center, radius, method, ellipse_params,
|
||||
except:
|
||||
pass
|
||||
|
||||
# 生成文件名
|
||||
# 统计所有图片文件(包括 .bmp 和 .jpg)
|
||||
try:
|
||||
all_images = [f for f in os.listdir(photo_dir) if f.endswith(('.bmp', '.jpg', '.jpeg'))]
|
||||
img_count = len(all_images)
|
||||
except:
|
||||
img_count = 0
|
||||
|
||||
x, y = laser_point
|
||||
|
||||
# 如果未检测到靶心,在文件名中标注
|
||||
if center is None or radius is None:
|
||||
method_str = "no_target"
|
||||
distance_str = "000"
|
||||
# 生成文件名:优先使用 shot_id,否则使用旧格式
|
||||
if shot_id:
|
||||
# 使用射箭ID作为文件名
|
||||
# 如果未检测到靶心,在文件名中标注
|
||||
if center is None or radius is None:
|
||||
filename = f"{photo_dir}/shot_{shot_id}_no_target.bmp"
|
||||
else:
|
||||
method_str = method or "unknown"
|
||||
filename = f"{photo_dir}/shot_{shot_id}_{method_str}.bmp"
|
||||
else:
|
||||
method_str = method or "unknown"
|
||||
distance_str = str(round((distance_m or 0.0) * 100))
|
||||
|
||||
filename = f"{photo_dir}/{method_str}_{int(x)}_{int(y)}_{distance_str}_{img_count:04d}.bmp"
|
||||
# 旧的文件名格式(向后兼容)
|
||||
try:
|
||||
all_images = [f for f in os.listdir(photo_dir) if f.endswith(('.bmp', '.jpg', '.jpeg'))]
|
||||
img_count = len(all_images)
|
||||
except:
|
||||
img_count = 0
|
||||
|
||||
# 如果未检测到靶心,在文件名中标注
|
||||
if center is None or radius is None:
|
||||
method_str = "no_target"
|
||||
distance_str = "000"
|
||||
else:
|
||||
method_str = method or "unknown"
|
||||
distance_str = str(round((distance_m or 0.0) * 100))
|
||||
|
||||
filename = f"{photo_dir}/{method_str}_{int(x)}_{int(y)}_{distance_str}_{img_count:04d}.bmp"
|
||||
|
||||
logger = logger_manager.logger
|
||||
if logger:
|
||||
if shot_id:
|
||||
logger.info(f"[VISION] 保存射箭图像,ID: {shot_id}, 文件名: {filename}")
|
||||
if center and radius:
|
||||
logger.info(f"结果 -> 圆心: {center}, 半径: {radius}, 方法: {method}")
|
||||
if ellipse_params:
|
||||
@@ -360,23 +609,35 @@ def save_shot_image(result_img, center, radius, method, ellipse_params,
|
||||
# 转换图像为 OpenCV 格式以便绘制
|
||||
img_cv = image.image2cv(result_img, False, False)
|
||||
|
||||
# 确保激光十字线被绘制(使用OpenCV在图像上绘制,确保可见性)
|
||||
laser_color = (config.LASER_COLOR[0], config.LASER_COLOR[1], config.LASER_COLOR[2])
|
||||
thickness = max(config.LASER_THICKNESS, 2) # 至少2像素宽,确保可见
|
||||
length = max(config.LASER_LENGTH, 10) # 至少10像素长
|
||||
# # 确保激光十字线被绘制(使用OpenCV在图像上绘制,确保可见性)
|
||||
# laser_color = (config.LASER_COLOR[0], config.LASER_COLOR[1], config.LASER_COLOR[2])
|
||||
# thickness = max(config.LASER_THICKNESS, 2) # 至少2像素宽,确保可见
|
||||
# length = max(config.LASER_LENGTH, 10) # 至少10像素长
|
||||
|
||||
# 绘制激光十字线(水平线)
|
||||
cv2.line(img_cv,
|
||||
(int(x - length), int(y)),
|
||||
(int(x + length), int(y)),
|
||||
laser_color, thickness)
|
||||
# 绘制激光十字线(垂直线)
|
||||
cv2.line(img_cv,
|
||||
(int(x), int(y - length)),
|
||||
(int(x), int(y + length)),
|
||||
laser_color, thickness)
|
||||
# 绘制激光点
|
||||
cv2.circle(img_cv, (int(x), int(y)), max(thickness, 3), laser_color, -1)
|
||||
# # 绘制激光十字线(水平线)
|
||||
# cv2.line(img_cv,
|
||||
# (int(x - length), int(y)),
|
||||
# (int(x + length), int(y)),
|
||||
# laser_color, thickness)
|
||||
# # 绘制激光十字线(垂直线)
|
||||
# cv2.line(img_cv,
|
||||
# (int(x), int(y - length)),
|
||||
# (int(x), int(y + length)),
|
||||
# laser_color, thickness)
|
||||
# # 绘制激光点
|
||||
# cv2.circle(img_cv, (int(x), int(y)), max(thickness, 3), laser_color, -1)
|
||||
# 在 vision.py 的 save_shot_image 函数中,替换第598-614行的代码:
|
||||
|
||||
# 绘制激光点标注(使用空心圆圈,类似校准时的标注方式)
|
||||
laser_color = (config.LASER_COLOR[0], config.LASER_COLOR[1], config.LASER_COLOR[2])
|
||||
thickness = 1 # 圆圈线宽
|
||||
|
||||
# 绘制外圈(半径10,空心)
|
||||
cv2.circle(img_cv, (int(x), int(y)), 10, laser_color, thickness)
|
||||
# 绘制中圈(半径5,空心)
|
||||
cv2.circle(img_cv, (int(x), int(y)), 5, laser_color, thickness)
|
||||
# 绘制中心点(半径2,实心,用于精确定位)
|
||||
cv2.circle(img_cv, (int(x), int(y)), 2, laser_color, -1)
|
||||
|
||||
# 如果检测到靶心,绘制靶心标注
|
||||
if center and radius:
|
||||
|
||||
Reference in New Issue
Block a user