#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
公用工具函数模块
"""


import json
import os
from xml.sax.handler import property_interning_dict

import cv2
import numpy as np
from typing import Dict, Any, Optional, List
import traceback
import pickle
import base64
import copy
from PIL import Image, ImageDraw, ImageFont
import glob

def get_files_by_extension_glob(directory, extension):
    """
    使用glob模块读取指定后缀名的文件
    """
    # 确保扩展名以点开头
    if not extension.startswith('.'):
        extension = '.' + extension
    
    # 构建搜索模式
    pattern = os.path.join(directory, f"*{extension}")
    
    # 获取文件列表
    file_list = glob.glob(pattern)
    
    return file_list

def put_Text_cn(img, text, position, font_path="C://Windows//Fonts//simsun.ttc" , font_size=30, color=(255, 255, 255)):
    """
    在OpenCV图像上添加中文文字
    """
    # 将OpenCV图像转换为PIL图像
    img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    
    # 创建绘图对象
    draw = ImageDraw.Draw(img_pil)
    
    # 加载字体
    font = ImageFont.truetype(font_path, font_size)
    
    # 绘制文字
    draw.text(position, text, font=font, fill=color)
    
    # 转换回OpenCV格式
    img_cv = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
    
    return img_cv

def ensure_path_exists(file_path):
    """检测路径是否存在，不存在则逐级创建"""
    # 如果是文件路径，获取目录路径
    if os.path.isfile(file_path) or '.' in os.path.basename(file_path):
        dir_path = os.path.dirname(file_path)
    else:
        dir_path = file_path
    
    # 如果目录不存在，则创建
    if dir_path and not os.path.exists(dir_path):
        try:
            os.makedirs(dir_path, exist_ok=True)
            #print(f"Created directory: {dir_path}")
            return True
        except Exception as e:
            #print(f"Failed to create directory {dir_path}: {e}")
            return False
    return True


def load_json_file(file_path: str) -> Optional[Dict[str, Any]]:
    """
    加载JSON文件为字典
    
    Args:
        file_path: JSON文件路径
        
    Returns:
        Dict: JSON数据字典，失败返回None
    """
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            return json.load(f)
    except Exception as e:
        print(f"加载JSON文件失败: {e}")
        return None


def save_json_file(data: Dict[str, Any], file_path: str) -> bool:
    """
    保存字典到JSON文件
    
    Args:
        data: 要保存的数据字典
        file_path: 保存路径
        
    Returns:
        bool: 是否保存成功
    """
    try:
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        return True
    except Exception as e:
        print(f"保存JSON文件失败: {e}")
        return False

def save_json_file_ex(data: Any, filepath: str, encoding: str = 'utf-8') -> bool:
    """
    保存JSON文件的增强版本，支持不可序列化的数据
    对于不可序列化的对象，会使用pickle进行序列化并转换为base64字符串
    
    Args:
        data: 要保存的数据
        filepath: 文件路径
        encoding: 文件编码，默认为utf-8
    
    Returns:
        bool: 保存成功返回True，失败返回False
    """
    
    def make_serializable(obj):
        """递归处理对象，使其可JSON序列化"""
        try:
            # 尝试直接序列化
            json.dumps(obj)
            return obj
        except (TypeError, ValueError):
            # 如果不能序列化，根据类型处理
            if isinstance(obj, dict):
                return {k: make_serializable(v) for k, v in obj.items()}
            elif isinstance(obj, (list, tuple)):
                return [make_serializable(item) for item in obj]
            elif isinstance(obj, set):
                return {"__set__": [make_serializable(item) for item in obj]}
            else:
                # 对于其他不可序列化的对象，使用pickle + base64
                try:
                    pickled = pickle.dumps(obj)
                    encoded = base64.b64encode(pickled).decode('ascii')
                    return {"__pickled__": encoded, "__type__": type(obj).__name__}
                except Exception:
                    # 如果pickle也失败，返回字符串表示
                    return {"__str__": str(obj), "__type__": type(obj).__name__}
    
    try:
        # 处理数据使其可序列化
        serializable_data = make_serializable(data)
        
        # 保存到文件
        with open(filepath, 'w', encoding=encoding) as f:
            json.dump(serializable_data, f, ensure_ascii=False, indent=2)
        
        return True
        
    except Exception as e:
        print(f"保存JSON文件失败: {e}")
        return False
    
def load_image_unicode(file_path: str) -> Optional[np.ndarray]:
    """
    加载浮点数图像文件（支持中文路径）
    
    Args:
        file_path: 图像文件路径
        
    Returns:
        numpy.ndarray: 浮点数图像数组，失败返回None
    """
    try:
        img_data = np.fromfile(file_path, dtype=np.uint8)
        
        # 根据文件扩展名选择不同的解码标志
        ext = file_path.lower().split('.')[-1]
        
        if ext in ['exr', 'hdr', 'tiff', 'tif']:
            # 对于浮点格式，使用ANYDEPTH和ANYCOLOR标志
            img = cv2.imdecode(img_data, cv2.IMREAD_ANYDEPTH | cv2.IMREAD_ANYCOLOR)
        else:
            # 对于普通格式，使用COLOR标志
            img = cv2.imdecode(img_data, cv2.IMREAD_COLOR)
            
        return img
    except Exception as e:
        print(f"加载浮点数图像失败: {e}")
        return None


def save_image_unicode(image: np.ndarray, file_path: str) -> bool:
    """
    保存图像文件（支持中文路径）
    
    Args:
        image: 图像数组
        file_path: 保存路径
        
    Returns:
        bool: 是否保存成功
    """
    try:
        ext = os.path.splitext(file_path)[1]
        success, encoded_img = cv2.imencode(ext, image)
        if success:
            encoded_img.tofile(file_path)
            return True
        return False
    except Exception as e:
        print(f"保存图像失败: {e}")
        return False


def validate_file_exists(file_path: str) -> bool:
    """
    验证文件是否存在
    
    Args:
        file_path: 文件路径
        
    Returns:
        bool: 文件是否存在
    """
    return os.path.exists(file_path) and os.path.isfile(file_path)


def get_image_info(image: np.ndarray) -> Dict[str, Any]:
    """
    获取图像信息
    
    Args:
        image: 图像数组
        
    Returns:
        Dict: 图像信息字典
    """
    if image is None:
        return {"valid": False, "error": "图像为空"}
    
    shape = image.shape
    return {
        "valid": True,
        "height": shape[0],
        "width": shape[1],
        "channels": shape[2] if len(shape) > 2 else 1,
        "dtype": str(image.dtype),
        "size": image.size
    }



def extract_json_from_string(text: str) -> Dict[str, Any]:
    """
    从字符串中提取JSON格式的内容并转换为字典
    
    Args:
        text (str): 包含JSON内容的字符串
        
    Returns:
        Dict[str, Any]: 解析后的字典，如果没有找到有效JSON则返回空字典
        
    Examples:
        >>> text = "这是一段文字```json\n{\"破损-1\": {\"位置\": [100, 200]}}\n```更多文字"
        >>> result = extract_json_from_string(text)
        >>> print(result)
        {'破损-1': {'位置': [100, 200]}}
    """
    import json
    import re
    
    # 方法1: 查找 ```json 代码块
    json_block_pattern = r'```json\s*\n?(.*?)\n?```'
    matches = re.findall(json_block_pattern, text, re.DOTALL | re.IGNORECASE)
    
    for match in matches:
        try:
            return json.loads(match.strip())
        except json.JSONDecodeError:
            continue
    
    # 方法2: 查找普通的 ``` 代码块（可能包含JSON）
    code_block_pattern = r'```\s*\n?(.*?)\n?```'
    matches = re.findall(code_block_pattern, text, re.DOTALL)
    
    for match in matches:
        try:
            return json.loads(match.strip())
        except json.JSONDecodeError:
            continue
    
    # 方法3: 查找花括号包围的JSON对象
    brace_pattern = r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}'
    matches = re.findall(brace_pattern, text, re.DOTALL)
    
    for match in matches:
        try:
            return json.loads(match.strip())
        except json.JSONDecodeError:
            continue
    
    # 方法4: 更复杂的嵌套花括号匹配
    def find_json_objects(s):
        """递归查找JSON对象"""
        results = []
        stack = []
        start = -1
        
        for i, char in enumerate(s):
            if char == '{':
                if not stack:
                    start = i
                stack.append(char)
            elif char == '}':
                if stack:
                    stack.pop()
                    if not stack and start != -1:
                        json_str = s[start:i+1]
                        try:
                            obj = json.loads(json_str)
                            results.append(obj)
                        except json.JSONDecodeError:
                            pass
                        start = -1
        return results
    
    json_objects = find_json_objects(text)
    if json_objects:
        return json_objects[0]  # 返回第一个有效的JSON对象
    
    return {}


def extract_all_json_from_string(text: str) -> List[Dict[str, Any]]:
    """
    从字符串中提取所有JSON格式的内容
    
    Args:
        text (str): 包含JSON内容的字符串
        
    Returns:
        List[Dict[str, Any]]: 所有解析成功的JSON字典列表
    """
    import json
    import re
    
    results = []
    
    # 查找所有 ```json 代码块
    json_block_pattern = r'```json\s*\n?(.*?)\n?```'
    matches = re.findall(json_block_pattern, text, re.DOTALL | re.IGNORECASE)
    
    for match in matches:
        try:
            results.append(json.loads(match.strip()))
        except json.JSONDecodeError:
            continue
    
    # 如果没找到json代码块，查找普通代码块
    if not results:
        code_block_pattern = r'```\s*\n?(.*?)\n?```'
        matches = re.findall(code_block_pattern, text, re.DOTALL)
        
        for match in matches:
            try:
                results.append(json.loads(match.strip()))
            except json.JSONDecodeError:
                continue
    
    # 如果还没找到，查找所有JSON对象
    if not results:
        def find_all_json_objects(s):
            objects = []
            stack = []
            start = -1
            
            for i, char in enumerate(s):
                if char == '{':
                    if not stack:
                        start = i
                    stack.append(char)
                elif char == '}':
                    if stack:
                        stack.pop()
                        if not stack and start != -1:
                            json_str = s[start:i+1]
                            try:
                                obj = json.loads(json_str)
                                objects.append(obj)
                            except json.JSONDecodeError:
                                pass
                            start = -1
            return objects
        
        results = find_all_json_objects(text)
    
    return results


def safe_extract_json(text: str, default_value: Any = None) -> Any:
    """
    安全地从字符串中提取JSON，失败时返回默认值
    
    Args:
        text (str): 包含JSON的字符串
        default_value (Any): 提取失败时的默认返回值
        
    Returns:
        Any: 提取的JSON数据或默认值
    """
    try:
        result = extract_json_from_string(text)
        return result if result else default_value
    except Exception:
        return default_value


def detect_edges_by_direction(image, params=None):
    """
    使用Sobel算子检测特定方向的边缘
    
    Args:
        image: 输入图像
        params: 参数字典，包含以下键值:
            - direction: str, 检测方向 ('horizontal', 'vertical', 'both')
            - ksize: int, Sobel核大小 (1, 3, 5, 7)
            - threshold: int, 二值化阈值 (0-255)
            - max_value: int, 二值化最大值 (0-255)
            - threshold_type: int, 阈值类型 (cv2.THRESH_BINARY, cv2.THRESH_BINARY_INV等)
    
    Returns:
        edges: 检测到的边缘图像
    """
    # 默认参数
    default_params = {
        'direction': 'both',        # 检测方向: 'horizontal'(水平梯度/垂直边缘), 'vertical'(垂直梯度/水平边缘), 'both'
        'ksize': 3,                 # Sobel核大小: 1, 3, 5, 7
        'threshold': 50,            # 二值化阈值: 0-255
        'max_value': 255,           # 二值化最大值: 0-255
        'threshold_type': cv2.THRESH_BINARY  # 阈值类型
    }
    
    # 合并参数
    if params is None:
        params = {}
    final_params = {**default_params, **params}
    
    # 转换为灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
    
    if final_params['direction'] == 'horizontal':
        # 检测水平方向的灰度变化（垂直边缘）
        sobel = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
    elif final_params['direction'] == 'vertical':
        # 检测垂直方向的灰度变化（水平边缘）
        sobel = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
    else:
        # 两个方向都检测
        sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
        sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
        sobel = np.sqrt(sobelx**2 + sobely**2)
    
    # 转换为uint8并应用阈值
    sobel = np.uint8(np.absolute(sobel))
    _, edges = cv2.threshold(sobel, final_params['threshold'], 
                           final_params['max_value'], final_params['threshold_type'])
    
    return edges

def detect_edges_gradient(image, params=None):
    """
    使用Sobel算子检测特定方向的边缘
    
    Args:
        image: 输入图像
        params: 参数字典，包含以下键值:
            - direction: str, 检测方向 ('horizontal', 'vertical', 'both')
            - ksize: int, Sobel核大小 (1, 3, 5, 7)
            - threshold: int, 二值化阈值 (0-255)
            - max_value: int, 二值化最大值 (0-255)
            - threshold_type: int, 阈值类型 (cv2.THRESH_BINARY, cv2.THRESH_BINARY_INV等)
    
    Returns:
        edges: 检测到的边缘图像
    """
    # 默认参数
    # default_params = {
    #     'direction': 'both',        # 检测方向: 'horizontal'(水平梯度/垂直边缘), 'vertical'(垂直梯度/水平边缘), 'both'
    #     'ksize': 3,                 # Sobel核大小: 1, 3, 5, 7
    #     'threshold': 50,            # 二值化阈值: 0-255
    #     'max_value': 255,           # 二值化最大值: 0-255
    #     'threshold_type': cv2.THRESH_BINARY  # 阈值类型
    # }
    default_params = {
        'direction': 'horizontal',   # 检测方向: 'horizontal'(水平梯度), 'vertical'(垂直梯度)
        'ksize': 3,                 # Sobel核大小: 1, 3, 5, 7
        'normalize': True,          # 是否归一化到0-255
        'threshold': 50,            # 二值化阈值: 0-255
        'apply_threshold': False,   # 是否应用二值化
        'max_value': 255,           # 二值化最大值
        'threshold_type': cv2.THRESH_BINARY  # 阈值类型
    }    
    # 合并参数
    if params is None:
        params = {}
    final_params = {**default_params, **params}
    
    # 转换为灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
    
    if final_params['direction'] == 'horizontal':
        # 检测水平方向的灰度变化（垂直边缘）
        sobel = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
    elif final_params['direction'] == 'vertical':
        # 检测垂直方向的灰度变化（水平边缘）
        sobel = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
    else:
        # 两个方向都检测
        sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
        sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
        sobel = np.sqrt(sobelx**2 + sobely**2)
    
    # 转换为uint8并应用阈值
    sobel = np.uint8(np.absolute(sobel))
    _, edges = cv2.threshold(sobel, final_params['threshold'], 
                           final_params['max_value'], final_params['threshold_type'])
    
    return edges

def detect_positive_gradient(image, params=None):
    """
    检测从暗到亮的变化（正梯度）
    
    Args:
        image: 输入图像
        params: 参数字典，包含以下键值:
            - direction: str, 检测方向 ('horizontal', 'vertical')
            - ksize: int, Sobel核大小 (1, 3, 5, 7)
            - normalize: bool, 是否归一化到0-255
            - threshold: int, 可选的二值化阈值
            - apply_threshold: bool, 是否应用二值化
    
    Returns:
        positive_edges: 正梯度边缘图像
    """
    # 默认参数
    default_params = {
        'direction': 'horizontal',   # 检测方向: 'horizontal'(水平梯度), 'vertical'(垂直梯度)
        'ksize': 3,                 # Sobel核大小: 1, 3, 5, 7
        'normalize': True,          # 是否归一化到0-255
        'threshold': 50,            # 二值化阈值: 0-255
        'apply_threshold': False,   # 是否应用二值化
        'max_value': 255,           # 二值化最大值
        'threshold_type': cv2.THRESH_BINARY  # 阈值类型
    }
    
    # 合并参数
    if params is None:
        params = {}
    final_params = {**default_params, **params}
    
    # 转换为灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
    
    if final_params['direction'] == 'horizontal':
        sobel = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
    else:
        sobel = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
    
    # 只保留正值（从暗到亮的变化）
    positive_edges = np.where(sobel > 0, sobel, 0)
    
    if final_params['normalize']:
        if positive_edges.max() > 0:
            positive_edges = np.uint8(positive_edges / positive_edges.max() * 255)
        else:
            positive_edges = np.uint8(positive_edges)
    else:
        positive_edges = np.uint8(np.clip(positive_edges, 0, 255))
    
    # 可选的二值化处理
    if final_params['apply_threshold']:
        _, positive_edges = cv2.threshold(positive_edges, final_params['threshold'], 
                                        final_params['max_value'], final_params['threshold_type'])
    
    return positive_edges

def detect_negative_gradient(image, params=None):
    """
    检测从亮到暗的变化（负梯度）
    
    Args:
        image: 输入图像
        params: 参数字典，包含以下键值:
            - direction: str, 检测方向 ('horizontal', 'vertical')
            - ksize: int, Sobel核大小 (1, 3, 5, 7)
            - normalize: bool, 是否归一化到0-255
            - threshold: int, 可选的二值化阈值
            - apply_threshold: bool, 是否应用二值化
    
    Returns:
        negative_edges: 负梯度边缘图像
    """
    # 默认参数
    default_params = {
        'direction': 'horizontal',   # 检测方向: 'horizontal'(水平梯度), 'vertical'(垂直梯度)
        'ksize': 3,                 # Sobel核大小: 1, 3, 5, 7
        'normalize': True,          # 是否归一化到0-255
        'threshold': 50,            # 二值化阈值: 0-255
        'apply_threshold': False,   # 是否应用二值化
        'max_value': 255,           # 二值化最大值
        'threshold_type': cv2.THRESH_BINARY  # 阈值类型
    }
    
    # 合并参数
    if params is None:
        params = {}
    final_params = {**default_params, **params}
    
    # 转换为灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
    
    if final_params['direction'] == 'horizontal':
        sobel = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
    else:
        sobel = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
    
    # 只保留负值（从亮到暗的变化）
    negative_edges = np.where(sobel < 0, -sobel, 0)
    
    if final_params['normalize']:
        if negative_edges.max() > 0:
            negative_edges = np.uint8(negative_edges / negative_edges.max() * 255)
        else:
            negative_edges = np.uint8(negative_edges)
    else:
        negative_edges = np.uint8(np.clip(negative_edges, 0, 255))
    
    # 可选的二值化处理
    if final_params['apply_threshold']:
        _, negative_edges = cv2.threshold(negative_edges, final_params['threshold'], 
                                        final_params['max_value'], final_params['threshold_type'])
    
    return negative_edges

def save_image_chinese_path(image, file_path):
    """
    保存图像到中文路径
    
    Args:
        image: OpenCV图像数组
        file_path: 包含中文的文件路径
    
    Returns:
        bool: 是否保存成功
    """
    try:
        # 获取文件扩展名
        ext = file_path.lower().split('.')[-1]
        
        # 根据扩展名设置编码格式
        if ext in ['jpg', 'jpeg']:
            encode_param = [cv2.IMWRITE_JPEG_QUALITY, 95]
            success, encoded_img = cv2.imencode('.jpg', image, encode_param)
        elif ext == 'png':
            encode_param = [cv2.IMWRITE_PNG_COMPRESSION, 3]
            success, encoded_img = cv2.imencode('.png', image, encode_param)
        elif ext in ['bmp']:
            success, encoded_img = cv2.imencode('.bmp', image)
        elif ext in ['tif', 'tiff']:
            success, encoded_img = cv2.imencode('.tiff', image)
        else:
            # 默认使用jpg格式
            encode_param = [cv2.IMWRITE_JPEG_QUALITY, 95]
            success, encoded_img = cv2.imencode('.jpg', image, encode_param)
        
        if success:
            # 使用numpy的tofile方法支持中文路径
            encoded_img.tofile(file_path)
            return True
        else:
            return False
            
    except Exception as e:
        print(f"保存图像失败: {e}")
        return False
    
def error_message_show():
                        # 获取完整的错误堆栈信息
    error_traceback = traceback.format_exc()
                
    # 发送详细错误信息
    print("详细错误堆栈:")
            
            # 将多行堆栈信息分行发送
    for line in error_traceback.strip().split('\n'):
        print(f"  {line}")   

def xyxy_from_xywh(rect):
    x = rect[0]
    y = rect[1]
    width = rect[2]
    height = rect[3]
    x1 = x
    y1 = y
    x2 = x + width
    y2 = y + height  
    return (x1, y1, x2, y2)

def xywh_from_xyxy(rect):  
    x0 = rect[0]
    y0 = rect[1]
    x1 = rect[2]
    y1 = rect[3] 
    x = x0
    y = y0
    w = abs(x1-x0)
    h = abs(y1-y0)  
    return (x, y, w, h)

def xywh_from_aoi(aoi):
    """
    从AOI字典中获取xywh格式的坐标
    
    Args:
        aoi: AOI字典，包含'x', 'y', 'width', 'height'
        
    Returns:
        tuple: (x, y, width, height)
    """
    x = aoi['x']
    y = aoi['y']
    width = aoi['width']
    height = aoi['height']
    return (x, y, width, height)
    
def xyxy_from_aoi(aoi):
    return xyxy_from_xywh(xywh_from_aoi(aoi))

def vote_for_diff(dataList, diff=10, min_vote=200, max_vote=2000, vote_diff=50):
    """
    对一组数值进行投票，返回最常见的数值及其出现次数
    
    Args:
        dataList: 数值列表
        diff: 允许的数值差异范围，默认10
        
    Returns:
        (most_common_value, count): 最常见的数值及其出现次数
    """
    if not dataList:
        return None
    for data in dataList:
        break
        print(data)
        print('\n')
    vote_dict = {}
    start_value = dataList[0][0]
    start_dict =  dataList[0][1]

    end_value = dataList[-1][0]
    end_dict =  dataList[-1][1]   

    
    num_of_vote = int(abs(max_vote - min_vote) / diff + 1)
    #vote_list = []
    for i in range(num_of_vote):
            vote_dict[f"{i}"] = {
                'value': 0,
                'datas': [],
                'cur_vote' : i * diff + min_vote,
                'diff_collect' : {}
            }
    cur_max = 0
    cur_dict = {}
    for vote_param in vote_dict:
        old_inv = 0       
        for data in dataList:
            value = data[0]
            interDict = data[1]
            cur_dec = abs(value - start_value)
            cur_diff = vote_dict[vote_param]['cur_vote'] 

            cur_inv = int(float(cur_dec) / float(cur_diff) + 0.5)
            cur_str = str(cur_inv)
            cur_mod = abs((cur_dec) - (cur_inv * cur_diff))
            #print(data)
            #print("间隔 " + str(cur_diff) + " , 新投票点:" + str(cur_inv) + " 旧投票点:" + str(old_inv) + " 投票差值:" + str(cur_mod))
            if cur_inv - old_inv >= 2:
                break
            
            if cur_mod < vote_diff:
                old_inv = cur_inv
                if cur_str not in vote_dict[vote_param]['diff_collect']:
                    #vote_dict[vote_param]['diff_collect']['lines'] = []
                    vote_dict[vote_param]['diff_collect'][cur_str] = {}
                    vote_dict[vote_param]['diff_collect'][cur_str]['no'] = cur_inv
                    vote_dict[vote_param]['diff_collect'][cur_str]['lines'] = []
                    vote_dict[vote_param]['diff_collect'][cur_str]['lines'].append(interDict)
                else:
                    vote_dict[vote_param]['diff_collect'][cur_str]['no'] += cur_inv
                    vote_dict[vote_param]['diff_collect'][cur_str]['lines'].append(interDict)
                    #vote_dict[vote_param]['diff_collect']['lines'].append(interDict)
                
        if len(vote_dict[vote_param]['diff_collect']) > cur_max:
            cur_max = len(vote_dict[vote_param]['diff_collect'])
            sorted_list = sorted(vote_dict[vote_param]['diff_collect'].items(),key=lambda x: x[1]['no'])
            vote_dict[vote_param]['lineList'] = sorted_list
         
            cur_dict = vote_dict[vote_param]
        #print(vote_dict[vote_param]['cur_vote'])
        #sorted_list = sorted(vote_dict[vote_param]['diff_collect'].items(), key=lambda x: int(x[0]))
        #print(sorted_list)

    #sorted_items = sorted(vote_dict.items(), key=lambda item: item[1]['value'], reverse=True) 
    #print(cur_dict)
    return cur_dict

def vote_for_parallel(dataList, line_info0, arg_area = 2 , target_width=180, target_area = 10):
    """
    对一组线段进行投票，返回与参考线平行，且满足距离要求的线段组
    过滤重复使用的线段，保留最接近目标宽度的线段对

    Args:
        dataList: 线段列表，每个元素为 (x0, y0, x1, y1)
        line_info0: 参考线段信息
        arg_area: 允许的面积差异范围，默认2
        target_width: 目标宽度，默认180
        target_area: 目标宽度容差，默认10
        
    Returns:
        list: 最优的线段对列表，无重复使用的线段
    """
    # 检查是否是平行线
    result_lines = []
    dataList0 = []
    for line_info in dataList:
        line = line_info[1] 
        if calculate_lines_parallelism(line_info0[1], line, arg_area):
            line_info_temp = copy.copy(line_info)
            line_info_temp.append(10000000)
            line_info_temp.append([])
            line_info_temp.append(-1)
            dataList0.append(line_info_temp)
    
    # 开始推算满足要求的平行线段
    for mark_itr in range(len(dataList0)):
        mark_info = dataList0[mark_itr]
        min_diff = 10000000
        for cur_itr in range(mark_itr + 1, len(dataList0)):
            cur_info = dataList0[cur_itr]
            cur_width = abs(mark_info[0] - cur_info[0])
            cur_diff = abs(cur_width - target_width)
            
            if cur_diff < min_diff:
                if (cur_diff < cur_info[2]):             
                    min_diff = cur_diff
                    cur_info[2] = cur_diff                   
                    if cur_diff < target_area:
                        cur_info_temp = []
                        cur_info_temp.append(int(cur_diff))
                        cur_info_temp.append(mark_info[1])
                        cur_info[3].append(cur_info_temp)

    # 收集所有候选线段对及其差值
    candidate_pairs = []
    for mark_itr in range(len(dataList0)):
        mark_info = dataList0[mark_itr]
        if len(mark_info[3]) > 0:
            # 满足要求的线段对
            mark_info[3].sort(key=lambda x: x[0])
            line0 = mark_info[1]
            line1 = mark_info[3][0][1]
            diff_value = mark_info[3][0][0]
            
            candidate_pairs.append({
                'line_pair': [line0, line1],
                'diff': diff_value,
                'line0_key': tuple(line0),  # 用于识别线段的唯一标识
                'line1_key': tuple(line1)
            })
    
    # 按差值排序，优先选择最接近目标宽度的
    candidate_pairs.sort(key=lambda x: x['diff'])
    
    # 过滤重复使用的线段，保留最优的组合
    used_lines = set()
    filtered_pairs = []
    
    for pair_info in candidate_pairs:
        line0_key = pair_info['line0_key']
        line1_key = pair_info['line1_key']
        
        # 检查这两条线段是否已经被使用
        if line0_key not in used_lines and line1_key not in used_lines:
            filtered_pairs.append(pair_info['line_pair'])
            used_lines.add(line0_key)
            used_lines.add(line1_key)
            #print(f"选择线段对，宽度差值: {pair_info['diff']}")
            #print(pair_info['line_pair'])
    
    return filtered_pairs


def vote_for_parallel_func_filter(
    dataList, line_info0, arg_area = 2,
    target_width=180, target_area = 10,
    func = None, **func_params
):
    """
    对一组线段进行投票，返回与参考线平行，且满足距离要求的线段组

    Args:
        dataList: 线段列表，每个元素为 (x0, y0, x1, y1)
        line0: 第一条线段
        line1: 第二条线段
        arg_area: 允许的面积差异范围，默认2
        target_width: 目标宽度，默认200
        target_area: 目标宽度容差，默认2
        
    Returns:
        (most_common_line, count): 最常见的线段及其出现次数
    """
    # 检查是否是平行线
    result_lines = []
    dataList0 = []
    for line_info in dataList:
        line = line_info[1] 
        #print(line)
        #print(line_info0[1])
        if calculate_lines_parallelism(line_info0[1], line, arg_area):
            line_info_temp = copy.copy(line_info)
            line_info_temp.append(10000000)
            line_info_temp.append([])
            dataList0.append(line_info_temp)
    # 开始推算满足要求的平行线段
    #start_itr = 0
    #end_itr = len(dataList0) - 1
    for mark_itr in range(len(dataList0)):
        #print(dataList0[mark_itr])
        mark_info = dataList0[mark_itr]
        min_diff = 10000000
        for cur_itr in range(mark_itr + 1, len(dataList0)):
            cur_info = dataList0[cur_itr]
            cur_width = abs(mark_info[0] - cur_info[0])
            cur_diff = abs(cur_width - target_width)
            #print (f"目标宽度 {target_width} , 当前宽度 {cur_width} , 差值 {cur_diff}")
            if cur_diff < min_diff:
                if (cur_diff < cur_info[2]):             
                    min_diff = cur_diff
                    cur_info[2] = cur_diff
                    if cur_diff < target_area:
                        cur_info_temp = []
                        cur_info_temp.append(int(cur_diff))
                        cur_info_temp.append(mark_info[1])
                        cur_info[3].append(cur_info_temp)

    dataList0.sort(key=lambda x: x[2])
    for mark_itr in range(len(dataList0)):
        
        mark_info = dataList0[mark_itr]
        #print(mark_info[3])
        if len(mark_info[3]) > 0:
            # 满足要求的线段对
            mark_info[3].sort(key=lambda x: x[0])
            line0 = mark_info[1]
            line1 = mark_info[3][0][1]
            line_cope = [line0, line1]
            print (line_cope)
            result_lines.append(line_cope)



    return result_lines


def get_cv_img_fronm_aoi(image, aoi): 
    x0 = aoi['x']
    y0 = aoi['y']   
    x1 = x0 + aoi['width']
    y1 = y0 + aoi['height']
    aoi_image = image[y0:y1, x0:x1]
    return aoi_image

# ...existing code...
def get_cv_img_fronm_aoi_ex(image, aoi): 
    """
    从图像中根据AOI提取子图像，如果AOI超过图像边界则取最大重叠部分
    
    Args:
        image: 输入图像 (numpy.ndarray)
        aoi: AOI字典，包含'x', 'y', 'width', 'height'
        
    Returns:
        numpy.ndarray: 提取的子图像，如果没有重叠则返回None
    """
    if image is None or aoi is None:
        return None
    
    # 获取图像尺寸
    img_height, img_width = image.shape[:2]
    
    # 获取AOI坐标
    x0 = aoi['x']
    y0 = aoi['y']   
    x1 = x0 + aoi['width']
    y1 = y0 + aoi['height']
    
    # 计算重叠区域
    overlap_x0 = max(0, x0)  # AOI左边界与图像左边界的最大值
    overlap_y0 = max(0, y0)  # AOI上边界与图像上边界的最大值
    overlap_x1 = min(img_width, x1)   # AOI右边界与图像右边界的最小值
    overlap_y1 = min(img_height, y1)  # AOI下边界与图像下边界的最小值
    
    # 检查是否有有效的重叠区域
    if overlap_x1 <= overlap_x0 or overlap_y1 <= overlap_y0:
        return None  # 没有重叠区域
    
    # 提取重叠区域
    aoi_image = image[overlap_y0:overlap_y1, overlap_x0:overlap_x1]
    return aoi_image
# ...existing code...

def get_aoi_xywh(x, y, width, height):
    aoi = {}
    aoi['x'] = x
    aoi['y'] = y
    aoi['width'] = width
    aoi['height'] = height
    return aoi

def C_Find_Line_Who_Strong(    
    image, rho=1,
    threshold=30, minLineLength=50, maxLineGap=5, 
    threshold_diff=5, minLineLength_diff=5, maxLineGap_diff=1,
    try_times=5, dir_type='left',return_dict={}
):
    cur_threshold = threshold  
    cur_minLineLength = minLineLength
    cur_maxLineGap = maxLineGap

    # 为图像添加边框，解决边缘线段检测问题
    padding = 5  # 边框宽度
    if len(image.shape) == 3:
        padded_image = cv2.copyMakeBorder(image, padding, padding, padding, padding, 
                                        cv2.BORDER_CONSTANT, value=(0, 0, 0))
    else:
        padded_image = cv2.copyMakeBorder(image, padding, padding, padding, padding, 
                                        cv2.BORDER_CONSTANT, value=0)

    return_dict['尝试信息'] = []
    for try_index in range(try_times):
        if cur_threshold < 3 or cur_minLineLength < 3:
            return_dict['尝试信息'].append("线段尝试参数过低，停止尝试")
            break
        return_dict['尝试信息'].append(f"开始尝试：当前参数 : threshold={cur_threshold}, minLineLength={cur_minLineLength}, maxLineGap={cur_maxLineGap}")
        
        cur_line = None
        cur_threshold = int(cur_threshold)
        cur_minLineLength = int(cur_minLineLength)
        cur_maxLineGap = int(cur_maxLineGap)
        
        if dir_type == 'left':
            cur_line = C_HoughLinesP_find_vertical(
                padded_image, rho, cur_threshold, cur_minLineLength, cur_maxLineGap, dirtype='left'
            )
        elif dir_type == 'right':
            cur_line = C_HoughLinesP_find_vertical(
                padded_image, rho, cur_threshold, cur_minLineLength, cur_maxLineGap, dirtype='right'
            )
        elif dir_type == 'up':
            cur_line = C_HoughLinesP_find_horizontal(
                padded_image, rho, cur_threshold, cur_minLineLength, cur_maxLineGap, dirtype='up'
            )
        elif dir_type == 'down':    
            cur_line = C_HoughLinesP_find_horizontal(
                padded_image, rho, cur_threshold, cur_minLineLength, cur_maxLineGap, dirtype='down'
            )
        else:
            cur_line = C_HoughLinesP_find_vertical(
                padded_image, rho, cur_threshold, cur_minLineLength, cur_maxLineGap, dirtype='left'
            )
            
        if cur_line is None:
            cur_threshold -= threshold_diff
            cur_minLineLength -= minLineLength_diff
            cur_maxLineGap += maxLineGap_diff
            return_dict['尝试信息'].append("未找到线段，降低线段检测参数")
        else:
            # 校正坐标：减去边框偏移
            if isinstance(cur_line, list):
                # 检查是否为多条线段（dirtype='all'时）
                if len(cur_line) > 0 and isinstance(cur_line[0], (list, tuple, np.ndarray)) and len(cur_line[0]) == 4:
                    # 多条线段的情况
                    corrected_lines = []
                    for line in cur_line:
                        x1, y1, x2, y2 = line
                        corrected_lines.append([x1-padding, y1-padding, x2-padding, y2-padding])
                    return corrected_lines
                elif len(cur_line) == 4:
                    # 单条线段的情况：[x1, y1, x2, y2]
                    x1, y1, x2, y2 = cur_line
                    return [x1-padding, y1-padding, x2-padding, y2-padding]
            return cur_line
    return None
        
def C_Find_Height_Auto_Like(    
    image, rho=1, height=100, area=100, min_area=20,
    threshold=30, minLineLength=50, maxLineGap=5, 
    threshold_diff=5, minLineLength_diff=5, maxLineGap_diff=1,
    try_times=5, return_dict={}
):
    up_threshold = threshold
    down_threshold = threshold      
    up_minLineLength = minLineLength
    down_minLineLength = minLineLength
    up_maxLineGap = maxLineGap
    down_maxLineGap = maxLineGap

    cur_diff = 10000000
    min_Diff = 10000000
    cur_lines = []
    info_lines = []

    return_dict['尝试信息'] = []
    for try_index in range(try_times):
        if up_threshold < 1 or up_minLineLength < 1  or up_minLineLength < 1:
            return_dict['尝试信息'].append("上边沿检测检测参数过低，停止尝试")
            break
        if down_threshold < 1 or down_minLineLength < 1  or down_maxLineGap < 1:
            return_dict['尝试信息'].append("下边沿线段检测参数过低，停止尝试")
            break
        return_dict['尝试信息'].append(f"开始尝试：上边沿当前参数 : threshold={up_threshold}, minLineLength={up_minLineLength}, maxLineGap={up_maxLineGap}")
        return_dict['尝试信息'].append(f"开始尝试：下边沿当前参数 : threshold={down_threshold}, minLineLength={down_minLineLength}, maxLineGap={down_maxLineGap}")
        up_line = C_HoughLinesP_find_horizontal(
            image, rho=1, threshold=int(up_threshold), 
            minLineLength=int(up_minLineLength), 
            maxLineGap=int(up_maxLineGap), dirtype='up'
        )
        down_line = C_HoughLinesP_find_horizontal(
            image, rho=1, threshold=int(down_threshold), 
            minLineLength=int(down_minLineLength), 
            maxLineGap=int(down_maxLineGap), dirtype='down'
        )
        if up_line is None:
            up_threshold -= threshold_diff
            up_minLineLength -= minLineLength_diff
            up_maxLineGap += maxLineGap_diff
            return_dict['尝试信息'].append("未找到上边沿线段，降低上边沿线段检测参数")
        if down_line is None:
            down_threshold -= threshold_diff
            down_minLineLength -= minLineLength_diff
            down_maxLineGap += maxLineGap_diff
            return_dict['尝试信息'].append("未找到下边沿线段，降低下边沿线段检测参数")
        if up_line is None or down_line is None:
            continue
        cur_height = abs(down_line[0][1] - up_line[0][1])
        if cur_height < height:
            return_dict['尝试信息'].append(f"当前高度 {cur_height} 小于目标高度 {height}±{area},可能是重要线段被忽略,继续尝试")
            up_threshold -= threshold_diff / 2
            up_minLineLength -= minLineLength_diff / 2
            up_maxLineGap += maxLineGap_diff
            down_threshold -= threshold_diff / 2 
            down_minLineLength -= minLineLength_diff / 2 
            down_maxLineGap += maxLineGap_diff 
        elif cur_height > height:
            return_dict['尝试信息'].append(f"当前高度 {cur_height} 大于目标高度 {height}±{area},可能是干扰线段被记录,继续尝试")
            up_threshold += threshold_diff / 2 
            up_minLineLength += minLineLength_diff / 2 
            up_maxLineGap -= maxLineGap_diff / 2 
            down_threshold += threshold_diff
            down_minLineLength += minLineLength_diff / 2 
            down_maxLineGap += maxLineGap_diff
        if abs(cur_height - height) <= area:
            return_dict['尝试信息'].append(f"成功找到上边沿线段 {up_line} 和下边沿线段 {down_line},高度 {cur_height} 符合目标高度 {height}±{area}")
            return_dict['尝试信息'].append(f"上边沿最终参数 : threshold={up_threshold}, minLineLength={up_minLineLength}, maxLineGap={up_maxLineGap}")
            return_dict['尝试信息'].append(f"下边沿最终参数 : threshold={down_threshold}, minLineLength={down_minLineLength}, maxLineGap={down_maxLineGap}")
            return_dict['尝试次数'] = try_index + 1
            #return_dict['结果'] = '成功'
            cur_diff = abs(cur_height - height)
            if (cur_diff < min_Diff):
                min_Diff = cur_diff
                cur_lines = []
                info_lines = []
                cur_lines = [up_line, down_line]
                info_lines.append(f"上边沿最终参数 : threshold={up_threshold}, minLineLength={up_minLineLength}, maxLineGap={up_maxLineGap}")
                info_lines.append(f"下边沿最终参数 : threshold={down_threshold}, minLineLength={down_minLineLength}, maxLineGap={down_maxLineGap}")
                if cur_diff < min_area:
                    break
    return_dict['尝试次数'] = try_times
    if len(cur_lines) == 2:
        return_dict['结果'] = '成功'
        for info in info_lines:
            return_dict['尝试信息'].append(info)
        return cur_lines
    else:
        return_dict['结果'] = '失败'    
        return_dict['尝试信息'].append("所有尝试均未成功找到符合要求的线段")
        line_list = []
        return line_list

def C_Find_Width_Auto_Like(    
    image, rho=1, width=100, area=30, min_area=20,
    threshold=30, minLineLength=50, maxLineGap=5, 
    threshold_diff=5, minLineLength_diff=5, maxLineGap_diff=1,
    try_times=5, return_dict={}
):
    """
    自动查找符合指定宽度的左右边沿线段
    通过旋转图像90度调用C_Find_Height_Auto_Like来实现
    
    Args:
        image: 输入图像
        rho: 距离分辨率，默认1
        width: 目标宽度，默认100
        area: 宽度容差，默认30
        min_area: 最小容差，默认20
        threshold: 初始累加器阈值，默认30
        minLineLength: 初始最小线段长度，默认50
        maxLineGap: 初始最大线段间隙，默认5
        threshold_diff: 阈值递减步长，默认5
        minLineLength_diff: 最小线段长度递减步长，默认5
        maxLineGap_diff: 最大间隙递增步长，默认1
        try_times: 最大尝试次数，默认5
        return_dict: 返回详细信息的字典
        
    Returns:
        list: 包含左右边沿线段的列表 [left_line, right_line]，失败返回空列表
    """
    # 将图像逆时针旋转90度（使垂直线变成水平线）
    rotated_image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
    
    # 创建内部return_dict来接收C_Find_Height_Auto_Like的信息
    internal_return_dict = {}
    
    # 调用C_Find_Height_Auto_Like来查找"高度"（实际上是原图的宽度）
    height_lines = C_Find_Height_Auto_Like(
        rotated_image, 
        rho=rho, 
        height=width,  # 原图的宽度对应旋转后图像的高度
        area=area, 
        min_area=min_area,
        threshold=threshold, 
        minLineLength=minLineLength, 
        maxLineGap=maxLineGap,
        threshold_diff=threshold_diff, 
        minLineLength_diff=minLineLength_diff, 
        maxLineGap_diff=maxLineGap_diff,
        try_times=try_times, 
        return_dict=internal_return_dict
    )
    
    # 处理返回的信息，将"上下边沿"转换为"左右边沿"的描述
    if '尝试信息' in internal_return_dict:
        return_dict['尝试信息'] = []
        for info in internal_return_dict['尝试信息']:
            # 替换描述中的方向词汇
            converted_info = info.replace('上边沿', '左边沿').replace('下边沿', '右边沿')
            converted_info = converted_info.replace('高度', '宽度')
            return_dict['尝试信息'].append(converted_info)
    
    if '尝试次数' in internal_return_dict:
        return_dict['尝试次数'] = internal_return_dict['尝试次数']
    
    if '结果' in internal_return_dict:
        return_dict['结果'] = internal_return_dict['结果']
    
    # 如果没有找到线段，直接返回空列表
    if not height_lines:
        return []
    
    # 将旋转后图像中的线段坐标转换回原图坐标
    original_lines = []
    original_height, original_width = image.shape[:2]
    rotated_height, rotated_width = rotated_image.shape[:2]
    
    # height_lines是一个线段列表的列表，每个线段列表里只有一个线段
    for line_group in height_lines:
        # line_group是一个包含一个线段的列表，格式为 [[x1, y1, x2, y2]]
        line = line_group[0]  # 取出唯一的线段
        x1, y1, x2, y2 = line
        
        # 逆时针旋转90度的逆变换：将旋转后的坐标转换回原坐标
        # 逆时针旋转90度：(x', y') = (-y, x)
        # 逆变换（顺时针旋转90度）：(x, y) = (y', -x')
        # 但需要考虑坐标系偏移
        original_x1 = y1
        original_y1 = rotated_width - 1 - x1
        original_x2 = y2
        original_y2 = rotated_width - 1 - x2
        
        # 保持与原函数相同的返回格式：线段列表的列表
        original_lines.append([[original_x1, original_y1, original_x2, original_y2]])
    
    return original_lines

def C_Find_Width_Auto_Like0(    
    image, rho=1, width=100, area=30,
    threshold=30, minLineLength=50, maxLineGap=5, 
    threshold_diff=5, minLineLength_diff=5, maxLineGap_diff=1,
    try_times=5, return_dict={}
):
    right_threshold = threshold
    left_threshold = threshold      
    right_minLineLength = minLineLength
    left_minLineLength = minLineLength
    right_maxLineGap = maxLineGap
    left_maxLineGap = maxLineGap
    return_dict['尝试信息'] = []
    for try_index in range(try_times):
        if right_threshold < 1 or right_minLineLength < 1  or right_minLineLength < 1:
            return_dict['尝试信息'].append("右边沿检测检测参数过低，停止尝试")
            break
        if left_threshold < 1 or left_minLineLength < 1  or left_maxLineGap < 1:
            return_dict['尝试信息'].append("左边沿线段检测参数过低，停止尝试")
            break
        return_dict['尝试信息'].append(f"开始尝试：右边沿当前参数 : threshold={right_threshold}, minLineLength={right_minLineLength}, maxLineGap={right_maxLineGap}")
        return_dict['尝试信息'].append(f"开始尝试：左边沿当前参数 : threshold={left_threshold}, minLineLength={left_minLineLength}, maxLineGap={left_maxLineGap}")
        right_line = C_HoughLinesP_find_vertical(
            image, rho=1, threshold=int(right_threshold), 
            minLineLength=int(right_minLineLength), 
            maxLineGap=int(right_maxLineGap), dirtype='right'
        )
        left_line = C_HoughLinesP_find_vertical(
            image, rho=1, threshold=int(left_threshold), 
            minLineLength=int(left_minLineLength), 
            maxLineGap=int(left_maxLineGap), dirtype='left'
        )
        if right_line is None:
            right_threshold -= threshold_diff
            right_minLineLength -= minLineLength_diff
            right_maxLineGap += maxLineGap_diff
            return_dict['尝试信息'].append("未找到上边沿线段，降低上边沿线段检测参数")
        if left_line is None:
            left_threshold -= threshold_diff
            left_minLineLength -= minLineLength_diff
            left_maxLineGap += maxLineGap_diff
            return_dict['尝试信息'].append("未找到下边沿线段，降低下边沿线段检测参数")
        if right_line is None or left_line is None:
            continue
        cur_width = abs(right_line[0][0] - left_line[0][0])
        if cur_width < width - area:
            return_dict['尝试信息'].append(f"当前宽度 {cur_width} 小于目标宽度 {width}±{area},可能是重要线段被忽略,继续尝试")
            right_threshold -= threshold_diff / 2
            right_minLineLength -= minLineLength_diff / 2
            right_maxLineGap += maxLineGap_diff 
            left_threshold -= threshold_diff / 2 
            left_minLineLength -= minLineLength_diff / 2 
            left_maxLineGap += maxLineGap_diff / 2 
        elif cur_width > width + area:
            return_dict['尝试信息'].append(f"当前宽度 {cur_width} 大于目标宽度 {width}±{area},可能是干扰线段被记录,继续尝试")
            right_threshold += threshold_diff / 2 
            right_minLineLength += minLineLength_diff / 2 
            right_maxLineGap -= maxLineGap_diff / 2 
            left_threshold += threshold_diff
            left_minLineLength += minLineLength_diff / 2 
            left_maxLineGap -= maxLineGap_diff / 2
        else:
            return_dict['尝试信息'].append(f"成功找到右边沿线段 {right_line} 和左边沿线段 {left_line},宽度 {cur_width} 符合目标宽度 {width}±{area}")
            return_dict['尝试信息'].append(f"右边沿最终参数 : threshold={right_threshold}, minLineLength={right_minLineLength}, maxLineGap={right_maxLineGap}")
            return_dict['尝试信息'].append(f"左边沿最终参数 : threshold={left_threshold}, minLineLength={left_minLineLength}, maxLineGap={left_maxLineGap}")
            return_dict['尝试次数'] = try_index + 1
            return_dict['结果'] = '成功'
            line_list = []
            line_list.append(right_line)   
            line_list.append(left_line)
            return line_list
    return_dict['尝试信息'].append(f"右边沿最终参数 : threshold={right_threshold}, minLineLength={right_minLineLength}, maxLineGap={right_maxLineGap}")
    return_dict['尝试信息'].append(f"左边沿最终参数 : threshold={left_threshold}, minLineLength={left_minLineLength}, maxLineGap={left_maxLineGap}")
    return_dict['尝试次数'] = try_times
    return_dict['结果'] = '失败'    
    return_dict['尝试信息'].append("所有尝试均未成功找到符合要求的线段")
    line_list = []
    return line_list
def C_HoughLinesP_find_vertical_auto_feed(
    image , rho = 1, 
    threshold = 30, 
    minLineLength = 50, 
    maxLineGap = 5, 
    dirtype = 'left',
    threshold_diff = 5,
    minLineLength_diff = 5,
    maxLineGap_diff = 1,
    try_times = 3,
    return_dict = {}
):
    for try_index in range(try_times):
        lines = C_HoughLinesP_find_vertical(
            image, 
            rho=rho, 
            threshold=threshold - try_index * threshold_diff, 
            minLineLength=minLineLength - try_index * minLineLength_diff, 
            maxLineGap=maxLineGap - try_index * maxLineGap_diff, 
            dirtype=dirtype
        )
        
        if lines is not None:
            return_dict['尝试次数'] = try_index + 1
            return_dict['threshold'] = threshold - try_index * threshold_diff
            return_dict['minLineLength'] = minLineLength - try_index * minLineLength_diff
            return_dict['maxLineGap'] = maxLineGap + try_index * maxLineGap_diff
            return_dict['结果'] = '成功'
            return lines
    return_dict['尝试次数'] = try_times
    return_dict['结果'] = '失败'
    return None

def C_HoughLinesP_find_horizontal_auto_feed(
    image , rho = 1, 
    threshold = 30, 
    minLineLength = 50, 
    maxLineGap = 5, 
    dirtype = 'up',
    threshold_diff = 5,
    minLineLength_diff = 5,
    maxLineGap_diff = 1,
    try_times = 3,
    return_dict = {}
):
    for try_index in range(try_times):
        lines = C_HoughLinesP_find_horizontal(
            image, 
            rho=rho, 
            threshold=threshold - try_index * threshold_diff, 
            minLineLength=minLineLength - try_index * minLineLength_diff, 
            maxLineGap=maxLineGap - try_index * maxLineGap_diff, 
            dirtype=dirtype
        )
        
        if lines is not None:
            return_dict['尝试次数'] = try_index + 1
            return_dict['threshold'] = threshold - try_index * threshold_diff
            return_dict['minLineLength'] = minLineLength - try_index * minLineLength_diff
            return_dict['maxLineGap'] = maxLineGap + try_index * maxLineGap_diff
            return_dict['结果'] = '成功'
            return lines

    return_dict['尝试次数'] = try_times
    return_dict['结果'] = '失败'
    return None

def median_resize(image, target_size):
    """使用中值的方式进行 resize"""
    h, w = image.shape[:2]
    target_w, target_h = target_size
    
    # 计算缩放比例
    scale_x = w / target_w
    scale_y = h / target_h
    
    if len(image.shape) == 3:
        result = np.zeros((target_h, target_w, image.shape[2]), dtype=image.dtype)
        
        for i in range(target_h):
            for j in range(target_w):
                # 计算原图中对应的区域
                start_y = int(i * scale_y)
                end_y = int((i + 1) * scale_y)
                start_x = int(j * scale_x)
                end_x = int((j + 1) * scale_x)
                
                # 取区域中值
                for c in range(image.shape[2]):
                    region = image[start_y:end_y, start_x:end_x, c]
                    result[i, j, c] = np.median(region)
    else:
        result = np.zeros((target_h, target_w), dtype=image.dtype)
        
        for i in range(target_h):
            for j in range(target_w):
                start_y = int(i * scale_y)
                end_y = int((i + 1) * scale_y)
                start_x = int(j * scale_x)
                end_x = int((j + 1) * scale_x)
                
                region = image[start_y:end_y, start_x:end_x]
                result[i, j] = np.median(region)
    
    return result

def check_resize(image, target_size, start_pnt, end_pnt):
    """使用排序后指定范围平均值的方式进行 resize"""
    h, w = image.shape[:2]
    target_w, target_h = target_size
    
    # 计算缩放比例
    scale_x = w / target_w
    scale_y = h / target_h
    
    if len(image.shape) == 3:
        result = np.zeros((target_h, target_w, image.shape[2]), dtype=image.dtype)
        
        for i in range(target_h):
            for j in range(target_w):
                # 计算原图中对应的区域
                start_y = int(i * scale_y)
                end_y = int((i + 1) * scale_y)
                start_x = int(j * scale_x)
                end_x = int((j + 1) * scale_x)
                
                # 处理每个通道
                for c in range(image.shape[2]):
                    region = image[start_y:end_y, start_x:end_x, c]
                    # 将区域展平并排序
                    sorted_region = np.sort(region.flatten())
                    region_len = len(sorted_region)
                    
                    # 参数检测和自动范围判定
                    if region_len == 0:
                        result[i, j, c] = 0
                        continue
                    
                    # 处理负数索引
                    if start_pnt < 0:
                        start_idx = max(0, region_len + start_pnt)
                    else:
                        start_idx = min(start_pnt, region_len - 1)
                    
                    if end_pnt < 0:
                        end_idx = max(0, region_len + end_pnt)
                    else:
                        end_idx = min(end_pnt, region_len - 1)
                    
                    # 确保start_idx <= end_idx
                    if start_idx > end_idx:
                        start_idx, end_idx = end_idx, start_idx
                    
                    # 计算指定范围的平均值
                    selected_values = sorted_region[start_idx:end_idx+1]
                    result[i, j, c] = np.mean(selected_values)
    else:
        result = np.zeros((target_h, target_w), dtype=image.dtype)
        
        for i in range(target_h):
            for j in range(target_w):
                start_y = int(i * scale_y)
                end_y = int((i + 1) * scale_y)
                start_x = int(j * scale_x)
                end_x = int((j + 1) * scale_x)
                
                region = image[start_y:end_y, start_x:end_x]
                # 将区域展平并排序
                sorted_region = np.sort(region.flatten())
                region_len = len(sorted_region)
                
                # 参数检测和自动范围判定
                if region_len == 0:
                    result[i, j] = 0
                    continue
                
                # 处理负数索引
                if start_pnt < 0:
                    start_idx = max(0, region_len + start_pnt)
                else:
                    start_idx = min(start_pnt, region_len - 1)
                
                if end_pnt < 0:
                    end_idx = max(0, region_len + end_pnt)
                else:
                    end_idx = min(end_pnt, region_len - 1)
                
                # 确保start_idx <= end_idx
                if start_idx > end_idx:
                    start_idx, end_idx = end_idx, start_idx
                
                # 计算指定范围的平均值
                selected_values = sorted_region[start_idx:end_idx+1]
                result[i, j] = np.mean(selected_values)
    
    return result

def C_HoughLinesP_find_vertical(
    image , rho = 1, 
    threshold = 30, 
    minLineLength = 50, 
    maxLineGap = 5, 
    dirtype = 'left'
):
    """
    使用Hough变换检测垂直线段
    
    Args:
        image: 输入图像
        rho: 距离分辨率
        theta: 角度分辨率
        threshold: 累加器阈值
        minLineLength: 最小线段长度
        maxLineGap: 最大线段间隙
        
    Returns:
        lines: 检测到的线段列表
    """
    lines = cv2.HoughLinesP(image, rho, theta=np.pi/2, threshold=threshold, minLineLength=minLineLength, maxLineGap=maxLineGap)
    #print(lines[0])
    #print(f"检测到 {len(lines)} 条垂直线段")
    lines = filter_vertical_lines(lines, angle_tolerance=10)
    if lines is None:
        return None
    lines, center_x_list = sort_lines_by_center_x(lines)
    if lines is None:
        return None  
    #print(f"检测到 {len(lines)} 条垂直线段")
    #for i in range(len(lines)):
        #print(lines[i])
    if lines is None:
        return None  
    line = None
    if dirtype == 'left':
        # 只保留左侧线段
        line = lines[0]
    elif dirtype == 'right':
        # 只保留右侧线段
        line = lines[-1]
    elif dirtype == 'all':
        line = lines
    else:
        line = lines[0]
    #print(f"检测到 {len(lines)} 条垂直线段")
    #print(lines[0])
    return line

def C_HoughLinesP_find_horizontal(
    image , rho = 1, 
    threshold = 30, 
    minLineLength = 50, 
    maxLineGap = 5, 
    dirtype = 'up'
):
    """
    使用Hough变换检测垂直线段
    
    Args:
        image: 输入图像
        rho: 距离分辨率
        theta: 角度分辨率
        threshold: 累加器阈值
        minLineLength: 最小线段长度
        maxLineGap: 最大线段间隙
        
    Returns:
        lines: 检测到的线段列表
    """
    lines = cv2.HoughLinesP(image, rho, theta=np.pi / 2, threshold=threshold, minLineLength=minLineLength, maxLineGap=maxLineGap)
    #print(lines[0])
    #print(f"检测到 {len(lines)} 条平行线段")
    lines = filter_horizontal_lines(lines, angle_tolerance=10)
    if lines is None:
        return None
    lines, center_x_list = sort_lines_by_center_y(lines)
    if lines is None:
        return None  
    #print(lines[0])
    #print(lines[0])
    if lines is None:
        return None  
    line = None
    if dirtype == 'up':
        # 只保留左侧线段
        line = lines[0]
    elif dirtype == 'down':
        # 只保留右侧线段
        line = lines[-1]
    elif dirtype == 'all':
        line = lines
    else:
        line = lines[0]
    #print(f"检测到 {len(lines)} 条垂直线段")
    #print(lines[0])
    return line

#right_lines = cv2.HoughLinesP(right_image, rho=1, theta=np.pi/2, threshold=30, minLineLength=50, maxLineGap=5)


def filter_lines_by_angle(lines, target_angle=90, angle_tolerance=10, return_angles=False):
    """
    根据角度筛选CV直线组
    
    Args:
        lines: cv2.HoughLinesP 返回的直线数组 [[[x1, y1, x2, y2]], ...]
        target_angle: 目标角度（度），默认90度（垂直线）
        angle_tolerance: 角度容差（度），默认±10度
        return_angles: 是否同时返回每条线的角度信息，默认False
        
    Returns:
        filtered_lines: 筛选后的直线数组
        line_angles: 如果return_angles=True，则返回(filtered_lines, angles)元组
    """
    if lines is None or len(lines) == 0:
        if return_angles:
            return None, []
        return None
    
    def calculate_line_angle(x1, y1, x2, y2):
        """计算直线与水平线的夹角（度）"""
        if x2 == x1:  # 垂直线
            return 90.0
        
        # 计算斜率和角度
        slope = (y2 - y1) / (x2 - x1)
        angle_rad = np.arctan(slope)
        angle_deg = np.degrees(angle_rad)
        
        # 将角度标准化到 [0, 180) 范围
        if angle_deg < 0:
            angle_deg += 180
            
        return angle_deg
    
    filtered_lines = []
    line_angles = []
    
    for line in lines:
        x1, y1, x2, y2 = line[0]
        angle = calculate_line_angle(x1, y1, x2, y2)
        
        # 检查角度是否在容差范围内
        angle_diff = abs(angle - target_angle)
        
        # 处理角度差超过90度的情况（例如：5度和175度实际很接近）
        if angle_diff > 90:
            angle_diff = 180 - angle_diff
        
        if angle_diff <= angle_tolerance:
            filtered_lines.append(line)
            line_angles.append(angle)
    
    filtered_lines = np.array(filtered_lines) if filtered_lines else None
    
    if return_angles:
        return filtered_lines, line_angles
    return filtered_lines


def filter_vertical_lines(lines, angle_tolerance=10):
    """
    筛选垂直线（90度左右）
    
    Args:
        lines: cv2.HoughLinesP 返回的直线数组
        angle_tolerance: 角度容差（度），默认±10度
        
    Returns:
        vertical_lines: 垂直线数组
    """
    return filter_lines_by_angle(lines, target_angle=90, angle_tolerance=angle_tolerance)


def filter_horizontal_lines(lines, angle_tolerance=10):
    """
    筛选水平线（0度或180度左右）
    
    Args:
        lines: cv2.HoughLinesP 返回的直线数组
        angle_tolerance: 角度容差（度），默认±10度
        
    Returns:
        horizontal_lines: 水平线数组
    """
    return filter_lines_by_angle(lines, target_angle=0, angle_tolerance=angle_tolerance)


def filter_diagonal_lines(lines, target_angle=45, angle_tolerance=15):
    """
    筛选对角线（45度或135度左右）
    
    Args:
        lines: cv2.HoughLinesP 返回的直线数组
        target_angle: 目标角度（45或135度）
        angle_tolerance: 角度容差（度），默认±15度
        
    Returns:
        diagonal_lines: 对角线数组
    """
    return filter_lines_by_angle(lines, target_angle=target_angle, angle_tolerance=angle_tolerance)


def classify_lines_by_angle(lines, vertical_tolerance=10, horizontal_tolerance=10):
    """
    将直线按角度分类为垂直线、水平线和其他线
    
    Args:
        lines: cv2.HoughLinesP 返回的直线数组
        vertical_tolerance: 垂直线角度容差
        horizontal_tolerance: 水平线角度容差
        
    Returns:
        dict: 包含分类结果的字典
        {
            'vertical': 垂直线数组,
            'horizontal': 水平线数组,
            'others': 其他角度线数组,
            'angles': 所有线的角度列表
        }
    """
    if lines is None or len(lines) == 0:
        return {
            'vertical': None,
            'horizontal': None,
            'others': None,
            'angles': []
        }
    
    def calculate_line_angle(x1, y1, x2, y2):
        if x2 == x1:
            return 90.0
        slope = (y2 - y1) / (x2 - x1)
        angle_rad = np.arctan(slope)
        angle_deg = np.degrees(angle_rad)
        if angle_deg < 0:
            angle_deg += 180
        return angle_deg
    
    vertical_lines = []
    horizontal_lines = []
    other_lines = []
    all_angles = []
    
    for line in lines:
        x1, y1, x2, y2 = line[0]
        angle = calculate_line_angle(x1, y1, x2, y2)
        all_angles.append(angle)
        
        # 检查是否为垂直线
        if abs(angle - 90) <= vertical_tolerance:
            vertical_lines.append(line)
        # 检查是否为水平线
        elif angle <= horizontal_tolerance or angle >= (180 - horizontal_tolerance):
            horizontal_lines.append(line)
        else:
            other_lines.append(line)
    
    return {
        'vertical': np.array(vertical_lines) if vertical_lines else None,
        'horizontal': np.array(horizontal_lines) if horizontal_lines else None,
        'others': np.array(other_lines) if other_lines else None,
        'angles': all_angles
    }


def filter_lines_by_length(lines, min_length=50, max_length=None):
    """
    根据线段长度筛选直线
    
    Args:
        lines: cv2.HoughLinesP 返回的直线数组
        min_length: 最小长度
        max_length: 最大长度，None表示不限制
        
    Returns:
        filtered_lines: 筛选后的直线数组
    """
    if lines is None or len(lines) == 0:
        return None
    
    filtered_lines = []
    
    for line in lines:
        x1, y1, x2, y2 = line[0]
        length = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)
        
        if length >= min_length and (max_length is None or length <= max_length):
            filtered_lines.append(line)
    
    return np.array(filtered_lines) if filtered_lines else None


def advanced_line_filter(lines, angle_ranges=None, min_length=50, max_length=None, return_details=False):
    """
    高级直线筛选函数，支持多个角度范围和长度筛选
    
    Args:
        lines: cv2.HoughLinesP 返回的直线数组
        angle_ranges: 角度范围列表，例如 [(85, 95), (175, 185)] 或 [(85, 95), (-5, 5)]
        min_length: 最小长度
        max_length: 最大长度
        return_details: 是否返回详细信息
        
    Returns:
        filtered_lines: 筛选后的直线数组
        details: 如果return_details=True，返回详细信息字典
    """
    if lines is None or len(lines) == 0:
        if return_details:
            return None, {'total': 0, 'filtered': 0, 'angles': [], 'lengths': []}
        return None
    
    def calculate_line_angle(x1, y1, x2, y2):
        if x2 == x1:
            return 90.0
        slope = (y2 - y1) / (x2 - x1)
        angle_rad = np.arctan(slope)
        angle_deg = np.degrees(angle_rad)
        if angle_deg < 0:
            angle_deg += 180
        return angle_deg
    
    def calculate_line_length(x1, y1, x2, y2):
        return np.sqrt((x2 - x1)**2 + (y2 - y1)**2)
    
    filtered_lines = []
    all_angles = []
    all_lengths = []
    filtered_angles = []
    filtered_lengths = []
    
    for line in lines:
        x1, y1, x2, y2 = line[0]
        angle = calculate_line_angle(x1, y1, x2, y2)
        length = calculate_line_length(x1, y1, x2, y2)
        
        all_angles.append(angle)
        all_lengths.append(length)
        
        # 检查角度范围
        angle_match = True
        if angle_ranges:
            angle_match = False
            for min_angle, max_angle in angle_ranges:
                # 处理跨越0度的情况
                if min_angle < 0:
                    min_angle += 180
                if max_angle > 180:
                    max_angle -= 180
                
                if min_angle <= max_angle:
                    if min_angle <= angle <= max_angle:
                        angle_match = True
                        break
                else:  # 跨越180度边界
                    if angle >= min_angle or angle <= max_angle:
                        angle_match = True
                        break
        
        # 检查长度范围
        length_match = (length >= min_length and 
                       (max_length is None or length <= max_length))
        
        if angle_match and length_match:
            filtered_lines.append(line)
            filtered_angles.append(angle)
            filtered_lengths.append(length)
    
    result_lines = np.array(filtered_lines) if filtered_lines else None
    
    if return_details:
        details = {
            'total': len(lines),
            'filtered': len(filtered_lines),
            'all_angles': all_angles,
            'all_lengths': all_lengths,
            'filtered_angles': filtered_angles,
            'filtered_lengths': filtered_lengths
        }
        return result_lines, details
    
    return result_lines

def sort_lines_by_center_x(lines, ascending=True):
    """
    按照直线中心点的X坐标排序
    
    Args:
        lines: cv2.HoughLinesP 返回的直线数组 [[[x1, y1, x2, y2]], ...]
        ascending: 是否升序排列，True为升序，False为降序
        
    Returns:
        sorted_lines: 按X坐标排序后的直线数组
        center_x_list: 对应的中心点X坐标列表
    """
    if lines is None or len(lines) == 0:
        return None, []
    
    # 计算每条线的中心点X坐标
    lines_with_center_x = []
    for line in lines:
        x1, y1, x2, y2 = line[0]
        center_x = (x1 + x2) / 2.0
        lines_with_center_x.append((line, center_x))
    
    # 按中心点X坐标排序
    lines_with_center_x.sort(key=lambda item: item[1], reverse=not ascending)
    
    # 分离排序后的线段和中心点坐标
    sorted_lines = [item[0] for item in lines_with_center_x]
    center_x_list = [item[1] for item in lines_with_center_x]
    
    return np.array(sorted_lines), center_x_list

def sort_lines_by_center_y(lines, ascending=True):
    """
    按照直线中心点的Y坐标排序
    
    Args:
        lines: cv2.HoughLinesP 返回的直线数组 [[[x1, y1, x2, y2]], ...]
        ascending: 是否升序排列，True为升序，False为降序
        
    Returns:
        sorted_lines: 按Y坐标排序后的直线数组
        center_y_list: 对应的中心点Y坐标列表
    """
    if lines is None or len(lines) == 0:
        return None, []
    
    # 计算每条线的中心点Y坐标
    lines_with_center_y = []
    for line in lines:
        x1, y1, x2, y2 = line[0]
        center_y = (y1 + y2) / 2.0
        lines_with_center_y.append((line, center_y))
    
    # 按中心点Y坐标排序
    lines_with_center_y.sort(key=lambda item: item[1], reverse=not ascending)
    
    # 分离排序后的线段和中心点坐标
    sorted_lines = [item[0] for item in lines_with_center_y]
    center_y_list = [item[1] for item in lines_with_center_y]
    
    return np.array(sorted_lines), center_y_list


def sort_lines_by_center(lines, sort_by='x', ascending=True, return_details=False):
    """
    通用的直线中心点排序函数
    
    Args:
        lines: cv2.HoughLinesP 返回的直线数组
        sort_by: 排序依据，'x' 或 'y'
        ascending: 是否升序排列
        return_details: 是否返回详细信息
        
    Returns:
        sorted_lines: 排序后的直线数组
        details: 如果return_details=True，返回详细信息字典
    """
    if lines is None or len(lines) == 0:
        if return_details:
            return None, {'center_coords': [], 'original_indices': []}
        return None
    
    # 计算每条线的中心点坐标和相关信息
    line_info = []
    for i, line in enumerate(lines):
        x1, y1, x2, y2 = line[0]
        center_x = (x1 + x2) / 2.0
        center_y = (y1 + y2) / 2.0
        
        if sort_by.lower() == 'x':
            sort_value = center_x
        elif sort_by.lower() == 'y':
            sort_value = center_y
        else:
            raise ValueError("sort_by 必须是 'x' 或 'y'")
        
        line_info.append({
            'line': line,
            'center_x': center_x,
            'center_y': center_y,
            'sort_value': sort_value,
            'original_index': i
        })
    
    # 按指定坐标排序
    line_info.sort(key=lambda item: item['sort_value'], reverse=not ascending)
    
    # 提取排序后的结果
    sorted_lines = [item['line'] for item in line_info]
    
    if return_details:
        details = {
            'center_coords': [(item['center_x'], item['center_y']) for item in line_info],
            'sort_values': [item['sort_value'] for item in line_info],
            'original_indices': [item['original_index'] for item in line_info],
            'sort_by': sort_by,
            'ascending': ascending
        }
        return np.array(sorted_lines), details
    
    return np.array(sorted_lines)

def convert_hough_lines_simple(lines, image_shape):
    """
    简化版本的HoughLines转换函数
    使用参数方程直接计算端点
    
    Args:
        lines: HoughLines返回的结果
        image_shape: 图像形状
        
    Returns:
        numpy.ndarray: HoughLinesP格式的线段数组
    """
    if lines is None or len(lines) == 0:
        return None
    
    height, width = image_shape[:2]
    line_segments = []
    
    for line in lines:
        rho, theta = line[0]
        
        # 计算直线方向
        cos_theta = np.cos(theta)
        sin_theta = np.sin(theta)
        
        # 计算直线上的基准点
        x0 = rho * cos_theta
        y0 = rho * sin_theta
        
        # 使用图像对角线长度作为延伸距离
        extension = int(np.sqrt(width**2 + height**2))
        
        # 计算直线的两个端点
        x1 = int(x0 + extension * (-sin_theta))
        y1 = int(y0 + extension * cos_theta)
        x2 = int(x0 - extension * (-sin_theta))
        y2 = int(y0 - extension * cos_theta)
        
        line_segments.append([[x1, y1, x2, y2]])
    
    return np.array(line_segments) if line_segments else None

def find_gray_regions_in_scaled_image(cv_image, cw_midan_image, scale_factor, 
                                     gray_min=0, gray_max=255, min_area=10):
    """
    在缩小的图像中查找指定灰度范围的区域，并返回在原图中对应的矩形坐标
    
    Args:
        cv_image: 原始图像 (numpy.ndarray)
        cw_midan_image: 缩小后的图像 (numpy.ndarray) 
        scale_factor: 缩放倍数，即cv_image缩小了多少倍得到cw_midan_image
        gray_min: 灰度最小值 (int, 0-255)
        gray_max: 灰度最大值 (int, 0-255)
        min_area: 最小区域面积，过滤掉过小的区域 (int)
        
    Returns:
        list: 矩形列表，每个矩形格式为 [x1, y1, x2, y2] (原图坐标)
    """
    import cv2
    import numpy as np
    
    # 转换为灰度图像（如果需要）
    if len(cw_midan_image.shape) == 3:
        gray_scaled = cv2.cvtColor(cw_midan_image, cv2.COLOR_BGR2GRAY)
    else:
        gray_scaled = cw_midan_image.copy()
    
    # 创建灰度范围的掩码
    mask = cv2.inRange(gray_scaled, gray_min, gray_max)
    
    # 查找轮廓
    contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    rect_list = []
    
    for contour in contours:
        # 计算轮廓面积
        area = cv2.contourArea(contour)
        if area < min_area:
            continue
            
        # 获取边界矩形（在缩小图像中的坐标）
        x, y, w, h = cv2.boundingRect(contour)
        
        # 转换到原图坐标系
        x1_orig = int(x * scale_factor)
        y1_orig = int(y * scale_factor)
        x2_orig = int((x + w) * scale_factor)
        y2_orig = int((y + h) * scale_factor)
        
        # 确保坐标不超出原图边界
        orig_height, orig_width = cv_image.shape[:2]
        x1_orig = max(0, min(x1_orig, orig_width - 1))
        y1_orig = max(0, min(y1_orig, orig_height - 1))
        x2_orig = max(0, min(x2_orig, orig_width))
        y2_orig = max(0, min(y2_orig, orig_height))
        
        rect_list.append([x1_orig, y1_orig, x2_orig, y2_orig])
    
    return rect_list

def find_gray_points_in_scaled_image(cv_image, cw_midan_image, scale_factor, 
                                    gray_min=0, gray_max=255):
    """
    在缩小的图像中查找指定灰度范围的所有点，并返回在原图中对应的点坐标
    
    Args:
        cv_image: 原始图像 (numpy.ndarray)
        cw_midan_image: 缩小后的图像 (numpy.ndarray)
        scale_factor: 缩放倍数
        gray_min: 灰度最小值 (int, 0-255)
        gray_max: 灰度最大值 (int, 0-255)
        
    Returns:
        list: 点坐标列表，每个点格式为 [x, y] (原图坐标)
    """
    import cv2
    import numpy as np
    
    cur_factor = float(cv_image.shape[0]) / float(cw_midan_image.shape[0])

    # 转换为灰度图像（如果需要）
    if len(cw_midan_image.shape) == 3:
        gray_scaled = cv2.cvtColor(cw_midan_image, cv2.COLOR_BGR2GRAY)
    else:
        gray_scaled = cw_midan_image.copy()
    
    # 创建灰度范围的掩码
    mask = cv2.inRange(gray_scaled, gray_min, gray_max)
    
    # 查找所有符合条件的点
    points_scaled = np.where(mask > 0)
    
    # 转换到原图坐标系
    points_orig = []
    for y, x in zip(points_scaled[0], points_scaled[1]):
        x_orig = int(float(x) * cur_factor + 0.5)
        y_orig = int(float(y) * cur_factor + 0.5)
        
        # 确保坐标不超出原图边界
        orig_height, orig_width = cv_image.shape[:2]
        if 0 <= x_orig < orig_width and 0 <= y_orig < orig_height:
            points_orig.append([x_orig, y_orig])
    
    return points_orig

def merge_close_rectangles(rect_list, distance_threshold=20, merge_method='union'):
    """
    合并距离较近的矩形框
    
    Args:
        rect_list: 矩形列表，每个矩形格式为 [x1, y1, x2, y2]
        distance_threshold: 距离阈值，小于此距离的框会被合并
        merge_method: 合并方法
            - 'union': 取所有框的外接矩形（默认）
            - 'center': 基于中心点距离合并
            - 'overlap': 基于重叠区域合并
        
    Returns:
        list: 合并后的矩形列表，格式为 [x1, y1, x2, y2]
    """
    if not rect_list or len(rect_list) == 0:
        return []
    
    import numpy as np
    
    def calculate_distance(rect1, rect2, method='min'):
        """计算两个矩形之间的距离"""
        x1_1, y1_1, x2_1, y2_1 = rect1
        x1_2, y1_2, x2_2, y2_2 = rect2
        
        if method == 'center':
            # 中心点距离
            center1_x = (x1_1 + x2_1) / 2
            center1_y = (y1_1 + y2_1) / 2
            center2_x = (x1_2 + x2_2) / 2
            center2_y = (y1_2 + y2_2) / 2
            return np.sqrt((center1_x - center2_x)**2 + (center1_y - center2_y)**2)
        
        elif method == 'min':
            # 最小边缘距离
            # 计算矩形之间的最小距离
            left = max(x1_1, x1_2)
            right = min(x2_1, x2_2)
            top = max(y1_1, y1_2)
            bottom = min(y2_1, y2_2)
            
            # 如果矩形重叠，距离为0
            if left <= right and top <= bottom:
                return 0
            
            # 计算最小距离
            dx = max(0, left - right)
            dy = max(0, top - bottom)
            return np.sqrt(dx*dx + dy*dy)
    
    def merge_two_rectangles(rect1, rect2):
        """合并两个矩形为一个外接矩形"""
        x1_1, y1_1, x2_1, y2_1 = rect1
        x1_2, y1_2, x2_2, y2_2 = rect2
        
        merged_x1 = min(x1_1, x1_2)
        merged_y1 = min(y1_1, y1_2)
        merged_x2 = max(x2_1, x2_2)
        merged_y2 = max(y2_1, y2_2)
        
        return [merged_x1, merged_y1, merged_x2, merged_y2]
    
    # 转换为可修改的列表
    rectangles = [list(rect) for rect in rect_list]
    merged = [False] * len(rectangles)
    
    result = []
    
    for i in range(len(rectangles)):
        if merged[i]:
            continue
            
        # 找到所有与当前矩形距离小于阈值的矩形
        current_group = [rectangles[i]]
        merged[i] = True
        
        # 使用队列进行广度优先搜索，确保传递性合并
        queue = [i]
        
        while queue:
            current_idx = queue.pop(0)
            current_rect = rectangles[current_idx]
            
            for j in range(len(rectangles)):
                if merged[j]:
                    continue
                    
                distance = calculate_distance(current_rect, rectangles[j], 'min')
                
                if distance <= distance_threshold:
                    current_group.append(rectangles[j])
                    merged[j] = True
                    queue.append(j)
        
        # 合并当前组中的所有矩形
        if len(current_group) == 1:
            result.append(current_group[0])
        else:
            # 计算外接矩形
            all_x1 = [rect[0] for rect in current_group]
            all_y1 = [rect[1] for rect in current_group]
            all_x2 = [rect[2] for rect in current_group]
            all_y2 = [rect[3] for rect in current_group]
            
            merged_rect = [
                min(all_x1),
                min(all_y1),
                max(all_x2),
                max(all_y2)
            ]
            result.append(merged_rect)
    
    return result

def merge_rectangles_by_center_distance(rect_list, distance_threshold=50):
    """
    基于中心点距离合并矩形框
    
    Args:
        rect_list: 矩形列表，每个矩形格式为 [x1, y1, x2, y2]
        distance_threshold: 中心点距离阈值
        
    Returns:
        list: 合并后的矩形列表
    """
    if not rect_list or len(rect_list) == 0:
        return []
    
    import numpy as np
    from sklearn.cluster import DBSCAN
    
    # 计算每个矩形的中心点
    centers = []
    for rect in rect_list:
        x1, y1, x2, y2 = rect
        center_x = (x1 + x2) / 2
        center_y = (y1 + y2) / 2
        centers.append([center_x, center_y])
    
    centers = np.array(centers)
    
    # 使用DBSCAN聚类算法
    clustering = DBSCAN(eps=distance_threshold, min_samples=1).fit(centers)
    labels = clustering.labels_
    
    # 按聚类结果合并矩形
    unique_labels = set(labels)
    result = []
    
    for label in unique_labels:
        if label == -1:  # 噪声点，单独处理
            indices = np.where(labels == label)[0]
            for idx in indices:
                result.append(rect_list[idx])
        else:
            # 合并同一聚类的矩形
            indices = np.where(labels == label)[0]
            group_rects = [rect_list[idx] for idx in indices]
            
            if len(group_rects) == 1:
                result.append(group_rects[0])
            else:
                # 计算外接矩形
                all_x1 = [rect[0] for rect in group_rects]
                all_y1 = [rect[1] for rect in group_rects]
                all_x2 = [rect[2] for rect in group_rects]
                all_y2 = [rect[3] for rect in group_rects]
                
                merged_rect = [
                    min(all_x1),
                    min(all_y1),
                    max(all_x2),
                    max(all_y2)
                ]
                result.append(merged_rect)
    
    return result

def merge_rectangles_iterative(rect_list, distance_threshold=20, max_iterations=10):
    """
    迭代式合并矩形框，直到没有可合并的框为止
    
    Args:
        rect_list: 矩形列表
        distance_threshold: 距离阈值
        max_iterations: 最大迭代次数，防止无限循环
        
    Returns:
        list: 合并后的矩形列表
    """
    if not rect_list or len(rect_list) == 0:
        return []
    
    def calculate_min_distance(rect1, rect2):
        """计算两个矩形之间的最小距离"""
        x1_1, y1_1, x2_1, y2_1 = rect1
        x1_2, y1_2, x2_2, y2_2 = rect2
        
        left = max(x1_1, x1_2)
        right = min(x2_1, x2_2)
        top = max(y1_1, y1_2)
        bottom = min(y2_1, y2_2)
        
        if left <= right and top <= bottom:
            return 0
        
        dx = max(0, left - right)
        dy = max(0, top - bottom)
        return (dx*dx + dy*dy) ** 0.5
    
    current_rects = [list(rect) for rect in rect_list]
    
    for iteration in range(max_iterations):
        merged_any = False
        new_rects = []
        used = [False] * len(current_rects)
        
        for i in range(len(current_rects)):
            if used[i]:
                continue
                
            current_rect = current_rects[i]
            used[i] = True
            
            # 查找距离最近且小于阈值的矩形
            min_distance = float('inf')
            merge_idx = -1
            
            for j in range(i + 1, len(current_rects)):
                if used[j]:
                    continue
                    
                distance = calculate_min_distance(current_rect, current_rects[j])
                if distance <= distance_threshold and distance < min_distance:
                    min_distance = distance
                    merge_idx = j
            
            if merge_idx != -1:
                # 合并两个矩形
                rect2 = current_rects[merge_idx]
                merged_rect = [
                    min(current_rect[0], rect2[0]),
                    min(current_rect[1], rect2[1]),
                    max(current_rect[2], rect2[2]),
                    max(current_rect[3], rect2[3])
                ]
                new_rects.append(merged_rect)
                used[merge_idx] = True
                merged_any = True
            else:
                new_rects.append(current_rect)
        
        current_rects = new_rects
        
        # 如果没有发生合并，结束迭代
        if not merged_any:
            break
    
    return current_rects

# 在utils.py中添加的完整函数，包含原有的find_gray_regions_in_scaled_image函数修改版本
def find_and_merge_gray_regions(cv_image, cw_midan_image, scale_factor, 
                               gray_min=0, gray_max=255, min_area=10, 
                               merge_distance=20, merge_method='union'):
    """
    在缩小图像中查找灰度区域并自动合并相近的区域
    
    Args:
        cv_image: 原始图像
        cw_midan_image: 缩小后的图像
        scale_factor: 缩放倍数
        gray_min: 灰度最小值
        gray_max: 灰度最大值
        min_area: 最小区域面积
        merge_distance: 合并距离阈值
        merge_method: 合并方法
        
    Returns:
        list: 合并后的矩形列表
    """
    # 首先找到所有区域
    rect_list = find_gray_regions_in_scaled_image(
        cv_image, cw_midan_image, scale_factor, 
        gray_min, gray_max, min_area
    )
    
    if not rect_list:
        return []
    
    # 合并相近的区域
    merged_rects = merge_close_rectangles(
        rect_list, distance_threshold=merge_distance, 
        merge_method=merge_method
    )
    
    return merged_rects

def set_border_gray_value(image, border_width, gray_value):
    """
    将图像四周宽度为w的范围内的灰度数据改为指定值
    
    Args:
        image: 输入图像 (numpy.ndarray)
        border_width: 边框宽度 (int)
        gray_value: 指定的灰度值 (int, 0-255)
        
    Returns:
        numpy.ndarray: 处理后的图像
    """
    import numpy as np
    
    # 复制图像以避免修改原图
    result = image.copy()
    
    # 获取图像尺寸
    if len(image.shape) == 3:
        height, width, channels = image.shape
    else:
        height, width = image.shape
    
    # 确保边框宽度不超过图像尺寸
    border_width = min(border_width, width // 2, height // 2)
    
    if border_width <= 0:
        return result
    
    if len(image.shape) == 3:
        # 彩色图像
        # 上边框
        result[0:border_width, :, :] = gray_value
        # 下边框
        result[height-border_width:height, :, :] = gray_value
        # 左边框
        result[:, 0:border_width, :] = gray_value
        # 右边框
        result[:, width-border_width:width, :] = gray_value
    else:
        # 灰度图像
        # 上边框
        result[0:border_width, :] = gray_value
        # 下边框
        result[height-border_width:height, :] = gray_value
        # 左边框
        result[:, 0:border_width] = gray_value
        # 右边框
        result[:, width-border_width:width] = gray_value
    
    return result

def set_border_gradient(image, border_width, inner_value, outer_value):
    """
    将图像四周宽度为w的范围内设置为渐变值
    
    Args:
        image: 输入图像 (numpy.ndarray)
        border_width: 边框宽度 (int)
        inner_value: 内侧灰度值 (int, 0-255)
        outer_value: 外侧灰度值 (int, 0-255)
        
    Returns:
        numpy.ndarray: 处理后的图像
    """
    import numpy as np
    
    result = image.copy()
    
    if len(image.shape) == 3:
        height, width, channels = image.shape
    else:
        height, width = image.shape
    
    border_width = min(border_width, width // 2, height // 2)
    
    if border_width <= 0:
        return result
    
    # 创建渐变数组
    gradient = np.linspace(outer_value, inner_value, border_width)
    
    if len(image.shape) == 3:
        # 彩色图像
        for i in range(border_width):
            # 上边框
            result[i, :, :] = gradient[i]
            # 下边框
            result[height-1-i, :, :] = gradient[i]
            # 左边框
            result[:, i, :] = gradient[i]
            # 右边框
            result[:, width-1-i, :] = gradient[i]
    else:
        # 灰度图像
        for i in range(border_width):
            # 上边框
            result[i, :] = gradient[i]
            # 下边框
            result[height-1-i, :] = gradient[i]
            # 左边框
            result[:, i] = gradient[i]
            # 右边框
            result[:, width-1-i] = gradient[i]
    
    return result

def set_selective_border_gray_value(image, border_width, gray_value, sides=['top', 'bottom', 'left', 'right']):
    """
    选择性地将图像指定边的宽度为w的范围内的灰度数据改为指定值
    
    Args:
        image: 输入图像 (numpy.ndarray)
        border_width: 边框宽度 (int)
        gray_value: 指定的灰度值 (int, 0-255)
        sides: 要修改的边 (list)，可选: 'top', 'bottom', 'left', 'right'
        
    Returns:
        numpy.ndarray: 处理后的图像
    """
    import numpy as np
    
    result = image.copy()
    
    if len(image.shape) == 3:
        height, width, channels = image.shape
    else:
        height, width = image.shape
    
    border_width = min(border_width, width // 2, height // 2)
    
    if border_width <= 0:
        return result
    
    if len(image.shape) == 3:
        # 彩色图像
        if 'top' in sides:
            result[0:border_width, :, :] = gray_value
        if 'bottom' in sides:
            result[height-border_width:height, :, :] = gray_value
        if 'left' in sides:
            result[:, 0:border_width, :] = gray_value
        if 'right' in sides:
            result[:, width-border_width:width, :] = gray_value
    else:
        # 灰度图像
        if 'top' in sides:
            result[0:border_width, :] = gray_value
        if 'bottom' in sides:
            result[height-border_width:height, :] = gray_value
        if 'left' in sides:
            result[:, 0:border_width] = gray_value
        if 'right' in sides:
            result[:, width-border_width:width] = gray_value
    
    return result

def set_border_based_on_condition(image, border_width, gray_value, condition_func=None):
    """
    基于条件函数设置边框灰度值
    
    Args:
        image: 输入图像 (numpy.ndarray)
        border_width: 边框宽度 (int)
        gray_value: 指定的灰度值 (int, 0-255)
        condition_func: 条件函数，接收(x, y, original_value)，返回bool
        
    Returns:
        numpy.ndarray: 处理后的图像
    """
    import numpy as np
    
    result = image.copy()
    
    if len(image.shape) == 3:
        height, width, channels = image.shape
        is_color = True
    else:
        height, width = image.shape
        is_color = False
    
    border_width = min(border_width, width // 2, height // 2)
    
    if border_width <= 0:
        return result
    
    # 如果没有提供条件函数，默认总是返回True
    if condition_func is None:
        condition_func = lambda x, y, val: True
    
    # 处理边框区域
    for y in range(height):
        for x in range(width):
            # 检查是否在边框区域内
            in_border = (y < border_width or y >= height - border_width or 
                        x < border_width or x >= width - border_width)
            
            if in_border:
                if is_color:
                    original_value = result[y, x, :]
                    if condition_func(x, y, original_value):
                        result[y, x, :] = gray_value
                else:
                    original_value = result[y, x]
                    if condition_func(x, y, original_value):
                        result[y, x] = gray_value
    
    return result

def enhance_center_stretch(image, center_cv, alpha=1):
    """
    中心拉伸
    """
    if len(image.shape) == 3:
        # 彩色图像，对每个通道单独处理
        result = np.zeros_like(image)
        for i in range(3):
            channel = image[:,:,i].astype(np.float32)
            center_data = center_cv[i].astype(np.float32)
            diff = channel - center_data
            new_channel = center_data + alpha * diff
            
            # 拉伸到0-255范围
            new_channel = np.clip(new_channel, 0, 255)
            result[:,:,i] = new_channel.astype(np.uint8)
        return result
    else:
        # 灰度图像
        channel = image.astype(np.float32)
        center_data = center_cv.astype(np.float32)
        diff = channel - center_data
        new_channel = center_data + alpha * diff
        new_channel = np.clip(new_channel, 0, 255)
        result = new_channel.astype(np.uint8)
        return result
def enhance_center_get_center_mean(center_image, start_w=5, end_w=5):
    center_mean = None
    if (start_w + end_w) > center_image.shape[0]:
        start_w = 0
        end_w = 0
    
    if len(center_image.shape) == 3:
        # 彩色图像，计算每个通道的平均值,使用掐头去尾
        height, width, channels = center_image.shape
        channel_means = []
        
        for i in range(channels):
            #channel_mean = center_image[:, :, i].mean()
            channel_mean0 = center_image[:, :, i]
            channel_mean0 = np.sort(channel_mean0, axis=0)
            if start_w + end_w >= channel_mean0.shape[0]:
                channel_mean = np.median(channel_mean0)
                channel_mean = channel_mean
            else:
                channel_mean = np.median(channel_mean0[:, start_w:-end_w])
                channel_mean = channel_mean 
            #print(channel_mean)
            channel_means.append(channel_mean)
        center_mean = channel_means
    else:
        #center_mean = center_image.mean()
        center_mean = np.sort(center_mean, axis=0)        
        channel_mean = np.median(center_mean[:, start_w:-end_w])
        channel_mean = channel_mean 

    return center_mean

def enhance_center_stretch_image(image, center_image, center_value=50, alpha=1.0, 
                                 dir='horizontal', start_w=5, end_w=5, gray_diff=0
):
    """
    中心拉伸
    """
    use_data = 0
    #input_iamge = center_image.copy()w
    center_mean = None
    max_w = 0
    if dir == 'horizontal':
        max_w =center_image.shape[1]
        if (start_w + end_w) > center_image.shape[0]:
            start_w = 0
            end_w = 0
    else:
        max_w =center_image.shape[0]
        if (start_w + end_w) > center_image.shape[1]:
            start_w = 0
            end_w = 0        
    
    if len(center_image.shape) == 3:
        # 彩色图像，计算每个通道的平均值,使用掐头去尾
        height, width, channels = center_image.shape
        channel_means = []
        
        for i in range(channels):
            #channel_mean = center_image[:, :, i].mean()
            channel_mean0 = center_image[:, :, i]
            channel_mean0 = np.sort(channel_mean0, axis=0)
            if start_w + end_w >= max_w:
                channel_mean = np.mean(channel_mean0)
                channel_mean = channel_mean + gray_diff
            else:
                print(channel_mean0.shape, start_w, end_w)
                channel_mean = np.mean(channel_mean0[:,start_w:-end_w])
                channel_mean = channel_mean + gray_diff
               
            #print(channel_mean)
            channel_means.append(channel_mean)
        center_mean = channel_means
    else:
        #center_mean = center_image.mean()
        center_mean = np.sort(center_mean, axis=0)        
        channel_mean = np.mean(center_mean[start_w:-end_w, :])
        channel_mean = channel_mean + gray_diff
    #('center_mean')
    #print(center_mean)
    #print('')
    if dir == 'horizontal':
        # 水平均值
        if len(center_image.shape) == 3:
            # 彩色图像，对每个通道单独处理
            center_height, center_width, center_channels = center_image.shape
            horizontal_data = np.zeros((1, center_width, center_channels))
            #print(horizontal_data.shape[0], horizontal_data.shape[1], horizontal_data.shape[2])
            #print(center_image.shape)
            for i in range(3):
                center_channel = center_image[:,:,i]
                center = center_channel.mean(axis=0, keepdims=True)
                center_value0 = center_value
                if center_mean is not None:
                    center_value0 = center_mean[i]
                center = center - center_value0
                horizontal_data[:,:,i] = center          
            use_data = horizontal_data
            #print(horizontal_data.shape)
        else:
            # 水平均值
            horizontal_data = center_image.mean(axis=0, keepdims=True)
            center_value0 = center_value
            if center_mean is not None:
                center_value0 = center_mean
            horizontal_data = horizontal_data - center_value0
            use_data = horizontal_data
            
    elif dir == 'vertical':
        # 水平均值
        if len(center_image.shape) == 3:
            # 彩色图像，对每个通道单独处理
            center_height, center_width, center_channels = center_image.shape
            vertical_data = np.zeros((center_height, 1, center_channels))
            for i in range(3):
                center_channel = center_image[:,:,i]
                center = center_channel.mean(axis=1, keepdims=True)
                center_value0 = center_value
                if center_mean is not None:
                    center_value0 = center_mean[i]
                center = center - center_value0
                vertical_data[:,:,i] = center  
            use_data = vertical_data
        else:
            # 水平均值
            vertical_data = center_image.mean(axis=1, keepdims=True)
            center_value0 = center_value
            if center_mean is not None:
                center_value0 = center_mean
            vertical_data = vertical_data - center_value0
            use_data = vertical_data
    else:
        # 水平均值
        if len(center_image.shape) == 3:
            # 彩色图像，对每个通道单独处理
            center_height, center_width, center_channels = center_image.shape
            horizontal_data = np.zeros((1, center_width, center_channels))
            #print(horizontal_data.shape[0], horizontal_data.shape[1], horizontal_data.shape[2])
            #print(center_image.shape)
            for i in range(3):
                center_channel = center_image[:,:,i]
                center = center_channel.mean(axis=0, keepdims=True)
                center_value0 = center_value
                if center_mean is not None:
                    center_value0 = center_mean[i]
                center = center - center_value0
                horizontal_data[:,:,i] = center          
            use_data = horizontal_data
            #print(horizontal_data.shape)
        else:
            # 水平均值
            horizontal_data = center_image.mean(axis=0, keepdims=True)
            center_value0 = center_value
            if center_mean is not None:
                center_value0 = center_mean
            horizontal_data = horizontal_data - center_value0
            use_data = horizontal_data
    #use_data = use_data.astype(np.uint8)
    #input_image = use_data + 50
    #input_image = np.clip(input_image, 0, 255)
    #input_image = input_image.astype(np.uint8)
    #print(input_image.shape)
    #print(use_data)
    if len(image.shape) == 3:
        # 彩色图像，对每个通道单独处理
        result = np.zeros_like(image)
        #print(use_data.shape)              
        for i in range(3):
            channel = image[:,:,i].astype(np.float32)
            use_data0 = use_data[:,:,i].astype(np.float32)
            #diff = channel - use_data0
            new_channel = channel - (alpha * use_data0)
            # 拉伸到0-255范围
            new_channel = np.clip(new_channel, 0, 255)
            result[:,:,i] = new_channel.astype(np.uint8)
        use_data = use_data.astype(np.uint8)
        return result, center_mean
    else:
        # 灰度图像
        channel = image.astype(np.float32)
        new_channel = channel - (alpha * use_data)
        new_channel = np.clip(new_channel, 0, 255)
        result = new_channel.astype(np.uint8)
        use_data = use_data.astype(np.uint8)
        return result, center_mean
    

    
def calculate_angle_between_lines(line1, line2, unit='degrees'):
    """
    计算两条线段之间的夹角
    
    Args:
        line1: 第一条线段，格式为 [x1, y1, x2, y2] 或 [[x1, y1, x2, y2]]
        line2: 第二条线段，格式为 [x1, y1, x2, y2] 或 [[x1, y1, x2, y2]]
        unit: 角度单位，'degrees'(度) 或 'radians'(弧度)
        
    Returns:
        float: 两条线段之间的夹角(0-90度或0-π/2弧度)
    """
    import numpy as np
    
    # 处理输入格式
    if isinstance(line1[0], list) or isinstance(line1[0], np.ndarray):
        x1_1, y1_1, x2_1, y2_1 = line1[0]
    else:
        x1_1, y1_1, x2_1, y2_1 = line1
        
    if isinstance(line2[0], list) or isinstance(line2[0], np.ndarray):
        x1_2, y1_2, x2_2, y2_2 = line2[0]
    else:
        x1_2, y1_2, x2_2, y2_2 = line2
    
    # 计算方向向量
    vector1 = np.array([x2_1 - x1_1, y2_1 - y1_1])
    vector2 = np.array([x2_2 - x1_2, y2_2 - y1_2])
    
    # 计算向量长度
    length1 = np.linalg.norm(vector1)
    length2 = np.linalg.norm(vector2)
    
    # 避免除零错误
    if length1 == 0 or length2 == 0:
        return 0.0 if unit == 'degrees' else 0.0
    
    # 计算单位向量
    unit_vector1 = vector1 / length1
    unit_vector2 = vector2 / length2
    
    # 计算点积
    dot_product = np.dot(unit_vector1, unit_vector2)
    
    # 限制点积值在[-1, 1]范围内，避免数值误差
    dot_product = np.clip(dot_product, -1.0, 1.0)
    
    # 计算夹角(弧度)
    angle_rad = np.arccos(abs(dot_product))  # 使用abs确保得到锐角
    
    if unit == 'degrees':
        return np.degrees(angle_rad)
    else:
        return angle_rad


def calculate_angle_with_direction(line1, line2, unit='degrees'):
    """
    计算两条线段之间的有向夹角（考虑方向）
    
    Args:
        line1: 第一条线段
        line2: 第二条线段
        unit: 角度单位
        
    Returns:
        float: 两条线段之间的有向夹角(-180到180度或-π到π弧度)
    """
    import numpy as np
    
    # 处理输入格式
    if isinstance(line1[0], list) or isinstance(line1[0], np.ndarray):
        x1_1, y1_1, x2_1, y2_1 = line1[0]
    else:
        x1_1, y1_1, x2_1, y2_1 = line1
        
    if isinstance(line2[0], list) or isinstance(line2[0], np.ndarray):
        x1_2, y1_2, x2_2, y2_2 = line2[0]
    else:
        x1_2, y1_2, x2_2, y2_2 = line2
    
    # 计算方向向量
    vector1 = np.array([x2_1 - x1_1, y2_1 - y1_1])
    vector2 = np.array([x2_2 - x1_2, y2_2 - y1_2])
    
    # 计算角度
    angle1 = np.arctan2(vector1[1], vector1[0])
    angle2 = np.arctan2(vector2[1], vector2[0])
    
    # 计算角度差
    angle_diff = angle2 - angle1
    
    # 将角度限制在[-π, π]范围内
    while angle_diff > np.pi:
        angle_diff -= 2 * np.pi
    while angle_diff < -np.pi:
        angle_diff += 2 * np.pi
    
    if unit == 'degrees':
        return np.degrees(angle_diff)
    else:
        return angle_diff


def calculate_lines_parallelism(line1, line2, angle_threshold=5):
    """
    判断两条线是否平行
    
    Args:
        line1: 第一条线段
        line2: 第二条线段
        angle_threshold: 角度阈值(度)，小于此值认为平行
        
    Returns:
        tuple: (is_parallel, angle) 是否平行和夹角
    """
    angle = calculate_angle_between_lines(line1, line2, 'degrees')
    is_parallel = angle <= angle_threshold or angle >= (90 - angle_threshold)
    
    return is_parallel, angle


def calculate_lines_perpendicularity(line1, line2, angle_threshold=5):
    """
    判断两条线是否垂直
    
    Args:
        line1: 第一条线段
        line2: 第二条线段
        angle_threshold: 角度阈值(度)，与90度的差值小于此值认为垂直
        
    Returns:
        tuple: (is_perpendicular, angle) 是否垂直和夹角
    """
    angle = calculate_angle_between_lines(line1, line2, 'degrees')
    is_perpendicular = abs(angle - 90) <= angle_threshold
    
    return is_perpendicular, angle


def detect_edges_gradient_v2(image, params=None):
    """
    使用Sobel算子检测特定方向的边缘 (v2版本 - 支持阈值范围)
    
    Args:
        image: 输入图像
        params: 参数字典，包含以下键值:
            - direction: str, 检测方向 ('horizontal', 'vertical', 'both')
            - ksize: int, Sobel核大小 (1, 3, 5, 7)
            - threshold_min: int, 最小阈值 (0-255)
            - threshold_max: int, 最大阈值 (0-255)
            - apply_threshold: bool, 是否应用阈值范围过滤
            - normalize: bool, 是否归一化到0-255
    
    Returns:
        edges: 检测到的边缘图像
    """
    # 默认参数
    default_params = {
        'direction': 'horizontal',   # 检测方向
        'ksize': 3,                 # Sobel核大小
        'threshold_min': 50,        # 最小阈值
        'threshold_max': 255,       # 最大阈值
        'apply_threshold': True,    # 是否应用阈值范围
        'normalize': True,          # 是否归一化
    }
    
    # 合并参数
    if params is None:
        params = {}
    final_params = {**default_params, **params}
    
    # 转换为灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
    
    if final_params['direction'] == 'horizontal':
        # 检测水平方向的灰度变化（垂直边缘）
        sobel = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
    elif final_params['direction'] == 'vertical':
        # 检测垂直方向的灰度变化（水平边缘）
        sobel = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
    else:
        # 两个方向都检测
        sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
        sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
        sobel = np.sqrt(sobelx**2 + sobely**2)
    
    # 取绝对值
    sobel = np.absolute(sobel)
    
    # 归一化处理
    if final_params['normalize']:
        if sobel.max() > 0:
            sobel = sobel / sobel.max() * 255
    
    # 转换为uint8
    sobel = np.uint8(sobel)
    
    # 应用阈值范围过滤
    if final_params['apply_threshold']:
        edges = np.where(
            (sobel >= final_params['threshold_min']) & 
            (sobel <= final_params['threshold_max']), 
            sobel, 0
        )
    else:
        edges = sobel
    
    return edges

def detect_positive_gradient_v2(image, params=None):
    """
    检测从暗到亮的变化（正梯度）(v2版本 - 支持阈值范围)
    
    Args:
        image: 输入图像
        params: 参数字典，包含以下键值:
            - direction: str, 检测方向 ('horizontal', 'vertical')
            - ksize: int, Sobel核大小 (1, 3, 5, 7)
            - threshold_min: int, 最小阈值 (0-255)
            - threshold_max: int, 最大阈值 (0-255)
            - apply_threshold: bool, 是否应用阈值范围过滤
            - normalize: bool, 是否归一化到0-255
    
    Returns:
        positive_edges: 正梯度边缘图像
    """
    # 默认参数
    default_params = {
        'direction': 'horizontal',   # 检测方向
        'ksize': 3,                 # Sobel核大小
        'threshold_min': 50,        # 最小阈值
        'threshold_max': 255,       # 最大阈值
        'apply_threshold': True,    # 是否应用阈值范围
        'normalize': True,          # 是否归一化
    }
    
    # 合并参数
    if params is None:
        params = {}
    final_params = {**default_params, **params}
    
    # 转换为灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
    
    if final_params['direction'] == 'horizontal':
        sobel = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
    else:
        sobel = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
    
    # 只保留正值（从暗到亮的变化）
    positive_edges = np.where(sobel > 0, sobel, 0)
    
    # 归一化处理
    if final_params['normalize']:
        if positive_edges.max() > 0:
            positive_edges = positive_edges / positive_edges.max() * 255
    
    # 转换为uint8
    positive_edges = np.uint8(positive_edges)
    
    # 应用阈值范围过滤
    if final_params['apply_threshold']:
        positive_edges = np.where(
            (positive_edges >= final_params['threshold_min']) & 
            (positive_edges <= final_params['threshold_max']), 
            positive_edges, 0
        )
    
    return positive_edges

def detect_negative_gradient_v2(image, params=None):
    """
    检测从亮到暗的变化（负梯度）(v2版本 - 支持阈值范围)
    
    Args:
        image: 输入图像
        params: 参数字典，包含以下键值:
            - direction: str, 检测方向 ('horizontal', 'vertical')
            - ksize: int, Sobel核大小 (1, 3, 5, 7)
            - threshold_min: int, 最小阈值 (0-255)
            - threshold_max: int, 最大阈值 (0-255)
            - apply_threshold: bool, 是否应用阈值范围过滤
            - normalize: bool, 是否归一化到0-255
    
    Returns:
        negative_edges: 负梯度边缘图像
    """
    # 默认参数
    default_params = {
        'direction': 'horizontal',   # 检测方向
        'ksize': 3,                 # Sobel核大小
        'threshold_min': 50,        # 最小阈值
        'threshold_max': 255,       # 最大阈值
        'apply_threshold': True,    # 是否应用阈值范围
        'normalize': True,          # 是否归一化
    }
    
    # 合并参数
    if params is None:
        params = {}
    final_params = {**default_params, **params}
    
    # 转换为灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
    
    if final_params['direction'] == 'horizontal':
        sobel = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
    else:
        sobel = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
    
    # 只保留负值的绝对值（从亮到暗的变化）
    negative_edges = np.where(sobel < 0, -sobel, 0)
    
    # 归一化处理
    if final_params['normalize']:
        if negative_edges.max() > 0:
            negative_edges = negative_edges / negative_edges.max() * 255
    
    # 转换为uint8
    negative_edges = np.uint8(negative_edges)
    
    # 应用阈值范围过滤
    if final_params['apply_threshold']:
        negative_edges = np.where(
            (negative_edges >= final_params['threshold_min']) & 
            (negative_edges <= final_params['threshold_max']), 
            negative_edges, 0
        )
    
    return negative_edges

def apply_channel_min_threshold(image, min_values):
    """
    对图像的每个通道应用最小值阈值处理
    
    Args:
        image: cv2图像，可以是单通道(H,W)或三通道(H,W,3)
        min_values: 每个通道的最小值，可以是单个数值或包含每个通道最小值的列表/数组
    
    Returns:
        处理后的图像，低于最小值的像素被设置为最小值
    """
    # 复制图像以避免修改原图
    result = image.copy()
    
    # 确保min_values是numpy数组
    if isinstance(min_values, (int, float)):
        min_values = np.array([min_values])
    else:
        min_values = np.array(min_values)
    
    # 处理单通道图像
    if len(image.shape) == 2:
        result[result < min_values[0]] = min_values[0]
    
    # 处理三通道图像
    elif len(image.shape) == 3 and image.shape[2] == 3:
        for channel in range(3):
            min_val = min_values[channel] if len(min_values) > channel else min_values[0]
            result[:, :, channel][result[:, :, channel] < min_val] = min_val
    
    return result

def apply_channel_min_threshold_mdian(image, min_values):
    """
    对图像的每个通道应用最小值阈值处理，用周围3x3邻域的中值替换低于最小值的像素
    
    Args:
        image: cv2图像，可以是单通道(H,W)或三通道(H,W,3)
        min_values: 每个通道的最小值，可以是单个数值或包含每个通道最小值的列表/数组
    
    Returns:
        处理后的图像，低于最小值的像素被3x3邻域中值替换
    """
    # 复制图像以避免修改原图
    result = image.copy().astype(np.float32)
    
    # 确保min_values是numpy数组
    if isinstance(min_values, (int, float)):
        min_values = np.array([min_values])
    else:
        min_values = np.array(min_values)
    
    def get_3x3_neighbor_median(img, row, col):
        """获取3x3邻域（除了中心点）的中值"""
        h, w = img.shape[:2]
        
        # 计算3x3邻域的边界
        start_row = max(0, row - 1)
        end_row = min(h, row + 2)
        start_col = max(0, col - 1)
        end_col = min(w, col + 2)
        
        # 提取邻域
        neighborhood = img[start_row:end_row, start_col:end_col]
        
        if len(img.shape) == 2:  # 单通道
            # 排除中心点
            mask = np.ones(neighborhood.shape, dtype=bool)
            center_row = row - start_row
            center_col = col - start_col
            if 0 <= center_row < mask.shape[0] and 0 <= center_col < mask.shape[1]:
                mask[center_row, center_col] = False
            
            valid_pixels = neighborhood[mask]
            return np.median(valid_pixels) if len(valid_pixels) > 0 else img[row, col]
        
        else:  # 三通道
            # 排除中心点
            mask = np.ones(neighborhood.shape[:2], dtype=bool)
            center_row = row - start_row
            center_col = col - start_col
            if 0 <= center_row < mask.shape[0] and 0 <= center_col < mask.shape[1]:
                mask[center_row, center_col] = False
            
            medians = []
            for c in range(neighborhood.shape[2]):
                valid_pixels = neighborhood[:, :, c][mask]
                medians.append(np.median(valid_pixels) if len(valid_pixels) > 0 else img[row, col, c])
            return np.array(medians)
    
    # 处理单通道图像
    if len(image.shape) == 2:
        h, w = image.shape
        min_val = min_values[0]
        
        for row in range(h):
            for col in range(w):
                if result[row, col] < min_val:
                    result[row, col] = get_3x3_neighbor_median(image, row, col)
    
    # 处理三通道图像
    elif len(image.shape) == 3 and image.shape[2] == 3:
        h, w, c = image.shape
        
        for row in range(h):
            for col in range(w):
                for channel in range(3):
                    min_val = min_values[channel] if len(min_values) > channel else min_values[0]
                    
                    if result[row, col, channel] < min_val:
                        # 获取该像素点所有通道的邻域中值
                        neighbor_medians = get_3x3_neighbor_median(image, row, col)
                        result[row, col, channel] = neighbor_medians[channel]
    
    # 转换回原始数据类型
    return result.astype(image.dtype)


def apply_channel_threshold_with_mask(image, threshold_values, mask=None, above_threshold=False, use_median=True, boundary_mode='skip'):
    """
    使用自定义掩膜对图像进行阈值处理，用邻域统计值替换目标像素
    
    Args:
        image: cv2图像，可以是单通道(H,W)或三通道(H,W,3)
        threshold_values: 每个通道的阈值，可以是单个数值或包含每个通道阈值的列表/数组
        mask: 掩膜数组，指定要计算统计值的邻域区域，True的位置参与计算
        above_threshold: 布尔值，True表示处理高于阈值的像素，False表示处理低于阈值的像素
        use_median: 布尔值，True使用中值，False使用均值
        boundary_mode: 边界处理模式，'skip', 'pad_zero', 'pad_edge', 'pad_reflect', 'ignore_pixel'
    
    Returns:
        处理后的图像
    """
    # 复制图像以避免修改原图
    result = image.copy().astype(np.float32)
    
    # 确保threshold_values是numpy数组
    if isinstance(threshold_values, (int, float)):
        threshold_values = np.array([threshold_values])
    else:
        threshold_values = np.array(threshold_values)
    
    # 默认掩膜为3x3全1掩膜，排除中心
    if mask is None:
        mask = np.ones((3, 3), dtype=bool)
        mask[1, 1] = False
    else:
        mask = np.array(mask, dtype=bool)
    
    def get_neighbor_statistic(img, row, col, mask, use_median=True, boundary_mode='skip'):
        h, w = img.shape[:2]
        mask_h, mask_w = mask.shape
        center_row, center_col = mask_h // 2, mask_w // 2
        
        valid_pixels_list = []
        
        for mask_r in range(mask_h):
            for mask_c in range(mask_w):
                if mask[mask_r, mask_c]:
                    img_r = row - center_row + mask_r
                    img_c = col - center_col + mask_c
                    
                    if boundary_mode == 'skip':
                        # 跳过越界像素
                        if 0 <= img_r < h and 0 <= img_c < w:
                            valid_pixels_list.append(img[img_r, img_c])
                    
                    elif boundary_mode == 'pad_zero':
                        # 越界用0填充
                        if 0 <= img_r < h and 0 <= img_c < w:
                            valid_pixels_list.append(img[img_r, img_c])
                        else:
                            valid_pixels_list.append(0)
                    
                    elif boundary_mode == 'pad_edge':
                        # 越界用最近边缘像素填充
                        clamped_r = max(0, min(h-1, img_r))
                        clamped_c = max(0, min(w-1, img_c))
                        valid_pixels_list.append(img[clamped_r, clamped_c])
                    
                    elif boundary_mode == 'pad_reflect':
                        # 越界用镜像填充
                        if img_r < 0:
                            img_r = -img_r
                        elif img_r >= h:
                            img_r = h - 1 - (img_r - h + 1)
                        
                        if img_c < 0:
                            img_c = -img_c
                        elif img_c >= w:
                            img_c = w - 1 - (img_c - w + 1)
                        
                        # 再次检查边界（镜像后可能仍越界）
                        if 0 <= img_r < h and 0 <= img_c < w:
                            valid_pixels_list.append(img[img_r, img_c])
                        else:
                            valid_pixels_list.append(img[row, col])  # fallback
                    
                    elif boundary_mode == 'ignore_pixel':
                        # 如果有任何邻域像素越界，就返回原值
                        if not (0 <= img_r < h and 0 <= img_c < w):
                            return img[row, col] if len(img.shape) == 2 else img[row, col, :]
                        valid_pixels_list.append(img[img_r, img_c])
        
        # 如果没有有效像素，返回原值
        if len(valid_pixels_list) == 0:
            return img[row, col] if len(img.shape) == 2 else img[row, col, :]
        
        # 将像素列表转换为numpy数组并计算统计值
        valid_pixels = np.array(valid_pixels_list)
        
        if len(img.shape) == 2:
            # 单通道图像
            if use_median:
                return np.median(valid_pixels)
            else:
                return np.mean(valid_pixels)
        else:
            # 多通道图像
            if use_median:
                return np.median(valid_pixels, axis=0)
            else:
                return np.mean(valid_pixels, axis=0)
    
    # 处理单通道图像
    if len(image.shape) == 2:
        h, w = image.shape
        threshold_val = threshold_values[0]
        
        for row in range(h):
            for col in range(w):
                pixel_val = result[row, col]
                should_process = (pixel_val > threshold_val) if above_threshold else (pixel_val < threshold_val)
                
                if should_process:
                    result[row, col] = get_neighbor_statistic(image, row, col, mask, use_median, boundary_mode)
    
    # 处理三通道图像
    elif len(image.shape) == 3 and image.shape[2] == 3:
        h, w, c = image.shape
        
        for row in range(h):
            for col in range(w):
                for channel in range(3):
                    threshold_val = threshold_values[channel] if len(threshold_values) > channel else threshold_values[0]
                    pixel_val = result[row, col, channel]
                    should_process = (pixel_val > threshold_val) if above_threshold else (pixel_val < threshold_val)
                    
                    if should_process:
                        neighbor_stats = get_neighbor_statistic(image, row, col, mask, use_median, boundary_mode)
                        if neighbor_stats is not None:
                            result[row, col, channel] = neighbor_stats[channel]
    
    return result.astype(image.dtype)

def resize_image_with_padding_or_cropping(image, target_width, direction='left', fill_color=(0, 0, 0)):
    """
    根据目标宽度调整图像，如果超出则截断，如果不足则填补
    
    Args:
        image: 输入图像 (numpy.ndarray)
        target_width: 目标宽度 (int)
        direction: 截断或填补方向 (str)
            - 'left': 从左侧截断或在左侧填补
            - 'right': 从右侧截断或在右侧填补
            - 'center': 居中截断或居中填补
        fill_color: 填补颜色，默认黑色 (tuple)
            - 灰度图: (0,) 或 0
            - 彩色图: (B, G, R) 如 (0, 0, 0) 表示黑色
            
    Returns:
        numpy.ndarray: 处理后的图像
    """
    if image is None:
        raise ValueError("输入图像不能为空")
        
    current_height, current_width = image.shape[:2]
    
    # 如果当前宽度等于目标宽度，直接返回
    if current_width == target_width:
        return image.copy()
    
    # 创建目标图像
    if len(image.shape) == 3:
        # 彩色图像
        channels = image.shape[2]
        target_image = np.full((current_height, target_width, channels), fill_color, dtype=image.dtype)
    else:
        # 灰度图像
        fill_value = fill_color[0] if isinstance(fill_color, (tuple, list)) else fill_color
        target_image = np.full((current_height, target_width), fill_value, dtype=image.dtype)
    
    if current_width > target_width:
        # 需要截断
        if direction == 'left':
            # 从左侧截断，保留右侧
            cropped = image[:, current_width - target_width:]
        elif direction == 'right':
            # 从右侧截断，保留左侧
            cropped = image[:, :target_width]
        elif direction == 'center':
            # 居中截断
            start_x = (current_width - target_width) // 2
            cropped = image[:, start_x:start_x + target_width]
        else:
            raise ValueError("direction 参数必须是 'left', 'right' 或 'center'")
        
        return cropped
        
    else:
        # 需要填补
        padding_width = target_width - current_width
        
        if direction == 'left':
            # 在左侧填补
            if len(image.shape) == 3:
                target_image[:, padding_width:] = image
            else:
                target_image[:, padding_width:] = image
                
        elif direction == 'right':
            # 在右侧填补
            if len(image.shape) == 3:
                target_image[:, :current_width] = image
            else:
                target_image[:, :current_width] = image
                
        elif direction == 'center':
            # 居中填补
            start_x = padding_width // 2
            if len(image.shape) == 3:
                target_image[:, start_x:start_x + current_width] = image
            else:
                target_image[:, start_x:start_x + current_width] = image
        else:
            raise ValueError("direction 参数必须是 'left', 'right' 或 'center'")
            
        return target_image

def resize_image_height_with_padding_or_cropping(image, target_height, direction='top', fill_color=(0, 0, 0)):
    """
    根据目标高度调整图像，如果超出则截断，如果不足则填补
    
    Args:
        image: 输入图像 (numpy.ndarray)
        target_height: 目标高度 (int)
        direction: 截断或填补方向 (str)
            - 'top': 从顶部截断或在顶部填补
            - 'bottom': 从底部截断或在底部填补
            - 'center': 居中截断或居中填补
        fill_color: 填补颜色，默认黑色 (tuple)
            
    Returns:
        numpy.ndarray: 处理后的图像
    """
    if image is None:
        raise ValueError("输入图像不能为空")
        
    current_height, current_width = image.shape[:2]
    
    # 如果当前高度等于目标高度，直接返回
    if current_height == target_height:
        return image.copy()
    
    # 创建目标图像
    if len(image.shape) == 3:
        # 彩色图像
        channels = image.shape[2]
        target_image = np.full((target_height, current_width, channels), fill_color, dtype=image.dtype)
    else:
        # 灰度图像
        fill_value = fill_color[0] if isinstance(fill_color, (tuple, list)) else fill_color
        target_image = np.full((target_height, current_width), fill_value, dtype=image.dtype)
    
    if current_height > target_height:
        # 需要截断
        if direction == 'top':
            # 从顶部截断，保留底部
            cropped = image[current_height - target_height:, :]
        elif direction == 'bottom':
            # 从底部截断，保留顶部
            cropped = image[:target_height, :]
        elif direction == 'center':
            # 居中截断
            start_y = (current_height - target_height) // 2
            cropped = image[start_y:start_y + target_height, :]
        else:
            raise ValueError("direction 参数必须是 'top', 'bottom' 或 'center'")
        
        return cropped
        
    else:
        # 需要填补
        padding_height = target_height - current_height
        
        if direction == 'top':
            # 在顶部填补
            target_image[padding_height:, :] = image
                
        elif direction == 'bottom':
            # 在底部填补
            target_image[:current_height, :] = image
                
        elif direction == 'center':
            # 居中填补
            start_y = padding_height // 2
            target_image[start_y:start_y + current_height, :] = image
        else:
            raise ValueError("direction 参数必须是 'top', 'bottom' 或 'center'")
            
        return target_image
    
def set_edge_pixels_black(image, direction: str, width: int):
    """
    将图片指定方向的边缘像素设置为纯黑色
    
    Args:
        image: 输入图像 (numpy.ndarray)
        direction: 方向 ('up', 'down', 'left', 'right')
        width: 要设置为黑色的像素宽度
        
    Returns:
        numpy.ndarray: 处理后的图像
    """
    import cv2
    import numpy as np
    
    if image is None:
        raise ValueError("输入图像不能为空")
    
    # 复制图像，避免修改原图
    result_image = image.copy()
    
    # 获取图像尺寸
    height, img_width = result_image.shape[:2]
    
    # 验证参数
    direction = direction.lower()
    if direction not in ['up', 'down', 'left', 'right']:
        raise ValueError("方向参数必须是 'up', 'down', 'left', 'right' 之一")
    
    if width <= 0:
        raise ValueError("宽度必须大于0")
    
    # 根据方向设置对应区域为黑色
    if direction == 'up':
        # 上边缘
        width = min(width, height)  # 确保不超过图像高度
        result_image[0:width, :] = 0
        
    elif direction == 'down':
        # 下边缘
        width = min(width, height)
        result_image[height-width:height, :] = 0
        
    elif direction == 'left':
        # 左边缘
        width = min(width, img_width)  # 确保不超过图像宽度
        result_image[:, 0:width] = 0
        
    elif direction == 'right':
        # 右边缘
        width = min(width, img_width)
        result_image[:, img_width-width:img_width] = 0
    
    return result_image

def set_multiple_edges_black(image, edge_configs: list):
    """
    同时设置多个方向的边缘为黑色
    
    Args:
        image: 输入图像
        edge_configs: 边缘配置列表，每个元素为 {'direction': str, 'width': int}
        
    Returns:
        numpy.ndarray: 处理后的图像
        
    Example:
        edge_configs = [
            {'direction': 'up', 'width': 10},
            {'direction': 'left', 'width': 20}
        ]
    """
    result_image = image.copy()
    
    for config in edge_configs:
        direction = config.get('direction')
        width = config.get('width')
        
        if direction and width:
            result_image = set_edge_pixels_black(result_image, direction, width)
    
    return result_image

def set_edge_pixels_with_color(image, direction: str, width: int, color=(0, 0, 0)):
    """
    将图片指定方向的边缘像素设置为指定颜色（扩展版本）
    
    Args:
        image: 输入图像
        direction: 方向 ('up', 'down', 'left', 'right')
        width: 要设置的像素宽度
        color: 颜色值，默认为黑色 (0, 0, 0)
        
    Returns:
        numpy.ndarray: 处理后的图像
    """
    import cv2
    import numpy as np
    
    if image is None:
        raise ValueError("输入图像不能为空")
    
    result_image = image.copy()
    height, img_width = result_image.shape[:2]
    
    direction = direction.lower()
    if direction not in ['up', 'down', 'left', 'right']:
        raise ValueError("方向参数必须是 'up', 'down', 'left', 'right' 之一")
    
    if width <= 0:
        raise ValueError("宽度必须大于0")
    
    # 处理不同的图像类型
    if len(result_image.shape) == 2:  # 灰度图
        fill_value = color[0] if isinstance(color, (list, tuple)) else color
    else:  # 彩色图
        fill_value = color
    
    # 根据方向设置对应区域
    if direction == 'up':
        width = min(width, height)
        result_image[0:width, :] = fill_value
        
    elif direction == 'down':
        width = min(width, height)
        result_image[height-width:height, :] = fill_value
        
    elif direction == 'left':
        width = min(width, img_width)
        result_image[:, 0:width] = fill_value
        
    elif direction == 'right':
        width = min(width, img_width)
        result_image[:, img_width-width:img_width] = fill_value
    
    return result_image



# def detect_edges_universal(image, params=None):
#     """
#     通用边缘检测函数，使用Sobel算子检测特定方向和类型的边缘
    
#     Args:
#         image: 输入图像
#         params: 参数字典，包含以下键值:
#             - direction: str, 检测方向 ('horizontal', 'vertical', 'both')
#                 * 'horizontal': 检测水平方向的灰度变化（垂直边缘）
#                 * 'vertical': 检测垂直方向的灰度变化（水平边缘）
#                 * 'both': 两个方向都检测
#             - edge_type: str, 边缘类型 ('all', 'positive', 'negative')
#                 * 'all': 检测所有边缘（默认）
#                 * 'positive': 只检测正梯度（从暗到亮）
#                 * 'negative': 只检测负梯度（从亮到暗）
#             - ksize: int, Sobel核大小 (1, 3, 5, 7)
#             - normalize: bool, 是否归一化到0-255
#             - threshold: int, 二值化阈值 (0-255)
#             - apply_threshold: bool, 是否应用二值化
#             - max_value: int, 二值化最大值 (0-255)
#             - threshold_type: int, 阈值类型 (cv2.THRESH_BINARY等)
    
#     Returns:
#         edges: 检测到的边缘图像
        
#     Examples:
#         # 检测所有垂直边缘
#         edges = detect_edges_universal(image, {'direction': 'horizontal'})
        
#         # 只检测从暗到亮的水平边缘
#         edges = detect_edges_universal(image, {
#             'direction': 'vertical', 
#             'edge_type': 'positive'
#         })
        
#         # 检测从亮到暗的垂直边缘并二值化
#         edges = detect_edges_universal(image, {
#             'direction': 'horizontal',
#             'edge_type': 'negative',
#             'apply_threshold': True,
#             'threshold': 100
#         })
#     """
#     # 默认参数
#     default_params = {
#         'direction': 'both',         # 检测方向: 'horizontal', 'vertical', 'both'
#         'edge_type': 'all',          # 边缘类型: 'all', 'positive', 'negative'
#         'ksize': 3,                  # Sobel核大小: 1, 3, 5, 7
#         'normalize': True,           # 是否归一化到0-255
#         'threshold': 50,             # 二值化阈值: 0-255
#         'apply_threshold': False,    # 是否应用二值化
#         'max_value': 255,            # 二值化最大值
#         'threshold_type': cv2.THRESH_BINARY  # 阈值类型
#     }
#     use_cuda = False
#     try:
#         cuda_count = cv2.cuda.getCudaEnabledDeviceCount()
#         if cuda_count > 0:
#             use_cuda = True
#             print(f"✅ 使用CUDA加速 (设备数: {cuda_count})")
#         else:
#             print(f"⚠️  CUDA不可用，设备数为0")
#     except Exception as e:
#         print(f"⚠️  CUDA初始化失败: {str(e)}")      
#     # 合并参数
#     if params is None:
#         params = {}
#     final_params = {**default_params, **params}
    
#     # 转换为灰度图
#     gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
    
#     # 根据方向计算Sobel梯度
#     if final_params['direction'] == 'horizontal':
#         # 检测水平方向的灰度变化（垂直边缘）
#         sobel = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
#     elif final_params['direction'] == 'vertical':
#         # 检测垂直方向的灰度变化（水平边缘）
#         sobel = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
#     elif final_params['direction'] == 'both':
#         # 两个方向都检测
#         sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
#         sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
#         sobel = np.sqrt(sobelx**2 + sobely**2)
#     else:
#         raise ValueError("direction 参数必须是 'horizontal', 'vertical' 或 'both'")
    
#     # 根据边缘类型处理梯度
#     edge_type = final_params['edge_type'].lower()
    
#     if edge_type == 'positive':
#         # 只保留正值（从暗到亮的变化）
#         edges = np.where(sobel > 0, sobel, 0)
#     elif edge_type == 'negative':
#         # 只保留负值（从亮到暗的变化）
#         edges = np.where(sobel < 0, -sobel, 0)
#     elif edge_type == 'all':
#         # 保留所有边缘（取绝对值）
#         edges = np.absolute(sobel)
#     else:
#         raise ValueError("edge_type 参数必须是 'all', 'positive' 或 'negative'")
    
#     # 归一化处理
#     if final_params['normalize']:
#         if edges.max() > 0:
#             edges = edges / edges.max() * 255
    
#     # 转换为uint8
#     edges = np.uint8(np.clip(edges, 0, 255))
    
#     # 可选的二值化处理
#     if final_params['apply_threshold']:
#         _, edges = cv2.threshold(
#             edges, 
#             final_params['threshold'], 
#             final_params['max_value'], 
#             final_params['threshold_type']
#         )
    
#     return edges
def detect_edges_universal(image, params=None):
    """
    通用边缘检测函数，使用Sobel算子检测特定方向和类型的边缘
    
    Args:
        image: 输入图像
        params: 参数字典，包含以下键值:
            - direction: str, 检测方向 ('horizontal', 'vertical', 'both')
                * 'horizontal': 检测水平方向的灰度变化（垂直边缘）
                * 'vertical': 检测垂直方向的灰度变化（水平边缘）
                * 'both': 两个方向都检测
            - edge_type: str, 边缘类型 ('all', 'positive', 'negative')
                * 'all': 检测所有边缘（默认）
                * 'positive': 只检测正梯度（从暗到亮）
                * 'negative': 只检测负梯度（从亮到暗）
            - ksize: int, Sobel核大小 (1, 3, 5, 7)
            - normalize: bool, 是否归一化到0-255
            - threshold: int, 二值化阈值 (0-255)
            - apply_threshold: bool, 是否应用二值化
            - max_value: int, 二值化最大值 (0-255)
            - threshold_type: int, 阈值类型 (cv2.THRESH_BINARY等)
    
    Returns:
        edges: 检测到的边缘图像
    """
    # 默认参数
    default_params = {
        'direction': 'both',         # 检测方向: 'horizontal', 'vertical', 'both'
        'edge_type': 'all',          # 边缘类型: 'all', 'positive', 'negative'
        'ksize': 3,                  # Sobel核大小: 1, 3, 5, 7
        'normalize': True,           # 是否归一化到0-255
        'threshold': 50,             # 二值化阈值: 0-255
        'apply_threshold': False,    # 是否应用二值化
        'max_value': 255,            # 二值化最大值
        'threshold_type': cv2.THRESH_BINARY  # 阈值类型
    }
    
    # 检测CUDA可用性
    use_cuda = False
    try:
        cuda_count = cv2.cuda.getCudaEnabledDeviceCount()
        if cuda_count > 0:
            use_cuda = True
            print(f"✅ 使用CUDA加速 (设备数: {cuda_count})")
        else:
            print(f"⚠️  CUDA不可用，设备数为0")
    except Exception as e:
        print(f"⚠️  CUDA初始化失败: {str(e)}")      
  
    # 合并参数
    if params is None:
        params = {}
    final_params = {**default_params, **params}
    
    # 转换为灰度图
    if len(image.shape) == 3:
        if use_cuda:
            try:
                # CUDA加速的颜色转换
                gpu_image = cv2.cuda_GpuMat()
                gpu_image.upload(image)
                gpu_gray = cv2.cuda.cvtColor(gpu_image, cv2.COLOR_BGR2GRAY)
                gray = gpu_gray.download()
            except Exception as e:
                print(f"⚠️  CUDA颜色转换失败，回退到CPU: {str(e)}")
                gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
                use_cuda = False
        else:
            gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    else:
        gray = image
    

    # CUDA加速的Sobel梯度计算
    if use_cuda:
        try:
            # 上传图像到GPU
            gpu_gray = cv2.cuda_GpuMat()
            gpu_gray.upload(gray)
            
            # 使用正确的CUDA Sobel滤波器
            ksize = final_params['ksize']
            
            # 根据方向计算Sobel梯度
            if final_params['direction'] == 'horizontal':
                # 检测水平方向的灰度变化（垂直边缘）
                sobel_filter = cv2.cuda.createSobelFilter(cv2.CV_8UC1, cv2.CV_32F, 1, 0, ksize)
                gpu_sobel = sobel_filter.apply(gpu_gray)
                sobel = gpu_sobel.download().astype(np.float64)
                
            elif final_params['direction'] == 'vertical':
                # 检测垂直方向的灰度变化（水平边缘）
                sobel_filter = cv2.cuda.createSobelFilter(cv2.CV_8UC1, cv2.CV_32F, 0, 1, ksize)
                gpu_sobel = sobel_filter.apply(gpu_gray)
                sobel = gpu_sobel.download().astype(np.float64)
                
            elif final_params['direction'] == 'both':
                # 两个方向都检测
                sobel_filter_x = cv2.cuda.createSobelFilter(cv2.CV_8UC1, cv2.CV_32F, 1, 0, ksize)
                sobel_filter_y = cv2.cuda.createSobelFilter(cv2.CV_8UC1, cv2.CV_32F, 0, 1, ksize)
                
                gpu_sobelx = sobel_filter_x.apply(gpu_gray)
                gpu_sobely = sobel_filter_y.apply(gpu_gray)
                
                # 🚀 完全在GPU上计算magnitude
                # try:
                #     # 方法1: 使用OpenCV CUDA的magnitude函数（推荐）
                #     gpu_magnitude = cv2.cuda.magnitude(gpu_sobelx, gpu_sobely)
                #     sobel = gpu_magnitude.download().astype(np.float64)
                #     print("✅ GPU magnitude函数计算成功")
                    
                # except Exception as e:
                #     print(f"⚠️  magnitude函数不可用: {str(e)}")
                try:
                    # 方法2: 手动GPU计算
                    gpu_sobelx_squared = cv2.cuda.multiply(gpu_sobelx, gpu_sobelx)
                    gpu_sobely_squared = cv2.cuda.multiply(gpu_sobely, gpu_sobely)
                    gpu_sum_squares = cv2.cuda.add(gpu_sobelx_squared, gpu_sobely_squared)
                    gpu_magnitude = cv2.cuda.sqrt(gpu_sum_squares)
                    sobel = gpu_magnitude.download().astype(np.float64)
                    print("✅ GPU手动magnitude计算成功")
                    
                except Exception as e2:
                    print(f"⚠️  GPU计算失败，回退CPU: {str(e2)}")
                    # 回退到CPU
                    sobelx = gpu_sobelx.download().astype(np.float64)
                    sobely = gpu_sobely.download().astype(np.float64)
                    sobel = np.sqrt(sobelx**2 + sobely**2)
            else:
                raise ValueError("direction 参数必须是 'horizontal', 'vertical' 或 'both'")
                
        except Exception as e:
            print(f"⚠️  CUDA Sobel计算失败，回退到CPU: {str(e)}")
            use_cuda = False
    
    # CPU回退的Sobel梯度计算
    if not use_cuda:
        if final_params['direction'] == 'horizontal':
            # 检测水平方向的灰度变化（垂直边缘）
            sobel = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
        elif final_params['direction'] == 'vertical':
            # 检测垂直方向的灰度变化（水平边缘）
            sobel = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
        elif final_params['direction'] == 'both':
            # 两个方向都检测
            sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=final_params['ksize'])
            sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=final_params['ksize'])
            sobel = np.sqrt(sobelx**2 + sobely**2)
        else:
            raise ValueError("direction 参数必须是 'horizontal', 'vertical' 或 'both'")
    
    # 根据边缘类型处理梯度
    edge_type = final_params['edge_type'].lower()
    
    if edge_type == 'positive':
        # 只保留正值（从暗到亮的变化）
        edges = np.where(sobel > 0, sobel, 0)
    elif edge_type == 'negative':
        # 只保留负值（从亮到暗的变化）
        edges = np.where(sobel < 0, -sobel, 0)
    elif edge_type == 'all':
        # 保留所有边缘（取绝对值）
        edges = np.absolute(sobel)
    else:
        raise ValueError("edge_type 参数必须是 'all', 'positive' 或 'negative'")
    
    # 归一化处理
    if final_params['normalize']:
        if edges.max() > 0:
            edges = edges / edges.max() * 255
    
    # 转换为uint8
    edges = np.uint8(np.clip(edges, 0, 255))
    
    # 可选的二值化处理
    if final_params['apply_threshold']:
        if use_cuda:
            try:
                # CUDA加速的阈值处理
                gpu_edges = cv2.cuda_GpuMat()
                gpu_edges.upload(edges)
                _, gpu_result = cv2.cuda.threshold(
                    gpu_edges, 
                    final_params['threshold'], 
                    final_params['max_value'], 
                    final_params['threshold_type']
                )
                edges = gpu_result.download()
            except Exception as e:
                print(f"⚠️  CUDA阈值处理失败，回退到CPU: {str(e)}")
                _, edges = cv2.threshold(
                    edges, 
                    final_params['threshold'], 
                    final_params['max_value'], 
                    final_params['threshold_type']
                )
        else:
            _, edges = cv2.threshold(
                edges, 
                final_params['threshold'], 
                final_params['max_value'], 
                final_params['threshold_type']
            )
    
    return edges
def normalize_image_for_display(image):
    """
    将图像标准化为适合显示的格式
    
    Args:
        image: 输入图像，任意数据类型
        
    Returns:
        numpy.ndarray: uint8格式的图像，像素值范围0-255
    """
    if image is None:
        return None
    
    # 获取图像信息
    dtype = image.dtype

    
    # uint8 图像直接返回
    if dtype == np.uint8:
        return image.copy()
    
    # 处理单通道图像
    if len(image.shape) == 2:
        # 单通道图像：直接计算99%分位数
        p1 = np.percentile(image, 1)
        p99 = np.percentile(image, 99)
        

        
        if p99 > p1:
            clipped_image = np.clip(image.astype(np.float64), p1, p99)
            normalized = ((clipped_image - p1) / (p99 - p1) * 255.0).astype(np.uint8)

        else:
            normalized = np.zeros_like(image, dtype=np.uint8)

    
    # 处理多通道图像
    elif len(image.shape) == 3:
        # 多通道图像：分别计算每个通道的99%分位数
        channels = []
        height, width, num_channels = image.shape
        

        
        for c in range(num_channels):
            channel = image[:, :, c]
            
            # 计算当前通道的1%和99%分位数
            p1 = np.percentile(channel, 3)
            p99 = np.percentile(channel, 97)
            
            
            
            if p99 > p1:
                # 对当前通道进行映射
                clipped_channel = np.clip(channel.astype(np.float64), p1, p99)
                normalized_channel = ((clipped_channel - p1) / (p99 - p1) * 255.0).astype(np.uint8)
            else:
                # 常数通道
                normalized_channel = np.zeros_like(channel, dtype=np.uint8)
                
            
            channels.append(normalized_channel)
        
        # 合并所有通道
        normalized = np.stack(channels, axis=2)
        
    
    else:
        # 其他维度的图像
        
        # 展平处理
        p1 = np.percentile(image, 1)
        p99 = np.percentile(image, 99)
        
        if p99 > p1:
            clipped_image = np.clip(image.astype(np.float64), p1, p99)
            normalized = ((clipped_image - p1) / (p99 - p1) * 255.0).astype(np.uint8)
        else:
            normalized = np.zeros_like(image, dtype=np.uint8)
    

    return normalized


def enhance_center_stretch_image_ex(image, center_image, center_value=50, alpha=1.0, 
                                 dir='horizontal', start_w=5, end_w=5, gray_diff=0
):
    """
    中心拉伸 - 在垂直均值和水平均值计算时也应用掐头去尾
    """
    use_data = 0
    center_mean = None
    max_w = 0
    
    if dir == 'horizontal':
        max_w = center_image.shape[1]
        if (start_w + end_w) > center_image.shape[0]:
            start_w = 0
            end_w = 0
    else:
        max_w = center_image.shape[0]
        if (start_w + end_w) > center_image.shape[1]:
            start_w = 0
            end_w = 0        
    
    if len(center_image.shape) == 3:
        # 彩色图像，计算每个通道的平均值,使用掐头去尾
        height, width, channels = center_image.shape
        channel_means = []
        
        for i in range(channels):
            # 获取当前通道的所有像素值
            channel_data = center_image[:, :, i]
            
            # 将通道数据展平并排序（按灰度值排序）
            sorted_pixels = np.sort(channel_data.flatten())
            total_pixels = len(sorted_pixels)
            
            if start_w + end_w >= total_pixels:
                # 如果要去掉的像素数量太多，就使用所有像素
                channel_mean = np.mean(sorted_pixels)
            else:
                # 去掉最小的start_w个和最大的end_w个像素值，计算剩余的平均值
                trimmed_pixels = sorted_pixels[start_w:-end_w] if end_w > 0 else sorted_pixels[start_w:]
                channel_mean = np.mean(trimmed_pixels)
            
            channel_mean = channel_mean + gray_diff
            #print(f"通道{i}: 总像素{total_pixels}, 去掉前{start_w}后{end_w}, 剩余{len(trimmed_pixels) if 'trimmed_pixels' in locals() else total_pixels}, 均值{channel_mean:.2f}")
            channel_means.append(channel_mean)
            
        center_mean = channel_means
    else:
        # 灰度图像
        # 将所有像素值展平并排序
        sorted_pixels = np.sort(center_image.flatten())
        total_pixels = len(sorted_pixels)
        
        if start_w + end_w >= total_pixels:
            channel_mean = np.mean(sorted_pixels)
        else:
            # 去掉最小的start_w个和最大的end_w个像素值
            trimmed_pixels = sorted_pixels[start_w:-end_w] if end_w > 0 else sorted_pixels[start_w:]
            channel_mean = np.mean(trimmed_pixels)
        
        channel_mean = channel_mean + gray_diff
        center_mean = channel_mean
    
    # 计算掐头去尾的均值函数
    def calculate_trimmed_mean(data, start_w, end_w):
        """计算掐头去尾的均值"""
        if len(data) == 0:
            return 0
        
        sorted_data = np.sort(data.flatten())
        total_len = len(sorted_data)
        
        if start_w + end_w >= total_len:
            return np.mean(sorted_data)
        else:
            trimmed_data = sorted_data[start_w:-end_w] if end_w > 0 else sorted_data[start_w:]
            return np.mean(trimmed_data)
    
    # 后续处理 - 在计算均值时也应用掐头去尾
    if dir == 'horizontal':
        # 水平均值 - 按行应用掐头去尾
        if len(center_image.shape) == 3:
            # 彩色图像，对每个通道单独处理
            center_height, center_width, center_channels = center_image.shape
            horizontal_data = np.zeros((1, center_width, center_channels))
            
            for i in range(3):
                center_channel = center_image[:,:,i]
                # 对每一列应用掐头去尾的均值计算
                trimmed_means = []
                for col in range(center_width):
                    column_data = center_channel[:, col]
                    trimmed_mean = calculate_trimmed_mean(column_data, start_w, end_w)
                    trimmed_means.append(trimmed_mean)
                
                center = np.array(trimmed_means).reshape(1, -1)
                center_value0 = center_value
                if center_mean is not None:
                    center_value0 = center_mean[i]
                center = center - center_value0
                horizontal_data[:,:,i] = center          
            use_data = horizontal_data
        else:
            # 灰度图像
            center_height, center_width = center_image.shape
            trimmed_means = []
            for col in range(center_width):
                column_data = center_image[:, col]
                trimmed_mean = calculate_trimmed_mean(column_data, start_w, end_w)
                trimmed_means.append(trimmed_mean)
            
            horizontal_data = np.array(trimmed_means).reshape(1, -1)
            center_value0 = center_value
            if center_mean is not None:
                center_value0 = center_mean
            horizontal_data = horizontal_data - center_value0
            use_data = horizontal_data
            
    elif dir == 'vertical':
        # 垂直均值 - 按列应用掐头去尾
        if len(center_image.shape) == 3:
            # 彩色图像，对每个通道单独处理
            center_height, center_width, center_channels = center_image.shape
            vertical_data = np.zeros((center_height, 1, center_channels))
            
            for i in range(3):
                center_channel = center_image[:,:,i]
                # 对每一行应用掐头去尾的均值计算
                trimmed_means = []
                for row in range(center_height):
                    row_data = center_channel[row, :]
                    trimmed_mean = calculate_trimmed_mean(row_data, start_w, end_w)
                    trimmed_means.append(trimmed_mean)
                
                center = np.array(trimmed_means).reshape(-1, 1)
                center_value0 = center_value
                if center_mean is not None:
                    center_value0 = center_mean[i]
                center = center - center_value0
                vertical_data[:,:,i] = center  
            use_data = vertical_data
        else:
            # 灰度图像
            center_height, center_width = center_image.shape
            trimmed_means = []
            for row in range(center_height):
                row_data = center_image[row, :]
                trimmed_mean = calculate_trimmed_mean(row_data, start_w, end_w)
                trimmed_means.append(trimmed_mean)
            
            vertical_data = np.array(trimmed_means).reshape(-1, 1)
            center_value0 = center_value
            if center_mean is not None:
                center_value0 = center_mean
            vertical_data = vertical_data - center_value0
            use_data = vertical_data
    else:
        # 默认水平方向 - 按行应用掐头去尾
        if len(center_image.shape) == 3:
            center_height, center_width, center_channels = center_image.shape
            horizontal_data = np.zeros((1, center_width, center_channels))
            
            for i in range(3):
                center_channel = center_image[:,:,i]
                # 对每一列应用掐头去尾的均值计算
                trimmed_means = []
                for col in range(center_width):
                    column_data = center_channel[:, col]
                    trimmed_mean = calculate_trimmed_mean(column_data, start_w, end_w)
                    trimmed_means.append(trimmed_mean)
                
                center = np.array(trimmed_means).reshape(1, -1)
                center_value0 = center_value
                if center_mean is not None:
                    center_value0 = center_mean[i]
                center = center - center_value0
                horizontal_data[:,:,i] = center          
            use_data = horizontal_data
        else:
            center_height, center_width = center_image.shape
            trimmed_means = []
            for col in range(center_width):
                column_data = center_image[:, col]
                trimmed_mean = calculate_trimmed_mean(column_data, start_w, end_w)
                trimmed_means.append(trimmed_mean)
            
            horizontal_data = np.array(trimmed_means).reshape(1, -1)
            center_value0 = center_value
            if center_mean is not None:
                center_value0 = center_mean
            horizontal_data = horizontal_data - center_value0
            use_data = horizontal_data

    if len(image.shape) == 3:
        # 彩色图像，对每个通道单独处理
        result = np.zeros_like(image)
        for i in range(3):
            channel = image[:,:,i].astype(np.float32)
            use_data0 = use_data[:,:,i].astype(np.float32)
            new_channel = channel - (alpha * use_data0)
            # 拉伸到0-255范围
            new_channel = np.clip(new_channel, 0, 255)
            result[:,:,i] = new_channel.astype(np.uint8)
        use_data = use_data.astype(np.uint8)
        return result, center_mean
    else:
        # 灰度图像
        channel = image.astype(np.float32)
        new_channel = channel - (alpha * use_data)
        new_channel = np.clip(new_channel, 0, 255)
        result = new_channel.astype(np.uint8)
        use_data = use_data.astype(np.uint8)
        return result, center_mean
        

def enhance_center_stretch_image_ex2(image, center_image, center_value=50, alpha=1.0, 
                                     dir='horizontal', start_w=5, end_w=5, gray_diff=0
):
    """
    中心拉伸 - 支持多种数据类型，在垂直均值和水平均值计算时也应用掐头去尾
    修正版本，确保输入输出接口不变
    
    Args:
        image: 输入图像，支持各种数据类型（uint8, uint16, int16, float32, float64等）
        center_image: 中心图像，用于计算基准值
        center_value: 中心值，默认50
        alpha: 拉伸强度，默认1.0
        dir: 方向，'horizontal'或'vertical'，默认'horizontal'
        start_w: 掐头去尾的起始像素数，默认5
        end_w: 掐头去尾的结束像素数，默认5
        gray_diff: 灰度差值调整，默认0
        
    Returns:
        tuple: (result_image, center_mean) - 处理后的图像和中心均值
    """
    if image is None or center_image is None:
        return None, None
    
    # 记录原始数据类型和范围信息
    original_dtype = image.dtype
    original_shape = image.shape
    
    # 将所有输入转换为float64进行计算，保证精度
    image_float = image.astype(np.float64)
    center_image_float = center_image.astype(np.float64)
    
    use_data = 0
    center_mean = None
    max_w = 0
    
    # 参数边界检查
    if dir == 'horizontal':
        max_w = center_image_float.shape[1]
        if (start_w + end_w) > center_image_float.shape[0]:
            start_w = 0
            end_w = 0
    else:
        max_w = center_image_float.shape[0]
        if (start_w + end_w) > center_image_float.shape[1]:
            start_w = 0
            end_w = 0        
    
    # 计算全局中心均值（使用掐头去尾）
    if len(center_image_float.shape) == 3:
        # 彩色图像，计算每个通道的平均值，使用掐头去尾
        height, width, channels = center_image_float.shape
        channel_means = []
        
        for i in range(channels):
            # 获取当前通道的所有像素值
            channel_data = center_image_float[:, :, i]
            
            # 将通道数据展平并排序（按灰度值排序）
            sorted_pixels = np.sort(channel_data.flatten())
            total_pixels = len(sorted_pixels)
            
            if start_w + end_w >= total_pixels:
                # 如果要去掉的像素数量太多，就使用所有像素
                channel_mean = np.mean(sorted_pixels)
            else:
                # 去掉最小的start_w个和最大的end_w个像素值，计算剩余的平均值
                trimmed_pixels = sorted_pixels[start_w:-end_w] if end_w > 0 else sorted_pixels[start_w:]
                channel_mean = np.mean(trimmed_pixels)
            
            channel_mean = channel_mean + gray_diff
            channel_means.append(channel_mean)
            
        center_mean = channel_means
    else:
        # 灰度图像
        # 将所有像素值展平并排序
        sorted_pixels = np.sort(center_image_float.flatten())
        total_pixels = len(sorted_pixels)
        
        if start_w + end_w >= total_pixels:
            channel_mean = np.mean(sorted_pixels)
        else:
            # 去掉最小的start_w个和最大的end_w个像素值
            trimmed_pixels = sorted_pixels[start_w:-end_w] if end_w > 0 else sorted_pixels[start_w:]
            channel_mean = np.mean(trimmed_pixels)
        
        channel_mean = channel_mean + gray_diff
        center_mean = channel_mean
    
    # 计算掐头去尾的均值函数
    def calculate_trimmed_mean(data, start_w, end_w):
        """计算掐头去尾的均值"""
        if len(data) == 0:
            return 0.0
        
        sorted_data = np.sort(data.flatten())
        total_len = len(sorted_data)
        
        if start_w + end_w >= total_len:
            return float(np.mean(sorted_data))
        else:
            trimmed_data = sorted_data[start_w:-end_w] if end_w > 0 else sorted_data[start_w:]
            return float(np.mean(trimmed_data))
    
    # 后续处理 - 在计算均值时也应用掐头去尾
    if dir == 'horizontal':
        # 水平均值 - 按行应用掐头去尾
        if len(center_image_float.shape) == 3:
            # 彩色图像，对每个通道单独处理
            center_height, center_width, center_channels = center_image_float.shape
            horizontal_data = np.zeros((1, center_width, center_channels), dtype=np.float64)
            
            for i in range(3):
                center_channel = center_image_float[:,:,i]
                # 对每一列应用掐头去尾的均值计算
                trimmed_means = []
                for col in range(center_width):
                    column_data = center_channel[:, col]
                    trimmed_mean = calculate_trimmed_mean(column_data, start_w, end_w)
                    trimmed_means.append(trimmed_mean)
                
                center = np.array(trimmed_means, dtype=np.float64).reshape(1, -1)
                center_value0 = float(center_value)
                if center_mean is not None:
                    center_value0 = float(center_mean[i])
                center = center - center_value0
                horizontal_data[:,:,i] = center          
            use_data = horizontal_data
        else:
            # 灰度图像
            center_height, center_width = center_image_float.shape
            trimmed_means = []
            for col in range(center_width):
                column_data = center_image_float[:, col]
                trimmed_mean = calculate_trimmed_mean(column_data, start_w, end_w)
                trimmed_means.append(trimmed_mean)
            
            horizontal_data = np.array(trimmed_means, dtype=np.float64).reshape(1, -1)
            center_value0 = float(center_value)
            if center_mean is not None:
                center_value0 = float(center_mean)
            horizontal_data = horizontal_data - center_value0
            use_data = horizontal_data
            
    elif dir == 'vertical':
        # 垂直均值 - 按列应用掐头去尾
        if len(center_image_float.shape) == 3:
            # 彩色图像，对每个通道单独处理
            center_height, center_width, center_channels = center_image_float.shape
            vertical_data = np.zeros((center_height, 1, center_channels), dtype=np.float64)
            
            for i in range(3):
                center_channel = center_image_float[:,:,i]
                # 对每一行应用掐头去尾的均值计算
                trimmed_means = []
                for row in range(center_height):
                    row_data = center_channel[row, :]
                    trimmed_mean = calculate_trimmed_mean(row_data, start_w, end_w)
                    trimmed_means.append(trimmed_mean)
                
                center = np.array(trimmed_means, dtype=np.float64).reshape(-1, 1)
                center_value0 = float(center_value)
                if center_mean is not None:
                    center_value0 = float(center_mean[i])
                center = center - center_value0
                vertical_data[:,:,i] = center  
            use_data = vertical_data
        else:
            # 灰度图像
            center_height, center_width = center_image_float.shape
            trimmed_means = []
            for row in range(center_height):
                row_data = center_image_float[row, :]
                trimmed_mean = calculate_trimmed_mean(row_data, start_w, end_w)
                trimmed_means.append(trimmed_mean)
            
            vertical_data = np.array(trimmed_means, dtype=np.float64).reshape(-1, 1)
            center_value0 = float(center_value)
            if center_mean is not None:
                center_value0 = float(center_mean)
            vertical_data = vertical_data - center_value0
            use_data = vertical_data
    else:
        # 默认水平方向 - 按行应用掐头去尾
        if len(center_image_float.shape) == 3:
            center_height, center_width, center_channels = center_image_float.shape
            horizontal_data = np.zeros((1, center_width, center_channels), dtype=np.float64)
            
            for i in range(3):
                center_channel = center_image_float[:,:,i]
                # 对每一列应用掐头去尾的均值计算
                trimmed_means = []
                for col in range(center_width):
                    column_data = center_channel[:, col]
                    trimmed_mean = calculate_trimmed_mean(column_data, start_w, end_w)
                    trimmed_means.append(trimmed_mean)
                
                center = np.array(trimmed_means, dtype=np.float64).reshape(1, -1)
                center_value0 = float(center_value)
                if center_mean is not None:
                    center_value0 = float(center_mean[i])
                center = center - center_value0
                horizontal_data[:,:,i] = center          
            use_data = horizontal_data
        else:
            center_height, center_width = center_image_float.shape
            trimmed_means = []
            for col in range(center_width):
                column_data = center_image_float[:, col]
                trimmed_mean = calculate_trimmed_mean(column_data, start_w, end_w)
                trimmed_means.append(trimmed_mean)
            
            horizontal_data = np.array(trimmed_means, dtype=np.float64).reshape(1, -1)
            center_value0 = float(center_value)
            if center_mean is not None:
                center_value0 = float(center_mean)
            horizontal_data = horizontal_data - center_value0
            use_data = horizontal_data

    # 执行中心拉伸计算（使用float64精度）
    if len(image_float.shape) == 3:
        # 彩色图像，对每个通道单独处理
        result_float = np.zeros_like(image_float, dtype=np.float64)
        for i in range(3):
            channel = image_float[:,:,i]
            use_data0 = use_data[:,:,i]
            new_channel = channel - (float(alpha) * use_data0)
            result_float[:,:,i] = new_channel
    else:
        # 灰度图像
        result_float = image_float - (float(alpha) * use_data)
    
    # 根据原始数据类型进行适当的范围限制和类型转换
    if original_dtype == np.uint8:
        # uint8: 0-255
        result = np.clip(result_float, 0, 255).astype(np.uint8)
    elif original_dtype == np.uint16:
        # uint16: 0-65535
        result = np.clip(result_float, 0, 65535).astype(np.uint16)
    elif original_dtype == np.int16:
        # int16: -32768 to 32767
        result = np.clip(result_float, -32768, 32767).astype(np.int16)
    elif original_dtype in [np.float32, np.float64]:
        # 浮点数：保持原始范围，不进行限制（或者基于输入图像的范围来限制）
        if original_dtype == np.float32:
            result = result_float.astype(np.float32)
        else:
            result = result_float
    else:
        # 其他类型，尝试转换为原始类型
        try:
            if np.issubdtype(original_dtype, np.integer):
                # 整数类型
                info = np.iinfo(original_dtype)
                result = np.clip(result_float, info.min, info.max).astype(original_dtype)
            elif np.issubdtype(original_dtype, np.floating):
                # 浮点类型
                result = result_float.astype(original_dtype)
            else:
                # 未知类型，转换为float32
                result = result_float.astype(np.float32)
        except Exception:
            # 转换失败，使用float32
            result = result_float.astype(np.float32)
    
    # 确保输出形状与输入一致
    assert result.shape == original_shape, f"输出形状 {result.shape} 与输入形状 {original_shape} 不匹配"
    
    return result, center_mean



def append_dict_info_to_image(image: np.ndarray, info_dict: dict, font_scale: float = None, line_spacing: int = None, debug_enabled: bool = False) -> np.ndarray:
    """
    在图片下方拼接黑底白字的字典信息
    
    Args:
        image: 输入图像 (BGR格式)
        info_dict: 要显示的字典信息
        font_scale: 字体大小，None时自动计算
        line_spacing: 行间距，None时自动计算
        debug_enabled: 是否启用调试信息
        
    Returns:
        np.ndarray: 拼接后的图像
    """
    def create_error_image(original_image, error_message):
        """创建包含错误信息的图像"""
        try:
            if original_image is not None:
                img_height, img_width = original_image.shape[:2]
            else:
                # 如果原图像也是None，创建一个默认大小的黑色图像
                img_height, img_width = 480, 640
                original_image = np.zeros((img_height, img_width, 3), dtype=np.uint8)
            
            # 创建错误信息文本区域
            font = cv2.FONT_HERSHEY_SIMPLEX
            error_font_scale = max(0.6, min(1.2, img_width / 800))
            font_thickness = max(1, int(2 * error_font_scale))
            error_line_spacing = int(35 * error_font_scale)
            
            # 将错误信息分行
            error_lines = []
            error_lines.append("❌ ERROR:")
            
            # 将长错误信息分割成多行
            max_chars_per_line = max(30, img_width // 12)  # 根据图像宽度调整每行字符数
            words = str(error_message).split(' ')
            current_line = ""
            
            for word in words:
                if len(current_line + " " + word) <= max_chars_per_line:
                    current_line += (" " + word) if current_line else word
                else:
                    if current_line:
                        error_lines.append("   " + current_line)
                    current_line = word
            
            if current_line:
                error_lines.append("   " + current_line)
            
            # 计算错误文本区域所需高度
            error_text_height = len(error_lines) * error_line_spacing + 20
            
            # 创建黑色背景的错误文本区域
            error_area = np.zeros((error_text_height, img_width, 3), dtype=np.uint8)
            
            # 在错误区域上绘制红色错误文字
            y_offset = error_line_spacing
            for line in error_lines:
                if line.startswith("❌"):
                    # 错误标题用红色
                    color = (0, 0, 255)  # 红色
                else:
                    # 错误内容用白色
                    color = (255, 255, 255)  # 白色
                
                cv2.putText(
                    error_area, 
                    line, 
                    (10, y_offset), 
                    font, 
                    error_font_scale, 
                    color,
                    font_thickness, 
                    cv2.LINE_AA
                )
                y_offset += error_line_spacing
            
            # 拼接原图像和错误信息
            combined_image = np.vstack([original_image, error_area])
            return combined_image
            
        except Exception as e:
            # 如果连错误处理都失败了，创建一个简单的错误图像
            fallback_image = np.zeros((200, 400, 3), dtype=np.uint8)
            cv2.putText(fallback_image, "CRITICAL ERROR", (10, 50), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
            cv2.putText(fallback_image, "Cannot display error", (10, 100), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
            cv2.putText(fallback_image, str(e)[:50], (10, 150), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1)
            return fallback_image
    
    try:
        # 输入验证
        if image is None:
            return create_error_image(None, "Input image is None")
        
        if info_dict is None:
            return create_error_image(image, "Input dictionary is None")
        
        if not isinstance(info_dict, dict):
            return create_error_image(image, f"Input is not a dictionary, got: {type(info_dict)}")
        
        if len(info_dict) == 0:
            return create_error_image(image, "Input dictionary is empty")
        
        img_height, img_width = image.shape[:2]
        
        # 自动计算字体大小和行间距
        if font_scale is None:
            font_scale = max(0.4, min(1.2, img_width / 1000))
        
        if line_spacing is None:
            line_spacing = int(30 * font_scale)
        
        # 递归处理嵌套字典，转换为文本行
        def dict_to_text_lines(d: dict, prefix: str = "", max_depth: int = 3) -> list:
            """将字典转换为文本行列表"""
            lines = []
            if max_depth <= 0:
                return [f"{prefix}..."]  # 防止过深嵌套
                
            try:
                for key, value in d.items():
                    try:
                        if isinstance(value, dict):
                            # 嵌套字典
                            lines.append(f"{prefix}{key}:")
                            sub_lines = dict_to_text_lines(value, prefix + "  ", max_depth - 1)
                            lines.extend(sub_lines)
                        elif isinstance(value, (list, tuple)):
                            # 列表或元组
                            if len(value) <= 5:  # 短列表直接显示
                                lines.append(f"{prefix}{key}: {value}")
                            else:  # 长列表显示前几个元素
                                lines.append(f"{prefix}{key}: [{value[0]}, {value[1]}, ..., {value[-1]}] (共{len(value)}项)")
                        else:
                            # 基本类型
                            if isinstance(value, float):
                                lines.append(f"{prefix}{key}: {value:.3f}")
                            else:
                                lines.append(f"{prefix}{key}: {value}")
                    except Exception as e:
                        lines.append(f"{prefix}{key}: <Error: {str(e)}>")
                        
            except Exception as e:
                lines.append(f"{prefix}<Dict processing error: {str(e)}>")
                
            return lines
        
        # 转换字典为文本行
        text_lines = dict_to_text_lines(info_dict)
        
        if not text_lines:
            return create_error_image(image, "Failed to convert dictionary to text lines")
        
        # 计算文本区域所需的高度
        font = cv2.FONT_HERSHEY_SIMPLEX
        font_thickness = max(1, int(2 * font_scale))
        
        # 获取文本尺寸
        max_text_width = 0
        text_heights = []
        
        for line in text_lines:
            try:
                # 限制单行长度，防止过长
                if len(line) > 100:  # 限制最大字符数
                    line = line[:97] + "..."
                
                text_size = cv2.getTextSize(line, font, font_scale, font_thickness)[0]
                max_text_width = max(max_text_width, text_size[0])
                text_heights.append(text_size[1])
            except Exception as e:
                if debug_enabled:
                    print(f"Warning: Failed to get text size for line: {line[:50]}..., error: {str(e)}")
                # 使用默认尺寸
                max_text_width = max(max_text_width, len(line) * int(font_scale * 10))
                text_heights.append(int(font_scale * 20))
        
        # 计算文本区域总高度
        total_text_height = len(text_lines) * line_spacing + 20  # 添加上下边距
        
        # 确保文本区域不会太窄
        text_area_width = max(img_width, max_text_width + 40)  # 添加左右边距
        
        # 创建黑色背景的文本区域
        text_area = np.zeros((total_text_height, text_area_width, 3), dtype=np.uint8)
        
        # 在文本区域上绘制白色文字
        y_offset = line_spacing
        for i, line in enumerate(text_lines):
            try:
                # 限制单行长度
                if len(line) > 100:
                    line = line[:97] + "..."
                
                # 计算文本位置
                x_pos = 20  # 左边距
                y_pos = y_offset
                
                # 绘制文本
                cv2.putText(
                    text_area, 
                    line, 
                    (x_pos, y_pos), 
                    font, 
                    font_scale, 
                    (255, 255, 255),  # 白色文字
                    font_thickness, 
                    cv2.LINE_AA
                )
                
                y_offset += line_spacing
                
            except Exception as e:
                if debug_enabled:
                    print(f"Warning: Failed to draw line {i}: {str(e)}")
                # 绘制错误提示
                cv2.putText(
                    text_area, 
                    f"<Line {i} error>", 
                    (20, y_offset), 
                    font, 
                    font_scale, 
                    (0, 255, 255),  # 黄色文字
                    font_thickness, 
                    cv2.LINE_AA
                )
                y_offset += line_spacing
        
        # 处理图像和文本区域宽度不一致的情况
        if img_width != text_area_width:
            try:
                if img_width > text_area_width:
                    # 图像更宽，扩展文本区域
                    padding = img_width - text_area_width
                    text_area = cv2.copyMakeBorder(
                        text_area, 0, 0, 0, padding, 
                        cv2.BORDER_CONSTANT, value=(0, 0, 0)
                    )
                else:
                    # 文本区域更宽，扩展图像
                    padding = text_area_width - img_width
                    image = cv2.copyMakeBorder(
                        image, 0, 0, 0, padding, 
                        cv2.BORDER_CONSTANT, value=(0, 0, 0)
                    )
            except Exception as e:
                if debug_enabled:
                    print(f"Warning: Failed to handle width mismatch: {str(e)}")
                # 如果处理失败，裁剪到较小的尺寸
                min_width = min(img_width, text_area_width)
                image = image[:, :min_width]
                text_area = text_area[:, :min_width]
        
        # 垂直拼接图像和文本区域
        combined_image = np.vstack([image, text_area])
        
        if debug_enabled:
            print(f"📝 字典信息拼接完成:")
            print(f"   原图尺寸: {img_width}x{img_height}")
            print(f"   文本区域尺寸: {text_area_width}x{total_text_height}")
            print(f"   最终图像尺寸: {combined_image.shape[1]}x{combined_image.shape[0]}")
            print(f"   文本行数: {len(text_lines)}")
            print(f"   字体大小: {font_scale}")
        
        return combined_image
        
    except Exception as e:
        # 捕获所有其他异常
        error_msg = f"Unexpected error in append_dict_info_to_image: {str(e)}"
        if debug_enabled:
            print(f"❌ {error_msg}")
            import traceback
            traceback.print_exc()
        
        return create_error_image(image, error_msg)


def append_simple_dict_info(image: np.ndarray, info_dict: dict, debug_enabled: bool = False) -> np.ndarray:
    """
    简化版本：在图片下方拼接关键信息
    
    只显示字典的第一层信息，适合快速预览
    """
    def create_simple_error_image(original_image, error_message):
        """创建简单的错误信息图像"""
        try:
            if original_image is not None:
                img_height, img_width = original_image.shape[:2]
            else:
                img_height, img_width = 300, 500
                original_image = np.zeros((img_height, img_width, 3), dtype=np.uint8)
            
            error_area = np.zeros((80, img_width, 3), dtype=np.uint8)
            cv2.putText(error_area, "ERROR:", (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
            cv2.putText(error_area, str(error_message)[:60], (10, 55), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1)
            
            return np.vstack([original_image, error_area])
        except:
            # 最终回退
            fallback = np.zeros((150, 300, 3), dtype=np.uint8)
            cv2.putText(fallback, "CRITICAL ERROR", (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
            return fallback
    
    try:
        if image is None or info_dict is None:
            return create_simple_error_image(image, "Input is None")
            
        if not isinstance(info_dict, dict):
            return create_simple_error_image(image, f"Not a dict: {type(info_dict)}")
            
        img_height, img_width = image.shape[:2]
        
        # 简化版本：只显示第一层键值对
        simple_lines = []
        try:
            for key, value in info_dict.items():
                try:
                    if isinstance(value, dict):
                        simple_lines.append(f"{key}: {{Dict,{len(value)}items}}")
                    elif isinstance(value, (list, tuple)):
                        simple_lines.append(f"{key}: [List,{len(value)}items]")
                    elif isinstance(value, float):
                        simple_lines.append(f"{key}: {value:.3f}")
                    else:
                        # 限制值的长度
                        value_str = str(value)
                        if len(value_str) > 40:
                            value_str = value_str[:37] + "..."
                        simple_lines.append(f"{key}: {value_str}")
                except Exception as e:
                    simple_lines.append(f"{key}: <Error: {str(e)[:20]}>")
        except Exception as e:
            return create_simple_error_image(image, f"Dict processing error: {str(e)}")
        
        if not simple_lines:
            simple_lines = ["<Empty or invalid dictionary>"]
        
        # 计算所需高度
        font_scale = max(0.5, min(1.0, img_width / 1200))
        line_spacing = int(25 * font_scale)
        total_height = len(simple_lines) * line_spacing + 20
        
        # 创建文本区域
        text_area = np.zeros((total_height, img_width, 3), dtype=np.uint8)
        
        # 绘制文本
        font = cv2.FONT_HERSHEY_SIMPLEX
        font_thickness = max(1, int(2 * font_scale))
        y_offset = line_spacing
        
        for line in simple_lines:
            try:
                # 限制行长度
                if len(line) > 80:
                    line = line[:77] + "..."
                    
                cv2.putText(
                    text_area, 
                    line, 
                    (10, y_offset), 
                    font, 
                    font_scale, 
                    (255, 255, 255), 
                    font_thickness, 
                    cv2.LINE_AA
                )
                y_offset += line_spacing
            except Exception as e:
                if debug_enabled:
                    print(f"Warning: Failed to draw simple line: {str(e)}")
        
        # 拼接图像
        return np.vstack([image, text_area])
        
    except Exception as e:
        error_msg = f"Simple dict error: {str(e)}"
        if debug_enabled:
            print(f"❌ {error_msg}")
        return create_simple_error_image(image, error_msg)


def concatenate_images_with_padding(image_list, direction='horizontal', gap_size=10, 
                                  align='center', fill_color=(0, 0, 0), 
                                  debug_enabled=False):
    """
    将多张图片拼接到一起，以最大图为基础，小图添加黑边补齐
    
    Args:
        image_list: 图像列表 [image1, image2, image3, ...]
        direction: 拼接方向 ('horizontal' 或 'vertical')
        gap_size: 图像间的黑条宽度（像素）
        align: 对齐方式
            - horizontal拼接时: 'top', 'center', 'bottom'
            - vertical拼接时: 'left', 'center', 'right'
        fill_color: 填充颜色，默认黑色 (B, G, R)
        debug_enabled: 是否启用调试信息
        
    Returns:
        np.ndarray: 拼接后的图像
    """
    try:
        # 输入验证
        if not image_list or len(image_list) == 0:
            raise ValueError("图像列表不能为空")
        
        # 过滤None图像
        valid_images = [img for img in image_list if img is not None]
        if not valid_images:
            raise ValueError("没有有效的图像")
            
        # 如果只有一张图，直接返回
        if len(valid_images) == 1:
            return valid_images[0].copy()
        
        # 获取所有图像的尺寸信息
        image_info = []
        max_height = 0
        max_width = 0
        
        for i, img in enumerate(valid_images):
            height, width = img.shape[:2]
            channels = img.shape[2] if len(img.shape) == 3 else 1
            image_info.append({
                'index': i,
                'image': img,
                'height': height,
                'width': width,
                'channels': channels
            })
            max_height = max(max_height, height)
            max_width = max(max_width, width)
        
        if debug_enabled:
            print(f"📏 图像尺寸分析:")
            print(f"   最大宽度: {max_width}")
            print(f"   最大高度: {max_height}")
            print(f"   图像数量: {len(valid_images)}")
            print(f"   拼接方向: {direction}")
            print(f"   间隙大小: {gap_size}px")
        
        # 统一所有图像的通道数（转换为3通道）
        processed_images = []
        for info in image_info:
            img = info['image']
            if len(img.shape) == 2:
                # 灰度图转3通道
                img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
            elif img.shape[2] == 4:
                # RGBA转BGR
                img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGR)
            processed_images.append(img)
        
        # 根据拼接方向处理图像
        if direction.lower() == 'horizontal':
            # 横向拼接：统一高度为max_height
            aligned_images = []
            total_width = 0
            
            for i, img in enumerate(processed_images):
                current_height, current_width = img.shape[:2]
                
                if current_height == max_height:
                    # 高度已经匹配，直接使用
                    aligned_img = img
                else:
                    # 需要添加垂直padding
                    padding_needed = max_height - current_height
                    
                    if align == 'top':
                        # 顶部对齐，在底部添加padding
                        aligned_img = cv2.copyMakeBorder(
                            img, 0, padding_needed, 0, 0,
                            cv2.BORDER_CONSTANT, value=fill_color
                        )
                    elif align == 'bottom':
                        # 底部对齐，在顶部添加padding
                        aligned_img = cv2.copyMakeBorder(
                            img, padding_needed, 0, 0, 0,
                            cv2.BORDER_CONSTANT, value=fill_color
                        )
                    else:  # center
                        # 居中对齐，上下分配padding
                        pad_top = padding_needed // 2
                        pad_bottom = padding_needed - pad_top
                        aligned_img = cv2.copyMakeBorder(
                            img, pad_top, pad_bottom, 0, 0,
                            cv2.BORDER_CONSTANT, value=fill_color
                        )
                
                aligned_images.append(aligned_img)
                total_width += aligned_img.shape[1]
                
                if debug_enabled:
                    print(f"   图像{i}: {current_width}x{current_height} -> {aligned_img.shape[1]}x{aligned_img.shape[0]}")
            
            # 计算最终图像尺寸（包括间隙）
            final_width = total_width + gap_size * (len(aligned_images) - 1)
            final_height = max_height
            
            # 创建最终图像
            result_image = np.full((final_height, final_width, 3), fill_color, dtype=np.uint8)
            
            # 拼接图像
            x_offset = 0
            for i, img in enumerate(aligned_images):
                img_width = img.shape[1]
                result_image[:, x_offset:x_offset + img_width] = img
                x_offset += img_width + gap_size
        
        elif direction.lower() == 'vertical':
            # 垂直拼接：统一宽度为max_width
            aligned_images = []
            total_height = 0
            
            for i, img in enumerate(processed_images):
                current_height, current_width = img.shape[:2]
                
                if current_width == max_width:
                    # 宽度已经匹配，直接使用
                    aligned_img = img
                else:
                    # 需要添加水平padding
                    padding_needed = max_width - current_width
                    
                    if align == 'left':
                        # 左对齐，在右侧添加padding
                        aligned_img = cv2.copyMakeBorder(
                            img, 0, 0, 0, padding_needed,
                            cv2.BORDER_CONSTANT, value=fill_color
                        )
                    elif align == 'right':
                        # 右对齐，在左侧添加padding
                        aligned_img = cv2.copyMakeBorder(
                            img, 0, 0, padding_needed, 0,
                            cv2.BORDER_CONSTANT, value=fill_color
                        )
                    else:  # center
                        # 居中对齐，左右分配padding
                        pad_left = padding_needed // 2
                        pad_right = padding_needed - pad_left
                        aligned_img = cv2.copyMakeBorder(
                            img, 0, 0, pad_left, pad_right,
                            cv2.BORDER_CONSTANT, value=fill_color
                        )
                
                aligned_images.append(aligned_img)
                total_height += aligned_img.shape[0]
                
                if debug_enabled:
                    print(f"   图像{i}: {current_width}x{current_height} -> {aligned_img.shape[1]}x{aligned_img.shape[0]}")
            
            # 计算最终图像尺寸（包括间隙）
            final_width = max_width
            final_height = total_height + gap_size * (len(aligned_images) - 1)
            
            # 创建最终图像
            result_image = np.full((final_height, final_width, 3), fill_color, dtype=np.uint8)
            
            # 拼接图像
            y_offset = 0
            for i, img in enumerate(aligned_images):
                img_height = img.shape[0]
                result_image[y_offset:y_offset + img_height, :] = img
                y_offset += img_height + gap_size
        
        else:
            raise ValueError("direction 参数必须是 'horizontal' 或 'vertical'")
        
        if debug_enabled:
            print(f"🎯 拼接完成:")
            print(f"   最终尺寸: {result_image.shape[1]}x{result_image.shape[0]}")
            print(f"   拼接图像数: {len(aligned_images)}")
        
        return result_image
        
    except Exception as e:
        print(f"❌ 图像拼接失败: {str(e)}")
        # 创建错误提示图像
        error_image = np.zeros((200, 400, 3), dtype=np.uint8)
        cv2.putText(error_image, "IMAGE CONCAT ERROR", (10, 50), 
                   cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
        cv2.putText(error_image, str(e)[:40], (10, 100), 
                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
        cv2.putText(error_image, f"Images: {len(image_list) if image_list else 0}", (10, 150), 
                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
        return error_image


def concatenate_images_smart_layout(image_list, max_width=1920, max_height=1080, 
                                  gap_size=10, fill_color=(0, 0, 0), debug_enabled=False):
    """
    智能布局版本：自动选择横向或垂直拼接，或创建网格布局
    
    Args:
        image_list: 图像列表
        max_width: 最大输出宽度
        max_height: 最大输出高度
        gap_size: 间隙大小
        fill_color: 填充颜色
        debug_enabled: 调试模式
        
    Returns:
        np.ndarray: 拼接后的图像
    """
    try:
        if not image_list or len(image_list) == 0:
            raise ValueError("图像列表不能为空")
        
        valid_images = [img for img in image_list if img is not None]
        if not valid_images:
            raise ValueError("没有有效的图像")
        
        num_images = len(valid_images)
        
        if num_images == 1:
            return valid_images[0].copy()
        
        # 分析图像特征
        avg_width = sum(img.shape[1] for img in valid_images) / num_images
        avg_height = sum(img.shape[0] for img in valid_images) / num_images
        max_img_width = max(img.shape[1] for img in valid_images)
        max_img_height = max(img.shape[0] for img in valid_images)
        
        if debug_enabled:
            print(f"📊 智能布局分析:")
            print(f"   图像数量: {num_images}")
            print(f"   平均尺寸: {avg_width:.0f}x{avg_height:.0f}")
            print(f"   最大尺寸: {max_img_width}x{max_img_height}")
        
        # 决策逻辑
        if num_images <= 2:
            # 2张图片：优先横向拼接
            horizontal_width = max_img_width * 2 + gap_size
            if horizontal_width <= max_width:
                if debug_enabled:
                    print("   选择: 横向拼接")
                return concatenate_images_with_padding(
                    valid_images, 'horizontal', gap_size, 'center', fill_color, debug_enabled
                )
            else:
                if debug_enabled:
                    print("   选择: 垂直拼接（横向超出限制）")
                return concatenate_images_with_padding(
                    valid_images, 'vertical', gap_size, 'center', fill_color, debug_enabled
                )
        
        elif num_images <= 4:
            # 3-4张图片：尝试2x2网格或一行拼接
            horizontal_width = max_img_width * num_images + gap_size * (num_images - 1)
            
            if horizontal_width <= max_width:
                if debug_enabled:
                    print("   选择: 横向拼接")
                return concatenate_images_with_padding(
                    valid_images, 'horizontal', gap_size, 'center', fill_color, debug_enabled
                )
            else:
                if debug_enabled:
                    print("   选择: 网格布局")
                return create_grid_layout(
                    valid_images, 2, gap_size, fill_color, debug_enabled
                )
        
        else:
            # 5张以上：创建网格布局
            if debug_enabled:
                print("   选择: 网格布局")
            cols = min(3, int(np.ceil(np.sqrt(num_images))))
            return create_grid_layout(
                valid_images, cols, gap_size, fill_color, debug_enabled
            )
        
    except Exception as e:
        print(f"❌ 智能布局失败: {str(e)}")
        return concatenate_images_with_padding(image_list, 'horizontal', gap_size, 'center', fill_color, debug_enabled)


def create_grid_layout(image_list, cols, gap_size=10, fill_color=(0, 0, 0), debug_enabled=False):
    """
    创建网格布局
    
    Args:
        image_list: 图像列表
        cols: 列数
        gap_size: 间隙大小
        fill_color: 填充颜色
        debug_enabled: 调试模式
        
    Returns:
        np.ndarray: 网格布局的图像
    """
    try:
        if not image_list:
            raise ValueError("图像列表不能为空")
        
        num_images = len(image_list)
        rows = (num_images + cols - 1) // cols  # 向上取整
        
        # 获取最大尺寸
        max_width = max(img.shape[1] for img in image_list)
        max_height = max(img.shape[0] for img in image_list)
        
        if debug_enabled:
            print(f"🔳 创建网格布局: {rows}x{cols}")
            print(f"   单元格尺寸: {max_width}x{max_height}")
        
        # 创建行
        grid_rows = []
        for row in range(rows):
            row_images = []
            for col in range(cols):
                img_idx = row * cols + col
                if img_idx < num_images:
                    # 有图像：调整到统一尺寸
                    img = image_list[img_idx]
                    if len(img.shape) == 2:
                        img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
                    
                    # 居中添加边框到最大尺寸
                    h, w = img.shape[:2]
                    pad_h = max_height - h
                    pad_w = max_width - w
                    
                    padded_img = cv2.copyMakeBorder(
                        img,
                        pad_h // 2, pad_h - pad_h // 2,
                        pad_w // 2, pad_w - pad_w // 2,
                        cv2.BORDER_CONSTANT, value=fill_color
                    )
                    row_images.append(padded_img)
                else:
                    # 没有图像：创建空白图像
                    empty_img = np.full((max_height, max_width, 3), fill_color, dtype=np.uint8)
                    row_images.append(empty_img)
            
            # 横向拼接当前行
            if row_images:
                row_concat = concatenate_images_with_padding(
                    row_images, 'horizontal', gap_size, 'center', fill_color, False
                )
                grid_rows.append(row_concat)
        
        # 垂直拼接所有行
        if grid_rows:
            result = concatenate_images_with_padding(
                grid_rows, 'vertical', gap_size, 'center', fill_color, False
            )
            return result
        else:
            raise ValueError("无法创建网格布局")
            
    except Exception as e:
        print(f"❌ 网格布局创建失败: {str(e)}")
        # 回退到简单横向拼接
        return concatenate_images_with_padding(image_list, 'horizontal', gap_size, 'center', fill_color, debug_enabled)


def draw_aoi_on_image(image: np.ndarray, aoi: dict, color=(0, 255, 0), thickness=2, label=None) -> np.ndarray:
    """
    在图像上绘制AOI框
    
    Args:
        image: 输入图像 (BGR格式)
        aoi: AOI字典 {'x': int, 'y': int, 'width': int, 'height': int}
        color: 绘制颜色 (B, G, R)，默认绿色
        thickness: 线条粗细
        label: 标签文字，None则不显示
        
    Returns:
        np.ndarray: 绘制后的图像
    """
    try:
        if image is None or aoi is None:
            return image
        
        # 获取AOI坐标
        x1 = int(aoi['x'])
        y1 = int(aoi['y'])
        x2 = x1 + int(aoi['width'])
        y2 = y1 + int(aoi['height'])
        
        result_image = image.copy()
        
        # 绘制矩形框
        cv2.rectangle(result_image, (x1, y1), (x2, y2), color, thickness)
        
        # 绘制标签
        if label is not None:
            cv2.putText(result_image, str(label), (x1, y1 - 10),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
        
        return result_image
        
    except Exception as e:
        print(f"❌ 绘制AOI失败: {str(e)}")
        return image


def overlay_binary_mask(original_image, binary_image, overlay_color=(0, 255, 0), alpha=0.3, preserve_brightness=True):
    """
    将二值化图像按照指定透明度和颜色叠加到原图上，可选择是否保持原图亮度
    
    Args:
        original_image (np.ndarray): 原始图像
        binary_image (np.ndarray): 二值化图像
        overlay_color (tuple): 叠加颜色，BGR格式，默认绿色(0, 255, 0)
        alpha (float): 叠加透明度，0-1之间，默认0.3
        preserve_brightness (bool): 是否保持原图亮度，默认True
        
    Returns:
        np.ndarray: 叠加后的图像
    """
    try:
        # 1. 输入图像预处理
        original = original_image.copy()
        binary = binary_image.copy()
        
        # 确保原图是3通道
        if len(original.shape) == 2:
            original = cv2.cvtColor(original, cv2.COLOR_GRAY2BGR)
        
        # 确保二值图是单通道
        if len(binary.shape) == 3:
            binary = cv2.cvtColor(binary, cv2.COLOR_BGR2GRAY)
        
        # 调整二值图尺寸与原图一致
        if original.shape[:2] != binary.shape[:2]:
            binary = cv2.resize(binary, (original.shape[1], original.shape[0]))
        
        # 2. 创建二值掩膜
        # 白色区域为前景（值为255），黑色区域为背景（值为0）
        mask = (binary > 127).astype(np.uint8)
        
        # 3. 创建彩色覆盖层
        overlay = np.zeros_like(original)
        overlay[:, :] = overlay_color  # 整个覆盖层设为指定颜色
        
        if preserve_brightness:
            # 方法1: 保持亮度不变的叠加
            # 计算原图在掩膜区域的亮度
            original_gray = cv2.cvtColor(original, cv2.COLOR_BGR2GRAY)
            
            # 在掩膜区域进行颜色叠加，但保持亮度
            result = original.copy().astype(np.float32)
            
            # 只在掩膜区域处理
            mask_regions = mask == 1
            
            if np.any(mask_regions):
                # 获取原图在掩膜区域的亮度
                original_brightness = original_gray[mask_regions].astype(np.float32)
                
                # 创建颜色层
                color_layer = np.full((np.sum(mask_regions), 3), overlay_color, dtype=np.float32)
                
                # 计算颜色层的亮度 (BGR to Gray: 0.114*B + 0.587*G + 0.299*R)
                color_brightness = (color_layer[:, 0] * 0.114 + 
                                  color_layer[:, 1] * 0.587 + 
                                  color_layer[:, 2] * 0.299)
                
                # 避免除零
                color_brightness = np.maximum(color_brightness, 1e-6)
                
                # 调整颜色层以匹配原图亮度
                brightness_ratio = original_brightness / color_brightness
                brightness_ratio = brightness_ratio.reshape(-1, 1)
                
                adjusted_color = color_layer * brightness_ratio
                adjusted_color = np.clip(adjusted_color, 0, 255)
                
                # 获取原图在掩膜区域的像素
                original_pixels = result[mask_regions].astype(np.float32)
                
                # 按透明度混合
                blended_pixels = original_pixels * (1 - alpha) + adjusted_color * alpha
                
                # 将混合结果放回原图
                result[mask_regions] = blended_pixels
                
        else:
            # 方法2: 不保持亮度的普通叠加
            result = original.copy().astype(np.float32)
            overlay_float = overlay.astype(np.float32)
            
            # 创建3通道掩膜
            mask_3d = np.stack([mask, mask, mask], axis=2)
            
            # 只在掩膜区域进行叠加
            blended = result * (1 - alpha) + overlay_float * alpha
            result = np.where(mask_3d == 1, blended, result)
        
        # 4. 转换回uint8并返回
        result = np.clip(result, 0, 255).astype(np.uint8)
        return result
        
    except Exception as e:
        print(f"叠加处理出错: {str(e)}")
        return original_image

def overlay_binary_mask_simple(original_image, binary_image, overlay_color=(0, 255, 0), alpha=0.3):
    """
    简化版本：将二值化图像叠加到原图上（不保持亮度）
    
    Args:
        original_image (np.ndarray): 原始图像
        binary_image (np.ndarray): 二值化图像  
        overlay_color (tuple): 叠加颜色，BGR格式
        alpha (float): 叠加透明度，0-1之间
        
    Returns:
        np.ndarray: 叠加后的图像
    """
    try:
        # 预处理
        original = original_image.copy()
        if len(original.shape) == 2:
            original = cv2.cvtColor(original, cv2.COLOR_GRAY2BGR)
        
        if len(binary_image.shape) == 3:
            binary = cv2.cvtColor(binary_image, cv2.COLOR_BGR2GRAY)
        else:
            binary = binary_image.copy()
            
        if original.shape[:2] != binary.shape[:2]:
            binary = cv2.resize(binary, (original.shape[1], original.shape[0]))
        
        # 创建掩膜
        mask = (binary > 127).astype(np.uint8)
        
        # 创建彩色覆盖层
        overlay = np.zeros_like(original)
        overlay[:, :] = overlay_color
        
        # 进行叠加
        result = original.copy().astype(np.float32)
        overlay_float = overlay.astype(np.float32)
        
        # 3通道掩膜
        mask_3d = np.stack([mask, mask, mask], axis=2)
        
        # 叠加计算
        blended = result * (1 - alpha) + overlay_float * alpha
        result = np.where(mask_3d == 1, blended, result)
        
        return result.astype(np.uint8)
        
    except Exception as e:
        print(f"简单叠加处理出错: {str(e)}")
        return original_image

        