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

150 lines
4.7 KiB
Python

import os
import cv2
import numpy as np
from pathlib import Path
from sklearn.linear_model import RANSACRegressor
# ---------------------------
# 读取 ROI 列表 txt
# ---------------------------
def load_rois_from_txt(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
# ---------------------------
# PCA + RANSAC + 迭代去离群点拟合直线
# ---------------------------
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
# RANSAC 拟合 y = kx + b
X = points[:, 0].reshape(-1, 1)
y = points[:, 1]
try:
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
# ---------------------------
# 封装函数:读取 ROI txt -> 拟合直线 -> 可视化
# ---------------------------
def fit_lines_from_image_txt(image_path, roi_txt_path, distance_thresh=5, draw_overlay=True):
"""
输入:
image_path: 原图路径
roi_txt_path: ROI txt 文件路径,每行 x,y,w,h
distance_thresh: 直线拟合残差阈值
draw_overlay: 是否在原图上叠加拟合直线
输出:
lines: 每个 ROI 的拟合直线 [(vx, vy, x0, y0), ...]
overlay_img: 可视化原图叠加拟合直线
"""
img = cv2.imread(image_path)
if img is None:
print(f"❌ 无法读取图片: {image_path}")
return [], None
rois = load_rois_from_txt(roi_txt_path)
if not rois:
print("❌ 没有有效 ROI")
return [], None
overlay_img = img.copy() if draw_overlay else None
lines = []
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:
vx, vy, x0, y0 = line
lines.append(line)
if draw_overlay:
cols = gray.shape[1]
lefty = int(y0 - vy/vx * x0)
righty = int(y0 + vy/vx * (cols - x0))
# 绘制在原图
cv2.line(overlay_img, (x, y + lefty), (x + cols - 1, y + righty), (0, 0, 255), 2)
cv2.drawContours(overlay_img, [longest_contour + np.array([x, y])], -1, (0, 255, 0), 1)
else:
lines.append(None)
else:
lines.append(None)
return lines, overlay_img
# ---------------------------
# 使用示例
# ---------------------------
if __name__ == "__main__":
image_path = "../test_image/1.jpg"
roi_txt_path = "../roi_coordinates/1_rois1.txt"
lines, overlay = fit_lines_from_image_txt(image_path, roi_txt_path, distance_thresh=5)
for idx, line in enumerate(lines):
print(f"ROI {idx} 拟合直线: {line}")
if overlay is not None:
cv2.imwrite("overlay_result.jpg", overlay)
print("✅ 原图叠加拟合直线已保存: overlay_result.jpg")