【无标题】

发布于:2025-06-16 ⋅ 阅读:(22) ⋅ 点赞:(0)

✅ 什么是 KMeans 聚类?为什么要用它?

通俗解释:
KMeans 聚类就像“自动分类器”,它根据像素的灰度值,把整张图分成亮度不同的几类区域。比如,把黑色背景、亮一点的重影、最亮的主影区分开。

为什么用它:
图像中的亮度差异很明显:

  • 背景暗(低灰度)

  • 重影比背景亮但比主影暗

  • 主影最亮

KMeans 可以自动分组像素,不需要手动设阈值,适应性强,适用于批量图像处理。


✅ 什么是“形态学去噪”?用来干什么?

通俗解释:
形态学操作(闭运算 + 开运算)是图像“清洗”的方法。它像拿橡皮擦先“填补小缝”,再“擦除小点”。

  • 闭运算(先膨胀后腐蚀):把小洞、小黑缝“补掉”,比如有些线段断裂的地方。

  • 开运算(先腐蚀后膨胀):把孤立的小白点(噪声)清除掉,不影响主体。

为什么用它:
提取主影/重影区域时会有毛刺或孤立点,用这两步操作可以得到干净、光滑、连通的区域。


✅ 如何提取矩形?为什么这么做?

通俗解释:
图像中白色区域一块块的,就是我们要的主影或重影,提取过程如下:

  1. findContours:找出每块白色区域的边界;

  2. minAreaRect:给每块区域找一个最小的矩形框包住它(不一定是水平的);

  3. 过滤小面积:剔除掉太小的区域(可能是误识别或噪声);

  4. 保存矩形四个顶点和中心点,供后续识别/排序使用。

为什么这么做:
HUD 图像的线段都是“矩形状”,用这个方式提取,可以准确还原出每一条本体/重影线条的位置、大小和角度。

整体框架分析

  1. 图像预处理

    • 读取图像:使用 cv2.imread() 加载图像;灰度化 + 高斯模糊降噪。

    • 目的:减少噪点干扰,提高 KMeans 聚类的稳定性和准确性。

  2. KMeans 聚类分离区域

    • 将图像像素聚为三类:背景、重影、主影。

    • 使用灰度聚类,自动划分亮度区域,无需人工阈值。

    • np.argsort(centers):按亮度从低到高排序,最高为主影,中间为重影。

  3. 重影精细扣除

    • 先对主影膨胀,再将其从重影中抠除,避免重叠误判。

    • 形态学操作:闭操作填孔洞,开操作去小块,保留连续区域。

  4. 轮廓提取与筛选

    • cv2.findContours 找出边界,cv2.minAreaRect 提取最小包围矩形。

    • 通过面积过滤去除异常区域,确保提取的是完整主影/重影。

  5. 重影恢复

    • 判断是否为竖直主影,如是,则计算重影偏移并“拼回”完整矩形。

    • 避免因重影被主影遮挡而只识别半个的问题。

  6. 排序与主-重影匹配

    • 主影按 (y, x) 排序。

    • 重影按“与主影最接近”策略匹配,一一对应,确保顺序一致。

  7. 日志输出与可视化

    • 输出数量、坐标、偏移关系。

    • 在图像上绘制每个主影和重影的轮廓和编号(M0/G0…),用于工程核对。


核心算法步骤流程图


mermaid

复制编辑

flowchart TD A[读取图像 & 灰度化 & 高斯模糊] --> B[KMeans聚类(3类)] B --> C[构建主影/重影掩码] C --> D[主影膨胀 & 精细扣除重影] D --> E[闭/开操作去噪] E --> F[findContours + 最小外接矩形提取] F --> G[竖直重影恢复] G --> H1[主影中心按(y,x)排序] H1 --> H2[重影与主影一一匹配] H2 --> I[输出偏移日志] I --> J[绘图标注编号]

import cv2  # OpenCV库,用于图像处理
import numpy as np  # 数值计算库
import os  # 文件系统操作


def detect_rectangles(image_path, log_dir='log', rows=5, cols=9,
                      main_ratio=0.5, ghost_ratio=0.5, visualize=True):
    os.makedirs(log_dir, exist_ok=True)  # 创建日志目录

    # === 核心算法(不变) ===
    img = cv2.imread(image_path)  # 读取输入图像
    if img is None:
        # 若图像无法加载,抛出错误
        raise FileNotFoundError(f"无法加载图片: {image_path}")
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转为灰度
    gray_blur = cv2.GaussianBlur(gray, (3, 3), 0)  # 小尺度高斯模糊,去除细小噪声

    # 将图像像素重塑为 n×1 列向量,准备用于 K-means 聚类
    pixels = gray_blur.reshape(-1, 1).astype(np.float32)
    # K-means 终止条件:最大迭代次数 100 或 误差阈值 0.2
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
    # 3 类聚类:背景、重影、主影
    _, labels, centers = cv2.kmeans(pixels, 3, None, criteria, 10, cv2.KMEANS_PP_CENTERS)
    labels2d = labels.flatten().reshape(gray.shape)  # 将聚类标签重构为图像形状
    centers = centers.flatten()  # 三个簇中心灰度
    idx = np.argsort(centers)  # 按灰度升序排列簇索引
    ghost_idx, main_idx = idx[1], idx[2]  # 中灰度为重影,最高灰度为主影

    # 构建二值掩码:主影=255,其余=0;重影=255,其余=0
    main_mask = (labels2d == main_idx).astype(np.uint8) * 255
    ghost_mask = (labels2d == ghost_idx).astype(np.uint8) * 255

    # 删除主影区域:先膨胀主影,再与重影取反交
    main_dilate = cv2.dilate(
        main_mask,
        cv2.getStructuringElement(cv2.MORPH_RECT, (7, 3)),  # 长条矩形核,重点在横向膨胀
        iterations=2
    )
    ghost_only = cv2.bitwise_and(ghost_mask, cv2.bitwise_not(main_dilate))

    # 噪声过滤:先闭运算填洞,再开运算去小斑点
    kern = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
    ghost_only = cv2.morphologyEx(ghost_only, cv2.MORPH_CLOSE, kern, iterations=1)
    ghost_only = cv2.morphologyEx(ghost_only, cv2.MORPH_OPEN,  kern, iterations=1)

    # 矩形提取函数:检测轮廓并最小外界矩形
    def extract(mask, thresh):
        rects, centers = [], []
        cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        for c in cnts:
            if cv2.contourArea(c) < thresh:
                continue  # 小面积过滤
            r = cv2.minAreaRect(c)  # 最小外界矩形
            box = cv2.boxPoints(r).astype(int)  # 获取4个顶点
            if len(box)==4:
                rects.append(box)
                centers.append(tuple(r[0]))  # 矩形中心坐标
        return rects, centers

    # 统计主影轮廓面积并取第 N 大作为基准
    cnts_main, _ = cv2.findContours(main_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    areas = sorted([cv2.contourArea(c) for c in cnts_main], reverse=True)
    N = rows*cols
    base_area = areas[N-1] if len(areas)>=N else (areas[-1] if areas else 0)

    # 按基准乘比率过滤:主影与重影
    main_boxes, main_centers = extract(main_mask,  base_area*main_ratio)
    raw_gb, raw_gc           = extract(ghost_only, base_area*ghost_ratio)

    # 恢复竖直重影:若重影对应主影为竖直,则拼回完整矩形
    recovered, vidx = [], []
    for i,(gx,gy) in enumerate(raw_gc):
        d = [np.hypot(gx-mx, gy-my) for mx,my in main_centers]  # 匹配最近主影
        mi = int(np.argmin(d))
        xs, ys = zip(*main_boxes[mi])
        if max(ys)-min(ys) > max(xs)-min(xs):  # 判定为竖直
            dx, dy = gx-np.mean(xs), gy-np.mean(ys)
            rec = (main_boxes[mi] + np.array([dx,dy])).astype(int)
            recovered.append(rec)
            vidx.append(i)
    # 最终重影:剔除原始竖直 + 加入恢复后
    ghost_boxes = [b for i,b in enumerate(raw_gb) if i not in vidx] + recovered
    ghost_centers = [c for i,c in enumerate(raw_gc) if i not in vidx] + \
                     [(float(np.mean(b[:,0])), float(np.mean(b[:,1]))) for b in recovered]

    # 排序:先对主影按 (y, x) 排序,再让重影一一对应
    def sort_by_coord(centers, boxes):
        items = list(zip(centers, boxes))
        items.sort(key=lambda x: (x[0][1], x[0][0]))  # Y优先,再X
        sc, sb = zip(*items) if items else ([], [])
        return list(sc), list(sb)

    main_centers, main_boxes = sort_by_coord(main_centers, main_boxes)
    sorted_gc, sorted_gb = [], []
    temp_c, temp_b = ghost_centers.copy(), ghost_boxes.copy()
    for mc in main_centers:
        d = [np.hypot(mc[0]-gc[0], mc[1]-gc[1]) for gc in temp_c]
        idx_min = int(np.argmin(d)) if d else 0
        sorted_gc.append(temp_c.pop(idx_min))
        sorted_gb.append(temp_b.pop(idx_min))
    ghost_centers, ghost_boxes = sorted_gc, sorted_gb

    # 输出日志及可视化标注
    offsets=[]
    with open(os.path.join(log_dir,'results.txt'),'w') as f:
        f.write(f"Main Count: {len(main_boxes)}\n")
        for i,(x,y) in enumerate(main_centers): f.write(f"Main[{i}]: ({x:.1f},{y:.1f})\n")
        f.write(f"Ghost Count: {len(ghost_boxes)}\n")
        for i,(x,y) in enumerate(ghost_centers): f.write(f"Ghost[{i}]: ({x:.1f},{y:.1f})\n")
        f.write("Offsets:\n")
        for i,(gx,gy) in enumerate(ghost_centers):
            d = [np.hypot(gx-mx, gy-my) for mx,my in main_centers]
            mi = int(np.argmin(d)); dx,dy = gx-main_centers[mi][0], gy-main_centers[mi][1]
            offsets.append((mi,i,dx,dy))
            f.write(f"(Main[{mi}],Ghost[{i}]) dx={dx:.1f},dy={dy:.1f}\n")

    if visualize:
        res=img.copy()
        for i,box in enumerate(main_boxes):
            cv2.drawContours(res,[box],0,(0,0,255),2)
            cx,cy=map(int,main_centers[i]); cv2.putText(res,f"M{i}",(cx+5,cy),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,255),1)
        for i,box in enumerate(ghost_boxes):
            cv2.drawContours(res,[box],0,(255,0,0),2)
            cx,cy=map(int,ghost_centers[i]); cv2.putText(res,f"G{i}",(cx+5,cy),cv2.FONT_HERSHEY_SIMPLEX,0.5,(255,0,0),1)
        cv2.imwrite(os.path.join(log_dir,'step7_final_result.png'),res)

    return main_boxes, ghost_boxes, offsets, (res if visualize else None)


import cv2
import os
import argparse

from circle_detector import detect_circles
from visualizer import draw_circles, save_results
from fas import calculate_all_params

from rectangle_detector import detect_rectangles
from lines_detector_v5pro import detect_lines_v5r_debug
from detect_rectangles_pro import detect_rectangles_v2


# ===== 配置区域:修改图像路径 =====
IMAGE_PATH = "sample_data/cl_2.jpg"  # 待检测的图像路径
PARAMS_FILE = "sample_data/input_params.txt"
RESULT_FILE = "log/hud_projection_results.txt"



def main():
    """主函数:控制程序执行流程"""
    print("=" * 60)
    print("黑底白圆检测器 | 霍夫变换算法(模块化版本)")
    print("=" * 60)

    # 检查图像文件是否存在
    if not os.path.exists(IMAGE_PATH):
        print(f"错误:文件不存在 - {IMAGE_PATH}")
        return
    if not os.path.exists(PARAMS_FILE):
        print(f"错误:参数文件不存在 - {PARAMS_FILE}")
        return

    #####执行矩形检测

    # 不可视化,只输出结果文件
   # main_boxes, ghost_boxes, offsets, _ = detect_rectangles('sample_data/ghost.jpeg', 'log')

    # main_boxes, ghost_boxes, offsets, _= detect_rectangles(
    #     'sample_data/ghost.jpeg', 'log',rows=5,cols=9,visualize=True)

    ####lines检测
    detect_rectangles_v2("sample_data/ghost.jpeg")

    # 执行圆形检测
    circles, proc_time, original_img = detect_circles(IMAGE_PATH)

    if not circles:
        print("检测失败,未找到圆形目标")
        return

    print(f"成功检测到 {len(circles)} 个圆,开始计算HUD投影参数...")

    # 计算HUD投影参数
    hud_params = calculate_all_params(circles, PARAMS_FILE)
    if hud_params is None:
        print("HUD参数计算失败")
        return

    # 绘制检测结果
    result_img = draw_circles(original_img, circles)

    # 保存结果图像和坐标信息
    output_name = "result_hough_" + os.path.basename(IMAGE_PATH)
    save_results(result_img, circles, output_name, proc_time)

    # 保存HUD计算结果
    save_hud_results(hud_params, RESULT_FILE)

    # 输出检测报告
    print("\n检测完成报告:")
    print(f"处理时间: {proc_time:.2f} 秒")
    print(f"检测到圆形数量: {len(circles)}")
    print(f"结果图像保存路径: log/{output_name}")
    print(f"坐标信息保存路径: log/hough_coordinates.txt")
    print("\nHUD投影参数计算结果:")
    print(f"下视角(LDA): {hud_params['lda']:.4f} 度")
    print(f"左视角(LOA): {hud_params['loa']:.4f} 度")
    print(f"横向视场角(FOVH): {hud_params['fovh']:.4f} 度")
    print(f"纵向视场角(FOVV): {hud_params['fovv']:.4f} 度")
    print(f"投影距离(VID): {hud_params['vid']:.4f} 毫米")
    print(f"虚像长度: {hud_params['length']:.4f} 毫米")
    print(f"虚像宽度: {hud_params['width']:.4f} 毫米")
    print(f"特定点畸变(Distortion_geely): {hud_params['distortion_geely']:.6f}")
    # print(f"所有点畸变(Distortion_CS): {hud_params['distortion_cs']:.6f}")
    print(f"结果已保存至: {RESULT_FILE}")

def save_hud_results(params, file_path):
    """保存HUD计算结果到文件"""
    try:
        with open(file_path, 'w') as f:
            f.write("===== HUD投影参数计算结果 =====\n\n")
            f.write(f"下视角(LDA): {params['lda']:.6f} 度\n")
            f.write(f"左视角(LOA): {params['loa']:.6f} 度\n")
            f.write(f"横向视场角(FOVH): {params['fovh']:.6f} 度\n")
            f.write(f"纵向视场角(FOVV): {params['fovv']:.6f} 度\n")
            f.write(f"投影距离(VID): {params['vid']:.6f} 毫米\n")
            f.write(f"虚像长度: {params['length']:.6f} 毫米\n")
            f.write(f"虚像宽度: {params['width']:.6f} 毫米\n")
            f.write("\n================================\n")
            f.write(f"特定点畸变(Distortion_geely): {params['distortion_geely']:.6f}\n")
            # f.write(f"所有点畸变(Distortion_CS): {params['distortion_cs']:.6f}\n")
        print(f"HUD参数已保存至: {file_path}")
    except Exception as e:
        print(f"保存结果出错: {str(e)}")


if __name__ == "__main__":
    main()
"""
lines_detector.py
新增校准逻辑:确保本体和重影一一配对,自动剔除额外检测区域。
"""
import cv2
import numpy as np
import os
import argparse
import math


def detect_rectangles(image_path, log_dir='log', rows=5, cols=9, visualize=True):
    os.makedirs(log_dir, exist_ok=True)

    # 1. 读取 & 预处理
    img = cv2.imread(image_path)
    if img is None:
        raise FileNotFoundError(f"无法加载图片: {image_path}")
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    gray_blur = cv2.GaussianBlur(gray, (5, 5), 0)
    cv2.imwrite(os.path.join(log_dir, 'step1_gray_blur.png'), gray_blur)

    # 2. K-means 分割为 3 个灰度层级
    pixels = gray_blur.reshape(-1, 1).astype(np.float32)
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
    _, labels, centers = cv2.kmeans(pixels, 3, None, criteria, 10, cv2.KMEANS_PP_CENTERS)
    labels2d = labels.flatten().reshape(gray_blur.shape)
    centers = centers.flatten()
    sorted_idx = np.argsort(centers)[::-1]  # 高到低
    overlap_idx, main_idx, ghost_idx = sorted_idx
    overlap_mask = (labels2d == overlap_idx).astype(np.uint8) * 255
    main_mask = (labels2d == main_idx).astype(np.uint8) * 255
    ghost_mask = (labels2d == ghost_idx).astype(np.uint8) * 255
    cv2.imwrite(os.path.join(log_dir, 'step2_overlap_mask.png'), overlap_mask)
    cv2.imwrite(os.path.join(log_dir, 'step3_main_mask.png'), main_mask)
    cv2.imwrite(os.path.join(log_dir, 'step4_ghost_mask.png'), ghost_mask)

    # 3. 合并重叠区
    main_full = cv2.bitwise_or(main_mask, overlap_mask)
    ghost_full = cv2.bitwise_or(ghost_mask, overlap_mask)
    cv2.imwrite(os.path.join(log_dir, 'step5_main_full.png'), main_full)
    cv2.imwrite(os.path.join(log_dir, 'step6_ghost_full.png'), ghost_full)

    # 4. 噪声过滤
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))

    def clean(mask):
        m = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=2)
        m = cv2.morphologyEx(m, cv2.MORPH_OPEN, kernel, iterations=1)
        return m

    main_clean = clean(main_full)
    ghost_clean = clean(ghost_full)
    cv2.imwrite(os.path.join(log_dir, 'step7_main_clean.png'), main_clean)
    cv2.imwrite(os.path.join(log_dir, 'step8_ghost_clean.png'), ghost_clean)

    # 5. 提取矩形及中心
    def extract(mask):
        boxes, centers = [], []
        cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        for c in cnts:
            if cv2.contourArea(c) < 10:
                continue
            r = cv2.minAreaRect(c)
            box = cv2.boxPoints(r).astype(int)
            boxes.append(box)
            centers.append(r[0])
        return boxes, centers

    main_boxes, main_centers = extract(main_clean)
    ghost_boxes, ghost_centers = extract(ghost_clean)

    # 6. 校准配对:贪心算法,保证一一对应
    # 构建距离列表
    candidates = []
    for i, m in enumerate(main_centers):
        for j, g in enumerate(ghost_centers):
            d = math.hypot(g[0] - m[0], g[1] - m[1])
            candidates.append((d, i, j))
    candidates.sort(key=lambda x: x[0])
    used_m, used_g = set(), set()
    pairs = []  # 存最终索引对 (i, j)
    for d, i, j in candidates:
        if i in used_m or j in used_g:
            continue
        used_m.add(i);
        used_g.add(j)
        pairs.append((i, j))
        # 当任何一方用完即跳出
        if len(used_m) == len(main_centers) or len(used_g) == len(ghost_centers):
            break

    # 裁剪多余检测,保证成对
    main_boxes_calib, main_centers_calib = [], []
    ghost_boxes_calib, ghost_centers_calib = [], []
    for i, j in pairs:
        main_boxes_calib.append(main_boxes[i])
        main_centers_calib.append(main_centers[i])
        ghost_boxes_calib.append(ghost_boxes[j])
        ghost_centers_calib.append(ghost_centers[j])
    main_boxes, main_centers = main_boxes_calib, main_centers_calib
    ghost_boxes, ghost_centers = ghost_boxes_calib, ghost_centers_calib

    # 7. 计算偏移并输出日志
    with open(os.path.join(log_dir, 'results.txt'), 'w') as f:
        f.write(f"Main Count: {len(main_boxes)}\n")
        f.write(f"Ghost Count: {len(ghost_boxes)}\n")
        for idx, (i, j) in enumerate(pairs):
            dx = ghost_centers[j][0] - main_centers[i][0]
            dy = ghost_centers[j][1] - main_centers[i][1]
            f.write(f"Pair Main[{i}] - Ghost[{j}]: dx={dx:.2f}, dy={dy:.2f}\n")
    print(f"结果已写入 {log_dir}/results.txt,已保证一一配对。")

    # 8. 可视化
    vis = img.copy()
    for box, cen in zip(main_boxes, main_centers):
        cv2.drawContours(vis, [box], 0, (0, 0, 255), 2)
        cv2.circle(vis, (int(cen[0]), int(cen[1])), 3, (0, 0, 255), -1)
    for box, cen in zip(ghost_boxes, ghost_centers):
        cv2.drawContours(vis, [box], 0, (255, 0, 0), 2)
        cv2.circle(vis, (int(cen[0]), int(cen[1])), 3, (255, 0, 0), -1)
    cv2.imwrite(os.path.join(log_dir, 'step9_final_result.png'), vis)

    return main_boxes, ghost_boxes, pairs, vis



# #已经把竖着的重影恢复出来了,但是还不对,然后重影也不够精确
# import cv2
# import numpy as np
# import os
#
#
# def detect_rectangles(image_path, log_dir='log', rows=5, cols=9,
#                       main_ratio=0.5, ghost_ratio=0.08, visualize=True):
#     os.makedirs(log_dir, exist_ok=True)
#
#     # 1. 读取 & 预处理
#     img = cv2.imread(image_path)
#     if img is None:
#         raise FileNotFoundError(f"无法加载图片: {image_path}")
#     gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#     gray_blur = cv2.GaussianBlur(gray, (5, 5), 0)
#     cv2.imwrite(os.path.join(log_dir, 'step1_gray_blur.png'), gray_blur)
#
#     # 2. K-means 分割主影 / 重影 / 背景
#     pixels = gray_blur.reshape(-1, 1).astype(np.float32)
#     criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
#     _, labels, centers = cv2.kmeans(pixels, 3, None, criteria, 10, cv2.KMEANS_PP_CENTERS)
#     labels2d = labels.flatten().reshape(gray.shape)
#     centers = centers.flatten()
#     idx = np.argsort(centers)
#     ghost_idx, main_idx = idx[1], idx[2]
#
#     main_mask = (labels2d == main_idx).astype(np.uint8) * 255
#     ghost_mask = (labels2d == ghost_idx).astype(np.uint8) * 255
#     cv2.imwrite(os.path.join(log_dir, 'step2_main_mask.png'), main_mask)
#     cv2.imwrite(os.path.join(log_dir, 'step3_ghost_mask.png'), ghost_mask)
#
#     # 3. 精细扣除:在最小膨胀范围内删除主影区域
#     main_dilate = cv2.dilate(main_mask, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)), iterations=1)
#     ghost_only = cv2.bitwise_and(ghost_mask, cv2.bitwise_not(main_dilate))
#     cv2.imwrite(os.path.join(log_dir, 'step4_ghost_only_raw.png'), ghost_only)
#
#     # 4. 噪声过滤:闭运算 + 开运算 去除小斑点
#     kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
#     ghost_only = cv2.morphologyEx(ghost_only, cv2.MORPH_CLOSE, kernel, iterations=1)
#     ghost_only = cv2.morphologyEx(ghost_only, cv2.MORPH_OPEN, kernel, iterations=1)
#     cv2.imwrite(os.path.join(log_dir, 'step5_ghost_filtered.png'), ghost_only)
#
#     # 5. 根据面积阈值提取主影 & 重影矩形
#     def extract(mask, thresh):
#         rects, centers = [], []
#         cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#         for c in cnts:
#             if cv2.contourArea(c) < thresh:
#                 continue
#             r = cv2.minAreaRect(c)
#             box = cv2.boxPoints(r).astype(int)
#             if box.shape[0] == 4:
#                 rects.append(box)
#                 centers.append(r[0])
#         return rects, centers
#
#     cnts_main, _ = cv2.findContours(main_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#     areas = sorted([cv2.contourArea(c) for c in cnts_main], reverse=True)
#     N = rows * cols
#     base_area = areas[N - 1] if len(areas) >= N else (areas[-1] if areas else 0)
#     main_thresh = base_area * main_ratio
#     ghost_thresh = base_area * ghost_ratio
#
#     main_boxes, main_centers = extract(main_mask, main_thresh)
#     ghost_boxes, ghost_centers = extract(ghost_only, ghost_thresh)
#
#     # 6. 恢复竖直重影:将被本体遮挡的那半截“拼回来”
#     recovered = []
#     rec_img = img.copy()
#     for mi, (gx, gy) in enumerate(ghost_centers):
#         # 找对应主影
#         dists = [np.hypot(gx - mx, gy - my) for mx, my in main_centers]
#         mi_best = int(np.argmin(dists))
#         box = main_boxes[mi_best]
#         xs, ys = zip(*box)
#         w, h = max(xs) - min(xs), max(ys) - min(ys)
#         # 仅对竖直主影做恢复
#         if h > w:
#             # 计算重影中心相对主影中心偏移
#             dx = gx - np.mean(xs)
#             dy = gy - np.mean(ys)
#             # 拼回完整矩形
#             rec_box = (box + np.array([dx, dy])).astype(int)
#             recovered.append(rec_box)
#             cv2.drawContours(rec_img, [rec_box], 0, (0, 255, 0), 2)
#     cv2.imwrite(os.path.join(log_dir, 'step6_recovered_vertical.png'), rec_img)
#
#     ghost_boxes.extend(recovered)
#
#     # 7. 计算偏移 & 输出日志
#     offsets = []
#     for gi, (gx, gy) in enumerate([np.mean(b, axis=0) for b in ghost_boxes]):
#         dists = [np.hypot(gx - mx, gy - my) for mx, my in main_centers]
#         mi = int(np.argmin(dists))
#         dx, dy = gx - main_centers[mi][0], gy - main_centers[mi][1]
#         offsets.append((mi, gi, dx, dy))
#
#     with open(os.path.join(log_dir, 'results.txt'), 'w') as f:
#         f.write(f"Main Count: {len(main_boxes)}\n")
#         f.write(f"Ghost Count: {len(ghost_boxes)}\n")
#         for off in offsets:
#             f.write(f"{off}\n")
#
#     # 8. 最终可视化
#     if visualize:
#         res = img.copy()
#         for b in main_boxes:   cv2.drawContours(res, [b], 0, (0, 0, 255), 2)
#         for b in ghost_boxes:  cv2.drawContours(res, [b], 0, (255, 0, 0), 2)
#         cv2.imwrite(os.path.join(log_dir, 'step7_final_result.png'), res)
#
#     return main_boxes, ghost_boxes, offsets, (res if visualize else None)



######################这个已经比较完美了,但是还要改进
# def detect_rectangles(image_path, log_dir='log', rows=5, cols=9,
#                       main_ratio=0.5, ghost_ratio=0.08, visualize=True):
#     import cv2, numpy as np, os
#
#     os.makedirs(log_dir, exist_ok=True)
#
#     img = cv2.imread(image_path)
#     gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#     gray_blur = cv2.GaussianBlur(gray, (5, 5), 0)
#
#     # Step 1: K-means分割
#     pixels = gray_blur.reshape(-1, 1).astype(np.float32)
#     criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
#     _, labels, centers = cv2.kmeans(pixels, 3, None, criteria, 10, cv2.KMEANS_PP_CENTERS)
#     labels2d = labels.flatten().reshape(gray.shape)
#     centers = centers.flatten()
#     idx = np.argsort(centers)
#     ghost_idx, main_idx = idx[1], idx[2]
#
#     main_mask = (labels2d == main_idx).astype(np.uint8) * 255
#     ghost_mask = (labels2d == ghost_idx).astype(np.uint8) * 255
#
#     # 保存第2、3步图
#     cv2.imwrite(os.path.join(log_dir, 'main_mask.png'), main_mask)
#     cv2.imwrite(os.path.join(log_dir, 'ghost_mask.png'), ghost_mask)
#
#     # Step 2:(改进)直接在二值mask基础上精细扣除
#     main_dilate = cv2.dilate(main_mask, np.ones((3, 3)), iterations=1)  # 小范围膨胀
#     ghost_only = cv2.bitwise_and(ghost_mask, cv2.bitwise_not(main_dilate))
#     ghost_only = cv2.morphologyEx(ghost_only, cv2.MORPH_OPEN, np.ones((2, 2)), iterations=1)
#
#     cv2.imwrite(os.path.join(log_dir, 'ghost_only.png'), ghost_only)
#
#     def extract(mask, thresh):
#         rects, centers = [], []
#         cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#         for c in cnts:
#             if cv2.contourArea(c) < thresh: continue
#             r = cv2.minAreaRect(c)
#             box = cv2.boxPoints(r).astype(int)
#             if box.shape[0] == 4:
#                 rects.append(box)
#                 centers.append(r[0])
#         return rects, centers
#
#     # Step 3: 提取框
#     cnts_main, _ = cv2.findContours(main_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#     areas = sorted([cv2.contourArea(c) for c in cnts_main], reverse=True)
#     N = rows * cols
#     base_area = areas[N - 1] if len(areas) >= N else (areas[-1] if areas else 0)
#     main_thresh = base_area * main_ratio
#     ghost_thresh = base_area * ghost_ratio
#
#     main_boxes, main_centers = extract(main_mask, main_thresh)
#     ghost_boxes, ghost_centers = extract(ghost_only, ghost_thresh)
#
#     offsets = []
#     for gi, g in enumerate(ghost_boxes):
#         gc = np.mean(g, axis=0)
#         dists = [np.linalg.norm(gc - np.mean(m, axis=0)) for m in main_boxes]
#         mi = int(np.argmin(dists))
#         dx, dy = gc - np.mean(main_boxes[mi], axis=0)
#         offsets.append((mi, gi, dx, dy))
#
#     with open(os.path.join(log_dir, 'results.txt'), 'w') as f:
#         f.write(f"Main Count:{len(main_boxes)}\n")
#         f.write(f"Ghost Count:{len(ghost_boxes)}\n")
#         for o in offsets: f.write(f"{o}\n")
#
#     if visualize:
#         result_img = img.copy()
#         for b in main_boxes: cv2.drawContours(result_img, [b], 0, (0, 0, 255), 2)
#         for b in ghost_boxes: cv2.drawContours(result_img, [b], 0, (255, 0, 0), 2)
#         cv2.imwrite(os.path.join(log_dir, 'final_result.png'), result_img)
#
#     return main_boxes, ghost_boxes, offsets, result_img


###########################这个第三步生成的图已经很好了,其实已经把重影都识别出来了
# def detect_rectangles(image_path, log_dir='log', rows=5, cols=9,
#                       main_ratio=0.5, ghost_ratio=0.08, visualize=True):
#     import cv2, numpy as np, os
#
#     os.makedirs(log_dir, exist_ok=True)
#
#     img = cv2.imread(image_path)
#     gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#     gray_blur = cv2.GaussianBlur(gray, (5, 5), 0)
#     cv2.imwrite(os.path.join(log_dir, 'step1_gray_blur.png'), gray_blur)
#
#     # Step 1: K-means分割
#     pixels = gray_blur.reshape(-1, 1).astype(np.float32)
#     criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
#     _, labels, centers = cv2.kmeans(pixels, 3, None, criteria, 10, cv2.KMEANS_PP_CENTERS)
#     labels2d = labels.flatten().reshape(gray.shape)
#     centers = centers.flatten()
#     idx = np.argsort(centers)
#     ghost_idx, main_idx = idx[1], idx[2]
#
#     main_mask = (labels2d == main_idx).astype(np.uint8) * 255
#     ghost_mask = (labels2d == ghost_idx).astype(np.uint8) * 255
#     cv2.imwrite(os.path.join(log_dir, 'step2_main_mask.png'), main_mask)
#     cv2.imwrite(os.path.join(log_dir, 'step3_ghost_mask.png'), ghost_mask)
#
#     # Step 2: 形态学闭运算
#     h_k = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 3))
#     v_k = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 15))
#     main_clean = cv2.bitwise_or(
#         cv2.morphologyEx(main_mask, cv2.MORPH_CLOSE, h_k, iterations=2),
#         cv2.morphologyEx(main_mask, cv2.MORPH_CLOSE, v_k, iterations=2)
#     )
#     ghost_closed = cv2.bitwise_or(
#         cv2.morphologyEx(ghost_mask, cv2.MORPH_CLOSE, h_k, iterations=2),
#         cv2.morphologyEx(ghost_mask, cv2.MORPH_CLOSE, v_k, iterations=2)
#     )
#     cv2.imwrite(os.path.join(log_dir, 'step4_main_clean.png'), main_clean)
#     cv2.imwrite(os.path.join(log_dir, 'step5_ghost_closed.png'), ghost_closed)
#
#     # Step 3: 扣除主影区域
#     main_dilate = cv2.dilate(main_clean, np.ones((13, 13)), iterations=1)
#     ghost_only = cv2.bitwise_and(ghost_closed, cv2.bitwise_not(main_dilate))
#     ghost_only = cv2.morphologyEx(ghost_only, cv2.MORPH_OPEN, np.ones((3, 3)), iterations=1)
#     cv2.imwrite(os.path.join(log_dir, 'step6_ghost_only.png'), ghost_only)
#
#     def extract(mask, thresh):
#         rects, centers = [], []
#         cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#         for c in cnts:
#             if cv2.contourArea(c) < thresh: continue
#             r = cv2.minAreaRect(c)
#             box = cv2.boxPoints(r).astype(int)
#             if box.shape[0] == 4:
#                 rects.append(box)
#                 centers.append(r[0])
#         return rects, centers
#
#     cnts_main, _ = cv2.findContours(main_clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#     areas = sorted([cv2.contourArea(c) for c in cnts_main], reverse=True)
#     N = rows * cols
#     base_area = areas[N - 1] if len(areas) >= N else (areas[-1] if areas else 0)
#     main_thresh = base_area * main_ratio
#     ghost_thresh = base_area * ghost_ratio
#
#     main_boxes, main_centers = extract(main_clean, main_thresh)
#     ghost_boxes, ghost_centers = extract(ghost_only, ghost_thresh)
#
#     # Step 4: 竖直重影优化(扩展区域搜索)
#     refined_ghost_boxes = []
#     ghost_refine_img = img.copy()
#     for box in main_boxes:
#         x_min, x_max = min(box[:, 0]) - 15, max(box[:, 0]) + 15
#         y_min, y_max = min(box[:, 1]) - 30, max(box[:, 1]) + 30
#         x_min, x_max = max(0, x_min), min(gray.shape[1], x_max)
#         y_min, y_max = max(0, y_min), min(gray.shape[0], y_max)
#
#         roi = gray[y_min:y_max, x_min:x_max]
#         main_roi_mask = np.zeros_like(roi)
#         box_shifted = box - [x_min, y_min]
#         cv2.drawContours(main_roi_mask, [box_shifted], -1, 255, -1)
#         main_val = np.mean(roi[main_roi_mask == 255])
#         ghost_thr = main_val - 20 if main_val > 20 else 10
#         ghost_roi = ((roi > ghost_thr) & (roi < main_val - 5)).astype(np.uint8) * 255
#         ghost_roi = cv2.morphologyEx(ghost_roi, cv2.MORPH_OPEN, np.ones((3, 3)), iterations=1)
#         ghost_roi = cv2.subtract(ghost_roi, main_roi_mask)
#
#         cnts, _ = cv2.findContours(ghost_roi, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#         for c in cnts:
#             if cv2.contourArea(c) < 5: continue
#             r = cv2.minAreaRect(c)
#             gb = cv2.boxPoints(r).astype(int) + [x_min, y_min]
#             refined_ghost_boxes.append(gb)
#             cv2.drawContours(ghost_refine_img, [gb], -1, (255, 0, 0), 2)
#             cv2.rectangle(ghost_refine_img, (x_min, y_min), (x_max, y_max), (0, 255, 0), 1)
#
#     cv2.imwrite(os.path.join(log_dir, 'step7_ghost_refine.png'), ghost_refine_img)
#
#     ghost_boxes.extend(refined_ghost_boxes)
#
#     offsets = []
#     for gi, g in enumerate(ghost_boxes):
#         gc = np.mean(g, axis=0)
#         dists = [np.linalg.norm(gc - np.mean(m, axis=0)) for m in main_boxes]
#         mi = int(np.argmin(dists))
#         dx, dy = gc - np.mean(main_boxes[mi], axis=0)
#         offsets.append((mi, gi, dx, dy))
#
#     with open(os.path.join(log_dir, 'results.txt'), 'w') as f:
#         f.write(f"Main Count:{len(main_boxes)}\n")
#         f.write(f"Ghost Count:{len(ghost_boxes)}\n")
#         for o in offsets: f.write(f"{o}\n")
#
#     if visualize:
#         result_img = img.copy()
#         for b in main_boxes: cv2.drawContours(result_img, [b], 0, (0, 0, 255), 2)
#         for b in ghost_boxes: cv2.drawContours(result_img, [b], 0, (255, 0, 0), 2)
#         cv2.imwrite(os.path.join(log_dir, 'step8_final_result.png'), result_img)
#
#     return main_boxes, ghost_boxes, offsets, result_img

# ###################终于分割开了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!但是精度不够
# def detect_rectangles(image_path, log_dir='log', rows=5, cols=9,
#                       main_ratio=0.5, ghost_ratio=0.2, visualize=False):
#     import cv2
#     import numpy as np
#     import os
#
#     img = cv2.imread(image_path)
#     if img is None:
#         raise FileNotFoundError(f"无法加载图片: {image_path}")
#     gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#     gray = cv2.GaussianBlur(gray, (5, 5), 0)
#
#     # 主影mask
#     pixels = gray.reshape(-1, 1).astype(np.float32)
#     criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
#     _, labels, centers = cv2.kmeans(pixels, 3, None, criteria, 10, cv2.KMEANS_PP_CENTERS)
#     centers = centers.flatten()
#     idx = np.argsort(centers)
#     ghost_idx, main_idx = idx[1], idx[2]
#     labels2d = labels.flatten().reshape(gray.shape)
#     main_mask = (labels2d == main_idx).astype(np.uint8) * 255
#     ghost_mask = (labels2d == ghost_idx).astype(np.uint8) * 255
#
#     # 形态学闭运算
#     h_k = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 3))
#     v_k = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 15))
#     main_clean = cv2.bitwise_or(
#         cv2.morphologyEx(main_mask, cv2.MORPH_CLOSE, h_k, iterations=2),
#         cv2.morphologyEx(main_mask, cv2.MORPH_CLOSE, v_k, iterations=2)
#     )
#     ghost_closed = cv2.bitwise_or(
#         cv2.morphologyEx(ghost_mask, cv2.MORPH_CLOSE, h_k, iterations=2),
#         cv2.morphologyEx(ghost_mask, cv2.MORPH_CLOSE, v_k, iterations=2)
#     )
#
#     # ------------ 彻底分离重影掩膜核心 ------------
#     # 主影膨胀多一点(强行扩展主影掩膜覆盖)
#     main_dilate = cv2.dilate(main_clean, cv2.getStructuringElement(cv2.MORPH_RECT, (13, 13)), iterations=1)
#     # 从重影掩膜中扣除所有主影膨胀区,只剩“绝对不会有本体的重影”
#     ghost_only = cv2.bitwise_and(ghost_closed, cv2.bitwise_not(main_dilate))
#     # 去杂点
#     ghost_only = cv2.morphologyEx(ghost_only, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)),
#                                   iterations=1)
#
#     # 后续照旧
#     cnts_main, _ = cv2.findContours(main_clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#     areas = sorted([cv2.contourArea(c) for c in cnts_main], reverse=True)
#     N = rows * cols
#     base_area = areas[N - 1] if len(areas) >= N else (areas[-1] if areas else 0)
#     main_thresh = base_area * main_ratio
#     ghost_thresh = base_area * ghost_ratio
#
#     def extract(mask, thresh):
#         rects, centers = [], []
#         cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#         for c in cnts:
#             if cv2.contourArea(c) < thresh:
#                 continue
#             r = cv2.minAreaRect(c)
#             box = cv2.boxPoints(r).astype(int)
#             if box.shape[0] == 4:
#                 rects.append(box)
#                 centers.append(r[0])
#         return rects, centers
#
#     main_boxes, main_centers = extract(main_clean, main_thresh)
#     ghost_boxes, ghost_centers = extract(ghost_only, ghost_thresh)
#
#     offsets = []
#     for gi, g in enumerate(ghost_centers):
#         if not main_centers:
#             break
#         dists = [np.hypot(g[0] - m[0], g[1] - m[1]) for m in main_centers]
#         mi = int(np.argmin(dists))
#         dx, dy = g[0] - main_centers[mi][0], g[1] - main_centers[mi][1]
#         offsets.append((mi, gi, dx, dy))
#
#     os.makedirs(log_dir, exist_ok=True)
#     with open(os.path.join(log_dir, 'results.txt'), 'w') as f:
#         f.write(f"Main Count: {len(main_boxes)}\n")
#         f.write(f"Ghost Count: {len(ghost_boxes)}\n")
#         f.write("Offsets(main_i,ghost_i,dx,dy):\n")
#         for it in offsets:
#             f.write(f"{it}\n")
#
#     result_img = None
#     if visualize:
#         result_img = img.copy()
#         for b in main_boxes:
#             cv2.drawContours(result_img, [b], 0, (0, 0, 255), 2)
#         for b in ghost_boxes:
#             cv2.drawContours(result_img, [b], 0, (255, 0, 0), 2)
#         cv2.imwrite(os.path.join(log_dir, 'result.png'), result_img)
#
#     return main_boxes, ghost_boxes, offsets, result_img





######################这个全部识别到了,但是框的不太对
# import cv2
# import numpy as np
# import os
#
#
# def detect_rectangles(image_path, log_dir='log', rows=5, cols=9,
#                       main_ratio=0.5, ghost_ratio=0.8, visualize=False):
#     """
#     检测 HUD 投影图中的主影与重影矩形,并计算位移偏移。
#     参数:
#         image_path: 图像路径
#         log_dir: 输出目录
#         rows, cols: HUD 主影排布行列数,用于动态估算基准面积
#         main_ratio: 主影过滤阈值占基准面积比例
#         ghost_ratio: 重影过滤阈值占基准面积比例(相对于基准面积)
#         visualize: 是否在返回时附带标注图像
#     返回:
#         main_boxes: 主影矩形列表 (Nx4x2 int numpy array)
#         ghost_boxes: 重影矩形列表
#         offsets: 重影相对于主影的偏移列表 [(idx_main, idx_ghost, dx, dy), ...]
#         result_img (可选): 标注后的 BGR 图像,当 visualize=True
#     """
#     # 1. 读取与预处理
#     img = cv2.imread(image_path)
#     if img is None:
#         raise FileNotFoundError(f"无法加载图片: {image_path}")
#     gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#     gray = cv2.GaussianBlur(gray, (5, 5), 0)
#
#     # 2. K-means 3 类聚类: 背景、重影、主影
#     pixels = gray.reshape(-1, 1).astype(np.float32)
#     criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
#     _, labels, centers = cv2.kmeans(pixels, 3, None, criteria, 10, cv2.KMEANS_PP_CENTERS)
#     centers = centers.flatten()
#     idx = np.argsort(centers)
#     ghost_idx, main_idx = idx[1], idx[2]
#
#     labels2d = labels.flatten().reshape(gray.shape)
#     main_mask = (labels2d == main_idx).astype(np.uint8) * 255
#     ghost_mask = (labels2d == ghost_idx).astype(np.uint8) * 255
#
#     # 3. 定向形态学闭运算
#     h_k = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 3))
#     v_k = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 15))
#     main_clean = cv2.bitwise_or(
#         cv2.morphologyEx(main_mask, cv2.MORPH_CLOSE, h_k, iterations=2),
#         cv2.morphologyEx(main_mask, cv2.MORPH_CLOSE, v_k, iterations=2)
#     )
#     ghost_clean = cv2.bitwise_or(
#         cv2.morphologyEx(ghost_mask, cv2.MORPH_CLOSE, h_k, iterations=2),
#         cv2.morphologyEx(ghost_mask, cv2.MORPH_CLOSE, v_k, iterations=2)
#     )
#
#     # 4. 留纯重影
#     ghost_clean = cv2.subtract(ghost_clean, main_clean)
#
#     # 5. 动态面积阈值
#     cnts_main, _ = cv2.findContours(main_clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#     areas = sorted([cv2.contourArea(c) for c in cnts_main], reverse=True)
#     N = rows * cols
#     base_area = areas[N - 1] if len(areas) >= N else (areas[-1] if areas else 0)
#     main_thresh = base_area * main_ratio
#     ghost_thresh = base_area * ghost_ratio
#
#     # 6. 提取矩形与质心
#     def extract(mask, thresh):
#         rects, centers = [], []
#         cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#         for c in cnts:
#             if cv2.contourArea(c) < thresh:
#                 continue
#             r = cv2.minAreaRect(c)
#             box = cv2.boxPoints(r).astype(int)
#             if box.shape[0] == 4:
#                 rects.append(box)
#                 centers.append(r[0])
#         return rects, centers
#
#     main_boxes, main_centers = extract(main_clean, main_thresh)
#     ghost_boxes, ghost_centers = extract(ghost_clean, ghost_thresh)
#
#     # 7. 计算偏移
#     offsets = []
#     for gi, g in enumerate(ghost_centers):
#         if not main_centers:
#             break
#         dists = [np.hypot(g[0] - m[0], g[1] - m[1]) for m in main_centers]
#         mi = int(np.argmin(dists))
#         dx = g[0] - main_centers[mi][0]
#         dy = g[1] - main_centers[mi][1]
#         offsets.append((mi, gi, dx, dy))
#
#     # 8. 保存结果
#     os.makedirs(log_dir, exist_ok=True)
#     with open(os.path.join(log_dir, 'results.txt'), 'w') as f:
#         f.write(f"Main Count: {len(main_boxes)}\n")
#         f.write(f"Ghost Count: {len(ghost_boxes)}\n")
#         f.write("Offsets(main_i,ghost_i,dx,dy):\n")
#         for it in offsets:
#             f.write(f"{it}\n")
#
#     result_img = None
#     if visualize:
#         result_img = img.copy()
#         for b in main_boxes:
#             cv2.drawContours(result_img, [b], 0, (0, 0, 255), 2)
#         for b in ghost_boxes:
#             cv2.drawContours(result_img, [b], 0, (255, 0, 0), 2)
#         cv2.imwrite(os.path.join(log_dir, 'result.png'), result_img)
#
#     return main_boxes, ghost_boxes, offsets, result_img




#######################这个识别到的有点少
# import cv2
# import numpy as np
# import os
#
#
# def detect_rectangles(image_path, log_dir='log'):
#     # Load image and convert to grayscale
#     img = cv2.imread(image_path)
#     if img is None:
#         print(f"Error: could not load image {image_path}")
#         return
#     gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#
#     # Flatten and prepare for k-means clustering (3 clusters: background, ghost, main)
#     pixel_vals = gray.reshape((-1, 1)).astype(np.float32)
#     criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
#     k = 3
#     _, labels, centers = cv2.kmeans(pixel_vals, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
#     centers = centers.flatten()
#
#     # Determine cluster labels: sort centers
#     sorted_centers_idx = np.argsort(centers)
#     background_label = sorted_centers_idx[0]
#     ghost_label = sorted_centers_idx[1]
#     main_label = sorted_centers_idx[2]
#
#     # Create binary masks for main and ghost segments
#     labels = labels.flatten().reshape(gray.shape)
#     main_mask = np.uint8(labels == main_label) * 255
#     ghost_mask = np.uint8(labels == ghost_label) * 255
#
#     # Morphological operations to clean noise
#     kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
#     main_mask = cv2.morphologyEx(main_mask, cv2.MORPH_OPEN, kernel, iterations=2)
#     ghost_mask = cv2.morphologyEx(ghost_mask, cv2.MORPH_OPEN, kernel, iterations=2)
#
#     # Find contours for each mask
#     contours_main, _ = cv2.findContours(main_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#     contours_ghost, _ = cv2.findContours(ghost_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#
#     # Filter contours by area to remove small noise
#     min_area = 50
#     main_rects = [(x, y, w, h) for cnt in contours_main
#                   for x, y, w, h in [cv2.boundingRect(cnt)]
#                   if cv2.contourArea(cnt) > min_area]
#     ghost_rects = [(x, y, w, h) for cnt in contours_ghost
#                    for x, y, w, h in [cv2.boundingRect(cnt)]
#                    if cv2.contourArea(cnt) > min_area]
#
#     # Ensure log directory exists
#     os.makedirs(log_dir, exist_ok=True)
#
#     # Write results to text file
#     result_txt = os.path.join(log_dir, 'results.txt')
#     with open(result_txt, 'w') as f:
#         f.write(f"Main rectangles: {len(main_rects)}\n")
#         f.write(f"Ghost rectangles: {len(ghost_rects)}\n")
#
#     # Draw rectangles on original image
#     output_img = img.copy()
#     for (x, y, w, h) in main_rects:
#         cv2.rectangle(output_img, (x, y), (x + w, y + h), (0, 0, 255), 2)  # red for main
#     for (x, y, w, h) in ghost_rects:
#         cv2.rectangle(output_img, (x, y), (x + w, y + h), (255, 0, 0), 2)  # blue for ghost
#
#     # Save annotated image
#     result_img = os.path.join(log_dir, 'result.png')
#     cv2.imwrite(result_img, output_img)
#
#     # Print counts
#     print(f"Main rectangles: {len(main_rects)}")
#     print(f"Ghost rectangles: {len(ghost_rects)}")
#
#
#

import cv2
import numpy as np
import time

# 霍夫变换检测参数
CIRCLE_DP = 1.0         #控制霍夫空间(累加器)的分辨率,dp=1.0 表示累加器与输入图像分辨率相同,dp=2.0 表示累加器分辨率为图像的一半(速度更快,但精度降低)
CIRCLE_MIN_DIST = 30    #圆之间的最小距离
CIRCLE_PARAM1 = 50      #Canny边缘检测的高阈值,若边缘缺失(圆不完整),降低 param1(如 30),若噪声过多(误检多),增大 param1(如 80)
CIRCLE_PARAM2 = 15      #控制累加器阈值,决定圆被检测到的置信度,若漏检严重(圆未检出),减小 param2,若误检多(假圆),增大 param2
CIRCLE_MIN_R = 5        #圆的半径范围
CIRCLE_MAX_R = 40


def is_circle_duplicate(circle_list, center, radius, factor=0.7):
    """检查圆是否重叠"""
    for circle in circle_list:
        dist = np.sqrt((circle['center'][0] - center[0]) ** 2 +
                       (circle['center'][1] - center[1]) ** 2)
        min_dist = factor * (radius + circle['radius'])
        if dist < min_dist:
            return True
    return False


def detect_circles(image_path):
    """执行检测并返回排序后的圆"""
    start_time = time.time()
    img = cv2.imread(image_path)
    if img is None:
        print(f"错误:无法读取图像 - {image_path}")
        return [], 0, None

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
    cleaned_img = cv2.morphologyEx(gray, cv2.MORPH_OPEN, kernel)
    blurred = cv2.GaussianBlur(cleaned_img, (9, 9), 2)

    # 霍夫圆检测
    circles = cv2.HoughCircles(
        blurred, cv2.HOUGH_GRADIENT, dp=CIRCLE_DP, minDist=CIRCLE_MIN_DIST,
        param1=CIRCLE_PARAM1, param2=CIRCLE_PARAM2,
        minRadius=CIRCLE_MIN_R, maxRadius=CIRCLE_MAX_R
    )

    circle_results = []
    if circles is not None:
        circles = np.round(circles[0, :]).astype("int")
        for (x, y, r) in circles:
            if not is_circle_duplicate(circle_results, (x, y), r):
                circle_results.append({'center': (x, y), 'radius': r, 'type': 'hough'})

    # 按行优先排序(先Y,再X)
    if circle_results:
        circle_results = sort_circles_by_grid(circle_results)

    return circle_results, time.time() - start_time, img


def sort_circles_by_grid(circles):
    """按网格顺序排序圆(从上到下,从左到右)"""
    if not circles:
        return circles

    # 按Y坐标排序
    circles.sort(key=lambda c: c['center'][1])

    # 分组为行
    rows = []
    if len(circles) > 0:
        current_row = [circles[0]]
        row_y = circles[0]['center'][1]

        for circle in circles[1:]:
            y = circle['center'][1]
            # 如果Y坐标差异小于行高阈值,则属于同一行
            if abs(y - row_y) < 30:  # 行高阈值可调整
                current_row.append(circle)
            else:
                rows.append(current_row)
                current_row = [circle]
                row_y = y
        rows.append(current_row)

    # 每行内按X坐标排序
    for row in rows:
        row.sort(key=lambda c: c['center'][0])

    # 展平为一维列表
    sorted_circles = []
    for row in rows:
        sorted_circles.extend(row)

    return sorted_circles

import cv2
import os
import numpy as np


def draw_circles(img, circles):
    """绘制圆并按顺序标记序号"""
    result_img = img.copy()
    for idx, circle in enumerate(circles, 1):
        center = circle['center']
        radius = circle['radius']

        # 绘制圆和圆心
        cv2.circle(result_img, center, radius, (0, 255, 0), 2)
        cv2.circle(result_img, center, 2, (0, 0, 255), -1)

        # 标记序号
        text = f"{idx}"
        text_pos = (center[0] + 10, center[1] + 5)
        cv2.putText(result_img, text, text_pos,
                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)

        # 标记半径
        cv2.putText(result_img, f"r={radius}",
                    (center[0] - 10, center[1] - 10),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 100, 255), 1)

    return result_img


def save_results(img, circles, output_name, proc_time):
    """保存结果和坐标"""
    os.makedirs("log", exist_ok=True)
    output_path = os.path.join("log", output_name)

    # 添加统计信息
    stats_text = f"检测到 {len(circles)} 个圆 | 时间: {proc_time:.2f}s"
    cv2.putText(img, stats_text, (10, 30),
                cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
    cv2.imwrite(output_path, img)

    # 保存坐标
    coord_path = os.path.join("log", "circle_coordinates.txt")
    with open(coord_path, "w") as f:
        f.write("序号, X, Y, 半径\n")
        for idx, circle in enumerate(circles, 1):
            x, y = circle['center']
            f.write(f"{idx}, {x}, {y}, {circle['radius']}\n")

import numpy as np


def read_input_params(file_path):
    """读取输入参数文件"""
    try:
        with open(file_path, 'r') as f:
            params = [float(line.strip()) for line in f.readlines()]

        if len(params) != 6:
            raise ValueError("输入参数文件必须包含6行有效数据")

        return {
            'p': params[0],  # 相机像素尺寸(mm/像素)
            'f': params[1],  # 相机焦距(mm)
            'CCDx': params[2],  # 横向分辨率(像素)
            'CCDy': params[3],  # 纵向分辨率(像素)
            'VIDs': params[4],  # 理论投影距离(mm)
            'FOVs': params[5]  # 纵向视场角设计值(度)
        }
    except Exception as e:
        print(f"读取参数文件出错: {str(e)}")
        return None


def calculate_lda(y_coord, params):
    """计算下视角(LDA)"""
    if params is None:
        return 0

    # 公式: LDA = arctan(((y坐标 - 0.5*CCDy)*p)/f)
    y_ccd = y_coord - 0.5 * params['CCDy']
    numerator = y_ccd * params['p']
    lda_rad = np.arctan(numerator / params['f'])
    return np.degrees(lda_rad)  # 转换为角度


def calculate_loa(x_coord, params):
    """计算左视角(LOA)"""
    if params is None:
        return 0

    # 公式: LOA = arctan(((x坐标 - 0.5*CCDx)*p)/f)
    x_ccd = x_coord - 0.5 * params['CCDx']
    numerator = x_ccd * params['p']
    loa_rad = np.arctan(numerator / params['f'])
    return np.degrees(loa_rad)  # 转换为角度


def calculate_fov(circles, params):
    """计算视场角(FOV)"""
    if params is None or len(circles) < 104:
        return 0, 0

    # 确保索引有效
    idx47 = 46  # 第47个点的索引(0-based)
    idx58 = 57  # 第58个点的索引(0-based)
    idx69 = 68  # 第69个点的索引(0-based)
    idx104 = 103  # 第104个点的索引(0-based)

    # 横向FOVH计算
    x69 = circles[idx69]['center'][0]
    x47 = circles[idx47]['center'][0]
    delta_x = x69 - x47
    fovh_rad = 2 * np.arctan((0.5 * delta_x * params['p']) / params['f'])
    fovh_deg = np.degrees(fovh_rad)

    # 纵向FOVV计算
    y104 = circles[idx104]['center'][1]
    y12 = circles[11]['center'][1]  # 第12个点的索引(0-based)
    delta_y = y104 - y12
    fovv_rad = 2 * np.arctan((0.5 * delta_y * params['p']) / params['f'])
    fovv_deg = np.degrees(fovv_rad)

    return fovh_deg, fovv_deg


def calculate_vid(fovv_deg, params):
    """计算投影距离(VID)"""
    if params is None:
        return 0

    # 公式: VID = (FOVV/FOVs)*VIDs
    return (fovv_deg / params['FOVs']) * params['VIDs']


def calculate_virtual_image_size(vid, loa_deg, lda_deg, fovh_deg, fovv_deg):
    """计算虚像大小"""
    if vid <= 0:
        return 0, 0

    # 转换为弧度
    loa_rad = np.radians(loa_deg)
    lda_rad = np.radians(lda_deg)
    fovh_rad = np.radians(fovh_deg)
    fovv_rad = np.radians(fovv_deg)

    # 计算虚像长度
    length = vid * np.cos(loa_rad) * (
            np.tan(0.5 * fovh_rad + loa_rad) +
            np.tan(0.5 * fovh_rad - loa_rad)
    )

    # 计算虚像宽度
    width = vid * np.cos(lda_rad) * (
            np.tan(0.5 * fovv_rad + lda_rad) -
            np.tan(lda_rad - 0.5 * fovv_rad)
    )

    return length, width


def calculate_distortion_geely(circles):
    """计算特定点的畸变 (Distortion_geely)"""
    if len(circles) < 115:
        print(f"警告: 检测到{len(circles)}个圆,少于115个,无法计算Distortion_geely")
        return 0

    # 获取指定点的坐标 (0-based索引)
    x1, y1 = circles[0]['center']  # 第1个点
    x2, y2 = circles[22]['center']  # 第23个点
    x3, y3 = circles[92]['center']  # 第93个点
    x4, y4 = circles[114]['center']  # 第115个点

    # 第一步: 计算中心点坐标
    Xa = (x1 + x2 + x3 + x4) / 4
    Ya = (y1 + y2 + y3 + y4) / 4

    # 第二步: 计算相对于中心点的偏移
    Xb = x1 - Xa
    Yb = y1 - Ya
    Xc = x2 - Xa
    Yc = y2 - Ya
    Xd = x3 - Xa
    Yd = y3 - Ya
    Xe = x4 - Xa
    Ye = y4 - Ya

    # 第三步: 计算平均偏移
    Xf = (abs(Xb) + abs(Xc) + abs(Xd) + abs(Xe)) / 4
    Yf = (abs(Yb) + abs(Yc) + abs(Yd) + abs(Ye)) / 4

    # 第四步: 计算D值
    D = np.sqrt(Xf ** 2 + Yf ** 2)

    # 第五步: 计算最大偏差与D的比值
    deviations = [
        np.sqrt((abs(Xf) - abs(Xb)) ** 2 + (abs(Yf) - abs(Yb)) ** 2),
        np.sqrt((abs(Xf) - abs(Xc)) ** 2 + (abs(Yf) - abs(Yc)) ** 2),
        np.sqrt((abs(Xf) - abs(Xd)) ** 2 + (abs(Yf) - abs(Yd)) ** 2),
        np.sqrt((abs(Xf) - abs(Xe)) ** 2 + (abs(Yf) - abs(Ye)) ** 2)
    ]

    max_deviation = max(deviations)
    distortion = max_deviation / D if D > 0 else 0

    return distortion


# def calculate_distortion_cs(circles):
#     """计算所有点的畸变 (Distortion_CS)"""
#     if len(circles) < 115:
#         print(f"警告: 检测到{len(circles)}个圆,少于115个,无法计算Distortion_CS")
#         return 0
#
#     # 仅使用前115个点
#     selected_circles = circles[:115]
#
#     # 第一步: 计算所有点的中心点坐标
#     x_coords = [circle['center'][0] for circle in selected_circles]
#     y_coords = [circle['center'][1] for circle in selected_circles]
#
#     Xa = sum(x_coords) / len(selected_circles)
#     Ya = sum(y_coords) / len(selected_circles)
#
#     # 第二步和第三步: 计算每个点的偏移和平均偏移
#     X_offsets = [x - Xa for x in x_coords]
#     Y_offsets = [y - Ya for y in y_coords]
#
#     Xf = sum(abs(x) for x in X_offsets) / len(X_offsets)
#     Yf = sum(abs(y) for y in Y_offsets) / len(Y_offsets)
#
#     # 第四步: 计算D值
#     D = np.sqrt(Xf * Xf + Yf * Yf)
#
#     # 第五步: 计算每个点的偏差并找出最大值
#     max_deviation = 0
#     for i in range(len(selected_circles)):
#         deviation = np.sqrt((abs(Xf) - abs(X_offsets[i])) ** 2 +
#                             (abs(Yf) - abs(Y_offsets[i])) ** 2)
#         max_deviation = max(max_deviation, deviation)
#
#     distortion = max_deviation / D if D > 0 else 0
#
#     return distortion


def calculate_all_params(circles, params_file):
    """计算所有HUD投影参数"""
    # 读取输入参数
    params = read_input_params(params_file)
    if params is None:
        return None

    # 确保有足够的圆
    if len(circles) < 104:
        print(f"警告: 检测到{len(circles)}个圆,少于104个,无法完成所有计算")
        return None

    # 第58个圆的坐标(1-based -> 0-based索引57)
    circle58 = circles[57]
    x58, y58 = circle58['center']

    # 计算下视角(LDA)
    lda = calculate_lda(y58, params)

    # 计算左视角(LOA)
    loa = calculate_loa(x58, params)

    # 计算视场角(FOV)
    fovh, fovv = calculate_fov(circles, params)

    # 计算投影距离(VID)
    vid = calculate_vid(fovv, params)

    # 计算虚像大小
    length, width = calculate_virtual_image_size(vid, loa, lda, fovh, fovv)

    # 计算畸变
    distortion_geely = calculate_distortion_geely(circles)
    # distortion_cs = calculate_distortion_cs(circles)

    return {
        'lda': lda,  # 下视角(度)
        'loa': loa,  # 左视角(度)
        'fovh': fovh,  # 横向视场角(度)
        'fovv': fovv,  # 纵向视场角(度)
        'vid': vid,  # 投影距离(mm)
        'length': length,  # 虚像长度(mm)
        'width': width,  # 虚像宽度(mm)
        # 'distortion_geely': distortion_geely,  # 吉利畸变
        # 'distortion_cs': distortion_cs  # 国标畸变
        'distortion_geely': round(distortion_geely, 6),
        # 'distortion_cs': round(distortion_cs, 6)
    }

onlyjuxing

import cv2
import numpy as np
import os

# 确保输出文件夹存在
os.makedirs('log', exist_ok=True)

# 读取图像并转为灰度图
image_path = 'sample_data/ghost.jpeg'
gray = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if gray is None:
    raise FileNotFoundError(f"无法读取图像: {image_path}")
color_img = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)

# 使用 KMeans 对图像亮点像素聚类,自动区分本体与重影
pixels = gray[gray > 0].astype(np.float32)
if pixels.size == 0:
    raise ValueError("图像中未检测到任何亮点。")
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
_, labels, centers = cv2.kmeans(pixels.reshape(-1, 1), 2, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
centers = centers.flatten()
main_center = centers.max()
ghost_center = centers.min()
thresh = (main_center + ghost_center) / 2.0

main_mask = (gray > thresh).astype(np.uint8) * 255
ghost_gray = gray.copy()
ghost_gray[main_mask == 255] = 0

# 本体连通域分析
num_labels, labels_im, stats, centroids = cv2.connectedComponentsWithStats(main_mask, 8, cv2.CV_32S)
if num_labels <= 1:
    raise ValueError("未检测到任何本体线段。")
object_labels = list(range(1, num_labels))
shape_w = int(np.median(stats[1:, cv2.CC_STAT_WIDTH]))
shape_h = int(np.median(stats[1:, cv2.CC_STAT_HEIGHT]))

# 自动估计垂直行间距(用于判断重影搜索窗口)
row_center_ys = sorted([centroids[i][1] for i in object_labels])
row_spacing = np.median(np.diff(row_center_ys)) if len(row_center_ys) > 5 else shape_h * 2

# 开始处理每个本体,寻找重影并输出结果
with open('log/detection_results.txt', 'w', encoding='utf-8') as f:
    idx = 1
    for label in object_labels:
        mx, my = centroids[label]
        x, y, w0, h0 = stats[label, cv2.CC_STAT_LEFT], stats[label, cv2.CC_STAT_TOP], stats[label, cv2.CC_STAT_WIDTH], \
        stats[label, cv2.CC_STAT_HEIGHT]
        # 绘制红色本体框
        cv2.rectangle(color_img, (x, y), (x + w0, y + h0), (0, 0, 255), 2)

        # 自动判断线条方向:宽>高为横线,否则为竖线
        is_horizontal = w0 >= h0

        # 定义重影搜索区域:横线向上搜索,竖线向左搜索
        margin = 5
        if is_horizontal:
            # 横线:重影在上方区域
            sx0 = int(mx - w0 / 2 - margin)
            sx1 = int(mx + w0 / 2 + margin)
            sy0 = int(my - row_spacing)
            sy1 = int(my - h0 / 2)
        else:
            # 竖线:重影在左方区域
            sx0 = int(mx - row_spacing)
            sx1 = int(mx - w0 / 2)
            sy0 = int(my - h0 / 2 - margin)
            sy1 = int(my + h0 / 2 + margin)

        sx0, sy0 = max(0, sx0), max(0, sy0)
        sx1, sy1 = min(gray.shape[1], sx1), min(gray.shape[0], sy1)
        roi = ghost_gray[sy0:sy1, sx0:sx1]

        if roi.size == 0 or np.sum(roi) == 0:
            gx, gy = mx, my
            dist_h = 0.0
            dist_v = 0.0
            f.write(f"编号{idx}: 本体中心 ({mx:.1f},{my:.1f}),未检测到重影\n")
        else:
            # 改进的质心提取方式:避免偏移,保持重心靠近 ROI 重心
            Y, X = np.indices(roi.shape)
            total = float(np.sum(roi))
            gx = float(np.sum(X * roi)) / total + sx0
            gy = float(np.sum(Y * roi)) / total + sy0

            if is_horizontal:
                dist_h = 0.0
                dist_v = abs(gy - my)
            else:
                dist_v = 0.0
                dist_h = abs(gx - mx)

            # 绘制蓝色重影框(与本体等尺寸)
            gx0 = int(gx - w0 / 2)
            gy0 = int(gy - h0 / 2)
            gx1 = gx0 + w0
            gy1 = gy0 + h0
            cv2.rectangle(color_img, (gx0, gy0), (gx1, gy1), (255, 0, 0), 2)

            f.write(
                f"编号{idx}: 本体中心 ({mx:.1f},{my:.1f}),重影中心 ({gx:.1f},{gy:.1f}),"
                f"水平距离 {dist_h:.1f} 像素,竖直距离 {dist_v:.1f} 像素\n"
            )
        idx += 1

# 保存输出图片
cv2.imwrite('log/detection_result.png', color_img)


##########################蓝色框有偏移
# # import cv2
# # import numpy as np
# # import os
# #
# import cv2
# import numpy as np
# import os
#
# # 确保输出文件夹存在
# os.makedirs('log', exist_ok=True)
#
# # 1. 读取输入图像并转为灰度图
# image_path = 'sample_data/ghost.jpeg'
# gray = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# if gray is None:
#     raise FileNotFoundError(f"无法读取图像: {image_path}")
# # 转为彩色图用于绘制彩色框
# color_img = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
#
# # 2. 利用 KMeans 聚类将非背景灰度像素分为两类:本体(高亮)和重影(暗)
# pixels = gray[gray > 0].astype(np.float32)
# if pixels.size == 0:
#     raise ValueError("图像中未检测到任何亮点。")
# criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
# _, labels, centers = cv2.kmeans(pixels.reshape(-1, 1), 2, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
# centers = centers.flatten()
# main_center = centers[0] if centers[0] > centers[1] else centers[1]
# ghost_center = centers[1] if centers[0] > centers[1] else centers[0]
# threshold_val = (main_center + ghost_center) / 2.0
#
# # 生成本体掩膜(亮度大于阈值)和重影灰度图(去除本体后保留暗部)
# main_mask = np.zeros_like(gray, dtype=np.uint8)
# main_mask[gray > threshold_val] = 255
# ghost_gray = gray.copy()
# ghost_gray[main_mask == 255] = 0
#
# # 3. 连通组件分析识别本体矩形,并获取统计信息和质心
# num_labels, labels_im, stats, centroids = cv2.connectedComponentsWithStats(main_mask, 8, cv2.CV_32S)
# if num_labels <= 1:
#     raise ValueError("未检测到任何本体线段。")
#
# # 获取所有本体的标签 ID 列表(跳过背景 0)
# object_labels = list(range(1, num_labels))
# # 获取典型矩形宽高,用于后续框大小
# shape_w = int(np.median(stats[1:, cv2.CC_STAT_WIDTH]))
# shape_h = int(np.median(stats[1:, cv2.CC_STAT_HEIGHT]))
#
# # 估计行间距,用于后续重影搜索区域计算
# row_center_ys = [centroids[i][1] for i in object_labels]
# row_center_ys.sort()
# if len(row_center_ys) > 1:
#     spacings = [row_center_ys[i + 1] - row_center_ys[i] for i in range(len(row_center_ys) - 1)]
#     row_spacing = float(np.median(spacings))
# else:
#     row_spacing = shape_h * 2
#
# # 4. 对每个本体,按检测到的质心顺序进行处理并输出结果
# with open('log/detection_results.txt', 'w', encoding='utf-8') as f:
#     idx = 1
#     for label in object_labels:
#         mx, my = centroids[label]  # 本体中心坐标
#         # 定位重影区域 ROI:本体上方,水平延伸 shape_w/2 + 5 像素
#         x0 = int(mx - shape_w / 2 - 5)
#         x1 = int(mx + shape_w / 2 + 5)
#         y1 = int(my)
#         y0 = int(my - min(row_spacing - shape_h, row_spacing * 0.5) - shape_h)
#         x0, y0 = max(0, x0), max(0, y0)
#         x1, y1 = min(gray.shape[1], x1), min(gray.shape[0], y1)
#         roi = ghost_gray[y0:y1, x0:x1]
#
#         if roi.size == 0 or np.sum(roi) == 0:
#             # 未找到重影
#             gx, gy = mx, my
#             dist_h = 0.0
#             dist_v = 0.0
#             f.write(f"编号{idx}: 本体中心 ({mx:.1f},{my:.1f}),未检测到重影\n")
#         else:
#             # 计算重影灰度质心
#             Y, X = np.indices(roi.shape)
#             total = float(np.sum(roi))
#             gx = float(np.sum(X * roi)) / total + x0
#             gy = float(np.sum(Y * roi)) / total + y0
#             # 判断线段方向:宽 > 高 为横线,否则为竖线
#             w = stats[label, cv2.CC_STAT_WIDTH]
#             h = stats[label, cv2.CC_STAT_HEIGHT]
#             if w > h:
#                 # 横线:重影在垂直方向
#                 dist_h = 0.0
#                 dist_v = abs(gy - my)
#             else:
#                 # 竖线:重影在水平方向
#                 dist_v = 0.0
#                 dist_h = abs(gx - mx)
#             # 写入日志,包含中心坐标与水平/竖直距离
#             f.write(
#                 f"编号{idx}: 本体中心 ({mx:.1f},{my:.1f}),重影中心 ({gx:.1f},{gy:.1f}),"
#                 f"水平距离 {dist_h:.1f} 像素,竖直距离 {dist_v:.1f} 像素\n"
#             )
#             # 绘制本体和重影的矩形框
#             x, y, w0, h0 = stats[label, cv2.CC_STAT_LEFT], stats[label, cv2.CC_STAT_TOP], stats[
#                 label, cv2.CC_STAT_WIDTH], stats[label, cv2.CC_STAT_HEIGHT]
#             # 红色框 本体
#             cv2.rectangle(color_img, (x, y), (x + w0, y + h0), (0, 0, 255), 2)
#             # 蓝色框 重影(使用同样大小框,以中心对齐)
#             gx0 = int(gx - w0 / 2)
#             gy0 = int(gy - h0 / 2)
#             gx1 = gx0 + w0
#             gy1 = gy0 + h0
#             cv2.rectangle(color_img, (gx0, gy0), (gx1, gy1), (255, 0, 0), 2)
#         idx += 1
#
# # 5. 保存标注结果图像(不弹窗)
# cv2.imwrite('log/detection_result.png', color_img)



###############非常好
# # 确保输出文件夹存在
# os.makedirs('log', exist_ok=True)
#
# # 1. 读取输入图像并转为灰度图
# image_path = 'sample_data/ghost.jpeg'
# gray = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# if gray is None:
#     raise FileNotFoundError(f"无法读取图像: {image_path}")
#
# # 将灰度图转换为彩色图,用于绘制彩色标注
# color_img = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
#
# # 2. 利用像素强度聚类,将亮度较高的本体与亮度较低的重影区分开
# # 首先,获取图像中所有非背景(黑色)像素的灰度值
# pixels = gray[gray > 0].astype(np.float32)
# if pixels.size == 0:
#     raise ValueError("图像中未检测到任何亮点。")
#
# # 使用 KMeans 聚类将非零像素灰度分为两类(本体和重影):contentReference[oaicite:3]{index=3}
# criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
# _, labels, centers = cv2.kmeans(pixels.reshape(-1, 1), 2, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
# centers = centers.flatten()
# # 判断哪个聚类中心对应本体(较亮),哪个对应重影(较暗)
# main_cluster_center = centers[0] if centers[0] > centers[1] else centers[1]
# ghost_cluster_center = centers[1] if centers[0] > centers[1] else centers[0]
# # 取两个中心值的中点作为阈值,将灰度图划分为本体和重影区域
# threshold_val = (main_cluster_center + ghost_cluster_center) / 2.0
#
# # 生成二值掩膜:本体掩膜和重影灰度图
# main_mask = np.zeros_like(gray, dtype=np.uint8)
# main_mask[gray > threshold_val] = 255
# # 重影灰度图:将原图中本体像素去除(置零),保留下较暗的重影部分
# ghost_gray = gray.copy()
# ghost_gray[main_mask == 255] = 0
#
# # 3. 寻找每个本体矩形(连通组件)及其中心坐标:contentReference[oaicite:4]{index=4}
# num_labels, labels_im, stats, centroids = cv2.connectedComponentsWithStats(main_mask, 8, cv2.CV_32S)
# num_objects = num_labels - 1  # 去除背景标签
# if num_objects == 0:
#     raise ValueError("未检测到任何本体线段。")
#
# # 提取所有本体的质心坐标列表(跳过标签0背景)
# object_centers = [tuple(centroids[i]) for i in range(1, num_labels)]
#
# # 将检测到的中心按垂直方向聚类分为5行
# rows = 5
# Y = np.array([centroids[i][1] for i in range(1, num_labels)], dtype=np.float32)
# K = rows if num_objects >= rows else num_objects
# _, row_labels, row_centers = cv2.kmeans(Y.reshape(-1, 1), K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
# row_centers = row_centers.flatten()
# # 按行从上到下排序每个组件,然后在行内按x从小到大排序
# row_order = np.argsort(row_centers)
# sorted_labels = []
# for ci in row_order:
#     indices = [i + 1 for i in range(row_labels.shape[0]) if row_labels[i] == ci]
#     indices.sort(key=lambda a: centroids[a][0])
#     sorted_labels += indices
#
# # 从统计信息获取典型的矩形宽度和高度(取中位数,以减小噪声影响)
# shape_w = int(np.median(stats[1:, cv2.CC_STAT_WIDTH]))
# shape_h = int(np.median(stats[1:, cv2.CC_STAT_HEIGHT]))
#
# # 估计相邻行的垂直间距,用于确定重影搜索区域高度
# row_centers_y = []
# for ci in row_order:
#     # 取该行所有本体的平均y坐标作为行中心
#     y_coords = [centroids[i][1] for i in range(1, num_labels) if row_labels[i - 1] == ci]
#     if y_coords:
#         row_centers_y.append(sum(y_coords) / len(y_coords))
# if len(row_centers_y) > 1:
#     spacings = [row_centers_y[i + 1] - row_centers_y[i] for i in range(len(row_centers_y) - 1)]
#     row_spacing = float(np.median(spacings))
# else:
#     row_spacing = float(shape_h * 2)
#
# # 4. 针对每个本体,在其上方局部区域定位重影中心并计算距离
# margin = 5  # 水平余量,以涵盖重影可能的小水平偏移
# # 垂直搜索高度为行间距的一半或行间距减去本体高度(取较小者),最低不少于5像素
# max_search = int(max(5.0, min(row_spacing - shape_h, row_spacing * 0.5)))
#
# # 打开结果文本文件用于写入
# with open('log/detection_results.txt', 'w', encoding='utf-8') as f:
#     for idx, label in enumerate(sorted_labels, start=1):
#         mx, my = centroids[label]  # 本体中心
#         # 定义重影搜索ROI(在本体中心正上方区域)
#         x0 = int(mx - shape_w / 2 - margin)
#         x1 = int(mx + shape_w / 2 + margin)
#         y1 = int(my)  # ROI下边界(不包含本体所在行)
#         y0 = int(my - max_search - shape_h)  # ROI上边界(在本体上方一定距离处)
#         # 边界检查,确保ROI在图像范围内
#         if x0 < 0:
#             x0 = 0
#         if x1 > gray.shape[1]:
#             x1 = gray.shape[1]
#         if y0 < 0:
#             y0 = 0
#         if y1 > gray.shape[0]:
#             y1 = gray.shape[0]
#
#         roi = ghost_gray[y0:y1, x0:x1]  # 提取重影区域的灰度图
#         if roi.size == 0 or np.sum(roi) == 0:
#             # 未在预期区域检测到任何重影像素
#             line = f"编号{idx}: 本体中心坐标 ({mx:.1f}, {my:.1f}),未找到对应的重影"
#         else:
#             # 计算ROI中像素的灰度质心作为重影中心
#             Y_coords, X_coords = np.indices(roi.shape)
#             total_intensity = float(np.sum(roi))
#             gx = float(np.sum(X_coords * roi)) / total_intensity + x0
#             gy = float(np.sum(Y_coords * roi)) / total_intensity + y0
#             # 计算本体与重影中心的欧氏距离(像素距离)
#             dist = np.sqrt((gx - mx) ** 2 + (gy - my) ** 2)
#             line = f"编号{idx}: 本体中心坐标 ({mx:.1f}, {my:.1f}),重影中心坐标 ({gx:.1f}, {gy:.1f}),像素距离 {dist:.1f} 像素"
#             # 在图像上绘制标记和连线
#             cv2.circle(color_img, (int(round(mx)), int(round(my))), 3, (0, 0, 255), -1)  # 红色圆点标记本体
#             cv2.circle(color_img, (int(round(gx)), int(round(gy))), 3, (255, 0, 0), -1)  # 蓝色圆点标记重影
#             cv2.line(color_img, (int(round(mx)), int(round(my))), (int(round(gx)), int(round(gy))), (0, 255, 0),
#                      1)  # 绿色连线
#
#         f.write(line + "\n")
#
# # 保存带标注的结果图像(不显示图像)
# cv2.imwrite('log/detection_result.png', color_img)


网站公告

今日签到

点亮在社区的每一天
去签到