"""
词典词条分割程序 - 最终优化版（带底部空白裁剪）

优化参数设置：
1. 二值化：块大小=19, C=20
2. 空隙高度阈值：30像素
3. 投影阈值系数：0.05
4. 修复左栏顶部词条丢失问题

安装依赖（使用清华源）:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-python numpy

使用方法:
1. 将词典页面保存为dictionary_page.jpg
2. 运行程序: python dictionary_splitter.py
3. 分割后的词条图像保存在output目录
"""

import cv2
import numpy as np
import os

def process_dictionary_page(image_path, output_dir="output"):
    """
    处理词典页面图片，分割为单个词条图像
    :param image_path: 词典页面图片路径
    :param output_dir: 输出目录
    :return: 分割后的词条图像列表
    """
    # 创建输出目录
    os.makedirs(output_dir, exist_ok=True)
    
    # 1. 图像加载与预处理
    print(f"加载图像: {image_path}")
    image = cv2.imread(image_path)
    if image is None:
        raise ValueError(f"无法加载图像: {image_path}")
    
    # 转换为灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    
    # 二值化处理 - 使用优化参数 (块大小=19, C=20)
    print("图像二值化处理 (块大小=19, C=20)...")
    binary = cv2.adaptiveThreshold(
        gray, 255,
        cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY_INV, 19, 20  # 优化参数
    )
    
    # 降噪处理
    print("图像降噪处理...")
    kernel = np.ones((3, 3), np.uint8)
    processed = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
    processed = cv2.morphologyEx(processed, cv2.MORPH_CLOSE, kernel)
    
    # 2. 版面分析 - 检测双栏布局
    print("检测双栏布局...")
    # 垂直投影计算
    vertical_projection = np.sum(processed, axis=0)
    
    # 寻找中间空白区域（分栏空隙）
    height, width = processed.shape
    center = width // 2
    search_width = width // 8  # 在中间区域搜索
    
    # 找到投影最小的区域（最可能是分栏空隙）
    min_val = np.inf
    gap_center = center
    
    # 确定分栏位置
    for i in range(center - search_width, center + search_width):
        window = vertical_projection[i-5:i+5]
        window_sum = np.sum(window)
        if window_sum < min_val:
            min_val = window_sum
            gap_center = i
    
    # 优化边界检测：寻找左栏的实际右边界
    print("优化左栏边界检测...")
    
    # 方法1：在分栏中心左侧区域寻找文字结束位置
    left_region = processed[:, max(0, gap_center-100):gap_center]
    left_region_proj = np.sum(left_region, axis=0)
    
    # 从右侧向左扫描，找到第一个有文字的位置
    text_end = gap_center
    for i in range(len(left_region_proj)-1, 0, -1):
        if left_region_proj[i] > 0:
            text_end = gap_center - 100 + i
            break
    
    # 方法2：计算文字密度变化
    window_size = 10
    density = []
    for i in range(gap_center-50, gap_center+50):
        col_density = np.sum(processed[:, i-window_size//2:i+window_size//2]) / (height * window_size)
        density.append(col_density)
    
    # 找到密度开始下降的点作为左栏边界
    min_density_idx = np.argmin(density)
    density_boundary = gap_center - 50 + min_density_idx
    
    # 结合两种方法的结果，选择更靠右的边界
    left_end = max(text_end, density_boundary)
    
    # 添加安全边距
    safety_margin = 15  # 增加15像素的安全边距
    left_end += safety_margin
    
    # 确保边界在合理范围内
    left_end = min(left_end, width - 50)  # 不要超过图像宽度
    
    # 右栏开始位置
    right_start = gap_center + 10
    
    # 3. 词条分割
    gap_threshold = 25  # 优化参数：空隙高度阈值30像素
    print(f"分割词条 (空隙阈值: {gap_threshold}像素)...")
    
    # 分割左右两栏
    left_column = processed[:, :left_end]
    left_column_img = image[:, :left_end]
    right_column = processed[:, right_start:]
    right_column_img = image[:, right_start:]
    
    # 处理左侧栏
    print("处理左侧栏...")
    left_entries = process_column(left_column, left_column_img, "left", output_dir, 
                                 gap_threshold=gap_threshold)
    
    # 处理右侧栏
    print("处理右侧栏...")
    right_entries = process_column(right_column, right_column_img, "right", output_dir, 
                                  gap_threshold=gap_threshold)
    
    return left_entries + right_entries

def process_column(column_bin, column_img, side, output_dir, gap_threshold):
    """
    处理单栏图像，分割词条
    :param column_bin: 二值化的栏图像
    :param column_img: 原始栏图像
    :param side: 左栏或右栏标识
    :param output_dir: 输出目录
    :param gap_threshold: 行间空隙高度阈值（像素）
    :return: 分割后的词条图像列表
    """
    # 水平投影计算
    horizontal_projection = np.sum(column_bin, axis=1)
    
    # 寻找词条之间的空隙（水平投影值接近0的区域）
    threshold_coef = 0.05  # 优化参数：投影阈值系数0.05
    threshold = np.max(horizontal_projection) * threshold_coef
    gaps = []
    in_gap = False
    gap_start = 0
    
    # 检测空隙区域 - 使用参数化的空隙高度阈值
    for i, val in enumerate(horizontal_projection):
        if val < threshold and not in_gap:
            in_gap = True
            gap_start = i
        elif val >= threshold and in_gap:
            in_gap = False
            # 使用参数化的空隙高度阈值
            if i - gap_start > gap_threshold:  # 空隙高度大于阈值
                gaps.append((gap_start, i))
    
    # 提取词条区域边界
    entry_boundaries = []
    prev_end = 0
    
    # 收集所有词条边界
    for i, (start, end) in enumerate(gaps):
        # 词条区域：上一个空隙结束到当前空隙开始
        if start - prev_end > 10:  # 最小高度检查
            # 提取词条区域（上下各扩展5像素确保完整）
            y_start = max(0, prev_end - 10)
            y_end = min(column_img.shape[0], start + 10)
            entry_boundaries.append((y_start, y_end))
        
        prev_end = end
    
    # 处理最后一个词条
    if prev_end < column_img.shape[0] - 10:
        # 提取最后一个词条（上边界扩展5像素）
        y_start = max(0, prev_end - 10)
        y_end = column_img.shape[0]
        entry_boundaries.append((y_start, y_end))
    
    # 裁剪底部空白（在检查高度之前）
    cropped_boundaries = []
    for idx, (y_start, y_end) in enumerate(entry_boundaries):
        # 提取词条区域
        entry_region = column_img[y_start:y_end, :]
        gray_entry = cv2.cvtColor(entry_region, cv2.COLOR_BGR2GRAY)
        
        # 二值化处理
        _, thresh = cv2.threshold(gray_entry, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
        
        # 计算水平投影
        h_proj = np.sum(thresh, axis=1)
        
        # 从底部向上扫描，找到最后一个有内容的行
        last_content_row = y_end - y_start  # 默认为底部
        for i in range(len(h_proj)-1, -1, -1):
            if h_proj[i] > 0:
                last_content_row = i
                break
        
        # 裁剪掉底部空白（保留1-2行空白）
        new_y_end = y_start + last_content_row + 10  # 保留5像素底部空白
        
        # 确保裁剪后不会超出原范围
        new_y_end = min(new_y_end, y_end)
        
        # 记录裁剪后的边界
        cropped_boundaries.append((y_start, new_y_end))
        
        # 记录裁剪信息
        if new_y_end < y_end:
            print(f"裁剪{side}栏词条 {idx+1} 的底部空白: {y_end - new_y_end}像素")
    
    # 修复：确保顶部词条不被遗漏
    # 检查第一个词条是否被遗漏
    if cropped_boundaries and cropped_boundaries[0][0] > 0:
        # 添加顶部词条
        top_start = 0
        top_end = cropped_boundaries[0][0]
        if top_end - top_start > 10:  # 确保有有效高度
            # 插入到边界列表开头
            cropped_boundaries.insert(0, (top_start, top_end))
            print(f"添加{side}栏顶部词条: {top_end - top_start}像素")
    
    # 智能合并短词条 - 根据空隙距离决定合并方向
    merged_boundaries = []
    i = 0
    while i < len(cropped_boundaries):
        y_start, y_end = cropped_boundaries[i]
        height = y_end - y_start
        
        # 检查词条高度是否不足50像素
        if height < 50:
            # 计算与上一个词条的空隙（如果存在）
            gap_prev = float('inf')
            if i > 0:
                prev_y_start, prev_y_end = cropped_boundaries[i-1]
                gap_prev = y_start - prev_y_end  # 当前词条上边界与上一个词条下边界的距离
            
            # 计算与下一个词条的空隙（如果存在）
            gap_next = float('inf')
            if i < len(cropped_boundaries) - 1:
                next_y_start, next_y_end = cropped_boundaries[i+1]
                gap_next = next_y_start - y_end  # 下一个词条上边界与当前词条下边界的距离
            
            # 根据空隙距离决定合并方向
            if gap_prev <= gap_next:
                # 合并到上一个词条
                if i > 0:
                    prev_y_start, prev_y_end = cropped_boundaries[i-1]
                    new_end = max(prev_y_end, y_end)
                    merged_boundaries[-1] = (prev_y_start, new_end)
                    print(f"合并{side}栏的短词条 {i+1} 到前一词条 (高度: {height}像素, 空隙: {gap_prev}px)")
                    i += 1  # 移动到下一个词条
                    continue
            else:
                # 合并到下一个词条
                if i < len(cropped_boundaries) - 1:
                    next_y_start, next_y_end = cropped_boundaries[i+1]
                    new_start = min(y_start, next_y_start)
                    # 创建新的合并边界
                    merged_boundaries.append((y_start, next_y_end))
                    print(f"合并{side}栏的短词条 {i+1} 和后一词条 (高度: {height}像素, 空隙: {gap_next}px)")
                    i += 2  # 跳过下一个词条（已经合并）
                    continue
        
        # 如果高度足够或无法合并，直接添加
        merged_boundaries.append((y_start, y_end))
        i += 1
    
    # 跳过页码词条并保存有效词条
    entries = []
    entry_count = 0
    
    # 标识是否已跳过页码
    skipped_page_number = False
    
    for idx, (y_start, y_end) in enumerate(merged_boundaries):
        # 提取词条图像
        entry_img = column_img[y_start:y_end, :]
        height = y_end - y_start
        
        # 跳过页码（通常高度较小且位于顶部）
        if not skipped_page_number and height < 130 and y_start < column_img.shape[0] * 0.1:
            print(f"跳过{side}栏的页码词条 (高度: {height}像素)")
            skipped_page_number = True
            continue
            
        # 保存词条图像为JPG格式
        entry_count += 1
        output_path = os.path.join(output_dir, f"{side}_entry_{entry_count:03d}.jpg")
        
        # 使用高质量JPEG保存
        cv2.imwrite(output_path, entry_img, [int(cv2.IMWRITE_JPEG_QUALITY), 95])
        
        entries.append(entry_img)
    
    print(f"{side}栏分割完成: 共 {len(merged_boundaries)} 个词条, 保存 {entry_count} 个有效词条")
    return entries

if __name__ == "__main__":
    # 输入图像路径
    input_image = "0082.jpg"
    
    # 检查图像是否存在
    if not os.path.exists(input_image):
        print(f"错误: 输入图像 {input_image} 不存在")
        print("请将词典页面保存为 dictionary_page.jpg 并放在当前目录")
        exit(1)
    
    # 处理图像并分割词条
    print("开始处理词典页面...")
    print("使用优化参数: 二值化(块大小=19, C=20), 空隙阈值=30, 投影系数=0.05")
    entries = process_dictionary_page(input_image)
    
    print(f"\n处理完成! 成功分割出 {len(entries)} 个有效词条图像")
    print(f"分割结果保存在 output 目录中 (JPG格式)")