Files
zjsh_yolov11/yemian/yemian_line/2line—distance.py
琉璃月光 df7c0730f5 bushu
2025-10-21 14:11:52 +08:00

228 lines
8.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ===================================================
# final_compare_corner.py
# 同时显示 Canny 物理边缘(红线)和 YOLO 预测左边缘(绿线)
# 基于角点拟合直线,剔除离群点
# ===================================================
import os
import cv2
import numpy as np
from pathlib import Path
from ultralytics import YOLO
# ============================
# 参数
# ============================
TARGET_SIZE = 640
MAX_CORNERS = 200
QUALITY_LEVEL = 0.01
MIN_DISTANCE = 5
DIST_THRESH = 15
ROIS = [(859, 810, 696, 328)] # 全局 ROI可按需修改
OUTPUT_DIR = "./final_output"
# ============================
# Canny 边缘部分(保持原逻辑)
# ============================
def load_global_rois(txt_path):
rois = []
if not os.path.exists(txt_path):
print(f"❌ ROI 文件不存在: {txt_path}")
return rois
with open(txt_path, 'r') as f:
for line in f:
s = line.strip()
if s:
try:
x, y, w, h = map(int, s.split(','))
rois.append((x, y, w, h))
except Exception as e:
print(f"⚠️ 无法解析 ROI 行 '{s}': {e}")
return rois
def fit_line_best(points, distance_thresh=5, max_iter=5):
if len(points) < 2:
return None
points = points.astype(np.float32)
for _ in range(max_iter):
mean = np.mean(points, axis=0)
cov = np.cov(points.T)
eigvals, eigvecs = np.linalg.eig(cov)
idx = np.argmax(eigvals)
direction = eigvecs[:, idx]
vx, vy = direction
x0, y0 = mean
dists = np.abs(vy*(points[:,0]-x0) - vx*(points[:,1]-y0)) / np.hypot(vx, vy)
inliers = points[dists <= distance_thresh]
if len(inliers) == len(points) or len(inliers) < 2:
break
points = inliers
if len(points) < 2:
return None
X = points[:, 0].reshape(-1, 1)
y = points[:, 1]
try:
from sklearn.linear_model import RANSACRegressor
ransac = RANSACRegressor(residual_threshold=distance_thresh)
ransac.fit(X, y)
k = ransac.estimator_.coef_[0]
b = ransac.estimator_.intercept_
vx = 1 / np.sqrt(1 + k**2)
vy = k / np.sqrt(1 + k**2)
x0 = np.mean(points[:,0])
y0 = k*x0 + b
except:
mean = np.mean(points, axis=0)
cov = np.cov(points.T)
eigvals, eigvecs = np.linalg.eig(cov)
idx = np.argmax(eigvals)
direction = eigvecs[:, idx]
vx, vy = direction
x0, y0 = mean
return vx, vy, x0, y0
def extract_canny_overlay(image_path, roi_file, distance_thresh=3):
img = cv2.imread(image_path)
if img is None:
print(f"❌ 无法读取图片: {image_path}")
return None
overlay_img = img.copy()
rois = load_global_rois(roi_file)
if not rois:
print("❌ 没有有效 ROI")
return overlay_img
for idx, (x, y, w, h) in enumerate(rois):
roi = img[y:y+h, x:x+w]
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 100, 200)
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
longest_contour = max(contours, key=lambda c: cv2.arcLength(c, closed=False), default=None)
if longest_contour is not None and len(longest_contour) >= 2:
points = longest_contour.reshape(-1, 2)
line = fit_line_best(points, distance_thresh=distance_thresh, max_iter=5)
if line is not None:
vx, vy, x0, y0 = line
cols = w
lefty = int(y0 - vy/vx * x0)
righty = int(y0 + vy/vx * (cols - x0))
pt1 = (x, y + lefty)
pt2 = (x + cols - 1, y + righty)
cv2.line(overlay_img, pt1, pt2, (0, 0, 255), 2) # 红色
print(f"✅ ROI {idx} Canny 边缘拟合完成")
return overlay_img
# ============================
# YOLO 角点 + 拟合直线
# ============================
def select_edge_corners(corners, w, left_ratio=0.2, right_ratio=0.2, y_var_thresh=5):
if corners is None:
return np.zeros((0,2), dtype=np.int32), np.zeros((0,2), dtype=np.int32)
corners = np.int32(corners).reshape(-1,2)
left_thresh = int(w*left_ratio)
right_thresh = w - int(w*right_ratio)
left_candidates = corners[corners[:,0]<=left_thresh]
right_candidates = corners[corners[:,0]>=right_thresh]
def filter_by_y_variation(pts):
if len(pts)<2:
return pts.astype(np.int32)
pts_sorted = pts[np.argsort(pts[:,1])]
diffs = np.abs(np.diff(pts_sorted[:,1]))
keep_idx = np.where(diffs>y_var_thresh)[0]
if len(keep_idx)==0:
return pts_sorted.astype(np.int32)
selected = [pts_sorted[i] for i in keep_idx]+[pts_sorted[i+1] for i in keep_idx]
selected = np.array(selected)
_, idx = np.unique(selected.reshape(-1,2), axis=0, return_index=True)
selected = selected[np.sort(idx)]
return selected.astype(np.int32)
return filter_by_y_variation(left_candidates), filter_by_y_variation(right_candidates)
def fit_line_with_outlier_removal(pts, dist_thresh=DIST_THRESH):
if pts is None or len(pts)<2:
return None, np.zeros((0,2), dtype=np.int32)
pts = np.array(pts, dtype=np.float64)
x, y = pts[:,0], pts[:,1]
try:
m, b = np.polyfit(y, x, 1)
except:
return None, pts.astype(np.int32)
x_fit = m*y + b
mask = np.abs(x-x_fit)<dist_thresh
if np.sum(mask)<2:
return (m,b), pts.astype(np.int32)
x2, y2 = x[mask], y[mask]
m2, b2 = np.polyfit(y2, x2, 1)
inliers = np.stack([x2,y2],axis=1).astype(np.int32)
return (m2,b2), inliers
def get_yolo_left_edge_lines(image_path, model_path, rois=ROIS, imgsz=TARGET_SIZE):
model = YOLO(model_path)
img = cv2.imread(image_path)
if img is None:
print(f"❌ 无法读取图片: {image_path}")
return []
lines = []
for (x, y, w, h) in rois:
roi_img = img[y:y+h, x:x+w]
resized = cv2.resize(roi_img, (imgsz, imgsz))
results = model(resized, imgsz=imgsz, verbose=False)
result = results[0]
if result.masks is None or len(result.masks.data)==0:
continue
mask = result.masks.data[0].cpu().numpy()
mask_bin = (mask>0.5).astype(np.uint8)
mask_bin = cv2.resize(mask_bin, (w,h), interpolation=cv2.INTER_NEAREST)
# 角点检测
mask_gray = (mask_bin*255).astype(np.uint8)
corners = cv2.goodFeaturesToTrack(mask_gray, maxCorners=MAX_CORNERS,
qualityLevel=QUALITY_LEVEL, minDistance=MIN_DISTANCE)
left_pts, _ = select_edge_corners(corners, w)
line_params, inliers = fit_line_with_outlier_removal(left_pts)
if line_params is None:
continue
m,b = line_params
y1, y2 = 0, h-1
x1 = int(m*y1 + b)
x2 = int(m*y2 + b)
lines.append((x+x1, y+y1, x+x2, y+y2))
return lines
# ============================
# 对比融合
# ============================
def compare_canny_vs_yolo(image_path, canny_roi_file, model_path, output_dir=OUTPUT_DIR):
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
canny_img = extract_canny_overlay(image_path, canny_roi_file, distance_thresh=6)
if canny_img is None:
return
yolo_lines = get_yolo_left_edge_lines(image_path, model_path)
result_img = canny_img.copy()
for x1,y1,x2,y2 in yolo_lines:
cv2.line(result_img, (x1,y1), (x2,y2), (0,255,0), 2) # 绿色
cv2.circle(result_img, (x1,y1), 4, (255,0,0), -1) # 蓝色起点
output_path = output_dir / f"compare_{Path(image_path).stem}.jpg"
cv2.imwrite(str(output_path), result_img)
print(f"✅ 对比图已保存: {output_path}")
# ============================
# 使用示例
# ============================
if __name__ == "__main__":
IMAGE_PATH = "../test_image/2.jpg"
CANNY_ROI_FILE = "../roi_coordinates/1_rois1.txt"
MODEL_PATH = "best.pt"
compare_canny_vs_yolo(IMAGE_PATH, CANNY_ROI_FILE, MODEL_PATH)