You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Python国际象棋OCR走法匹配错误:Levenshtein相似度缺陷求解

稳健的国际象棋OCR走法修正策略

问题根源分析

当前方案仅依赖Levenshtein文本相似度匹配,完全忽略了国际象棋记谱的语义特征和OCR手写识别的常见错误模式,导致出现合法但不符合实际的匹配结果,且单状态推进一旦出错就无法挽回。


具体修正策略

1. 基于记谱特征的加权相似度计算

国际象棋SAN记谱的不同部分优先级差异极大:棋子类型(B/N/R/Q/K)> 目标位置 > 来源位置(可选)。可以给不同部分设置权重,比如棋子类型匹配占60%权重,位置匹配占40%,避免因长度差异导致错误匹配。

示例修改后的score函数:

def score(self, ocr_move, legal_move):
    ocr = self.normalize(ocr_move)
    legal = self.normalize(legal_move)
    
    # 拆分记谱元素:棋子类型+位置
    def parse_san(s):
        piece = s[0] if s[0] in 'bnrqk' else ''
        pos = s[len(piece):]
        return piece, pos
    
    ocr_piece, ocr_pos = parse_san(ocr)
    legal_piece, legal_pos = parse_san(legal)
    
    # 棋子类型匹配权重0.6,位置匹配权重0.4
    piece_score = 1.0 if ocr_piece == legal_piece else 0.0
    pos_dist = Levenshtein.distance(ocr_pos, legal_pos)
    pos_max_len = max(len(ocr_pos), len(legal_pos), 1)
    pos_score = 1 - pos_dist / pos_max_len
    
    return 0.6 * piece_score + 0.4 * pos_score

这个修改能直接解决示例2的问题:Bf4(棋子类型b)和b4(棋子类型空)的棋子类型得分是0,最终总得分会远低于合法的Bf4(如果存在)。

2. 引入手写OCR错误映射表

统计手写国际象棋记谱中高频认错的字符对,比如:

  • 字母形近:d↔f, b↔h, n↔u, r↔k
  • 大小写混淆:B↔b, N↔n

在计算距离时,把这些配对的字符距离设为0.5而非1,更贴合实际错误场景:

# 预定义OCR常见错误映射
OCR_ERROR_MAP = {
    'd': {'f': 0.5},
    'f': {'d': 0.5},
    'b': {'h': 0.5, 'B': 0.0},
    'B': {'b': 0.0},
    'n': {'u': 0.5, 'N': 0.0},
    'N': {'n': 0.0}
}

def char_distance(c1, c2):
    if c1 == c2:
        return 0.0
    # 检查错误映射
    if c1 in OCR_ERROR_MAP and c2 in OCR_ERROR_MAP[c1]:
        return OCR_ERROR_MAP[c1][c2]
    return 1.0

# 修改Levenshtein距离计算为自定义加权距离
def weighted_levenshtein(s1, s2):
    m, n = len(s1), len(s2)
    dp = [[0.0]*(n+1) for _ in range(m+1)]
    
    for i in range(m+1):
        dp[i][0] = i
    for j in range(n+1):
        dp[0][j] = j
    
    for i in range(1, m+1):
        for j in range(1, n+1):
            cost = char_distance(s1[i-1], s2[j-1])
            dp[i][j] = min(
                dp[i-1][j] + 1,    # 删除
                dp[i][j-1] + 1,    # 插入
                dp[i-1][j-1] + cost # 替换
            )
    return dp[m][n]

用这个加权距离替代原Levenshtein距离,能让实际手写错误的匹配得分更合理。

3. 多候选棋局回溯机制

放弃单状态推进,维护多个候选棋局状态(比如Top 3得分的走法对应的棋局),每一步OCR结果都在所有候选状态中尝试匹配,当某个候选状态无法找到有效匹配时,就丢弃该状态,保留可行的状态继续推进。

示例简化实现:

class OcrChessEngine:
    def __init__(self):
        # 维护多个候选状态:(board_state, move_history, confidence)
        self.candidates = [(chess.Board(), [], 1.0)]
    
    def apply_ocr_move(self, ocr_move, top_n=3):
        new_candidates = []
        for board, history, conf in self.candidates:
            legal_moves = list(board.legal_moves)
            # 计算所有合法走法的得分并排序
            scored_moves = []
            for move in legal_moves:
                san = board.san(move)
                s = self.score(ocr_move, san)
                scored_moves.append((s, move, san, board.copy()))
            
            # 取Top N候选
            scored_moves.sort(reverse=True, key=lambda x: x[0])
            for s, move, san, new_board in scored_moves[:top_n]:
                new_board.push(move)
                new_history = history + [(san, s)]
                new_conf = conf * s
                new_candidates.append((new_board, new_history, new_conf))
        
        # 按总置信度排序,保留Top N候选
        new_candidates.sort(reverse=True, key=lambda x: x[2])
        self.candidates = new_candidates[:top_n]
        # 返回置信度最高的结果
        return self.candidates[0][1][-1] if self.candidates else (None, 0)

这个机制能避免一步错误导致全局崩坏,后续走法可以验证前面的候选是否合理。

4. 结合棋局合理性过滤

利用国际象棋的常识或引擎评估过滤不合理的走法:

  • 开局阶段:给e4、d4、Nf3等常见开局走法额外加0.1-0.2的得分权重;
  • 中局/残局:用python-chess的评估函数(比如board.evaluate())给更优的走法加权重,过滤掉明显送子、自绝后路的走法。

示例开局权重加成:

OPENING_MOVES = {'e4', 'd4', 'Nf3', 'Nc3', 'f4', 'b4'}

def score(self, ocr_move, legal_move):
    # 原有得分计算
    base_score = ... 
    # 开局阶段加成
    if len(self.board.move_stack) < 10 and legal_move in OPENING_MOVES:
        base_score += 0.15
    return base_score

这能解决示例1的问题:即使OCR输出f4,如果实际是d4,在开局阶段d4的得分会高于f4,从而被优先选择。


内容的提问来源于stack exchange,提问作者TacerTV

火山引擎 最新活动