fix: 复制 obsidian-kb 的 src 目录以解决依赖缺失

This commit is contained in:
OpenClaw Agent
2026-02-10 17:34:54 +08:00
parent 11f6c21740
commit f66a50cd3f
38 changed files with 9202 additions and 0 deletions
@@ -0,0 +1,355 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
OCR 文字识别处理器(EasyOCR 版本)
使用 EasyOCR 实现视频帧文字识别功能,作为 PaddleOCR 的替代方案
主要优势:不依赖 PaddlePaddle,避免 oneDNN 兼容性问题
"""
import re
import numpy as np
from typing import List, Dict, Any, Tuple
import logging
logger = logging.getLogger(__name__)
class EasyOCRProcessor:
"""
EasyOCR 文字识别处理器
使用 EasyOCR 从视频帧中识别文字,支持批量识别和结果过滤
相比 PaddleOCR 的优势:
- 不依赖 PaddlePaddle,避免 oneDNN 兼容性问题
- 安装更简单,依赖更少
- 支持多种语言(70+ 种)
"""
# 相似度阈值:用于判断两个文本是否相似(基于编辑距离)
SIMILARITY_THRESHOLD = 0.85
# 置信度阈值
CONFIDENCE_THRESHOLD = 0.7
# 最小字幕长度(字符数)
MIN_SUBTITLE_LENGTH = 2
def __init__(self, lang: str = 'ch_sim', use_gpu: bool = False, detail: int = 1):
"""
初始化 OCR 处理器
Args:
lang: 语言支持,'ch_sim' 为简体中文,'ch_tra' 为繁体中文
use_gpu: 是否使用 GPU 加速
detail: 识别结果详细程度(0=只返回文本,1=返回文本+坐标+置信度)
"""
self.lang = lang
self.use_gpu = use_gpu
self.detail = detail
# 初始化 EasyOCR 实例
self._ocr = None
self._initialize_ocr()
def _initialize_ocr(self):
"""初始化 EasyOCR 实例"""
try:
from easyocr import Reader
self._ocr = Reader(
[self.lang],
gpu=self.use_gpu,
verbose=False # 不显示详细日志
)
logger.info("EasyOCR 初始化成功")
except Exception as e:
logger.error(f"EasyOCR 初始化失败: {e}")
raise RuntimeError(f"无法初始化 EasyOCR: {e}")
def _initialize_ocr(self):
"""初始化 EasyOCR 实例"""
try:
from easyocr import Reader
self._ocr = Reader(
[self.lang],
gpu=self.use_gpu,
verbose=False # 不显示详细日志
)
logger.info("EasyOCR 初始化成功")
except Exception as e:
logger.error(f"EasyOCR 初始化失败: {e}")
raise RuntimeError(f"无法初始化 EasyOCR: {e}")
def recognize_frames(self, frames: List[Tuple[np.ndarray, float]]) -> List[Dict[str, Any]]:
"""
批量识别帧中的文字
Args:
frames: 帧列表,每个元素为 (图像数组, 时间戳) 元组
- 图像数组:numpy.ndarray,形状为 (H, W, 3) 的 RGB 图像
- 时间戳:float,单位为秒
Returns:
List[Dict[str, Any]]: 识别结果列表,每个结果包含:
- time: float, 时间戳(秒)
- text: str, 识别的文本
- confidence: float, 置信度 (0-1)
"""
if not frames:
logger.warning("输入帧列表为空")
return []
results = []
for image, timestamp in frames:
try:
# 调用 EasyOCR 进行识别
# EasyOCR 返回格式: [[[x1, y1], [x2, y2], [x3, y3], [x4, y4]], (text, confidence, ...)]
# 根据 detail 参数,返回格式不同:
# detail=0: 返回 [text1, text2, ...]
# detail=1: 返回 [[bbox, (text, confidence)], ...]
ocr_result = self._ocr.readtext(image, detail=self.detail)
if ocr_result:
if self.detail == 0:
# 简单模式:只返回文本列表
for i, text in enumerate(ocr_result):
if text:
results.append({
'time': timestamp,
'text': str(text),
'confidence': 1.0 # 简单模式下没有置信度
})
else:
# 详细模式:返回边界框+文本+置信度
for item in ocr_result:
if len(item) >= 2:
bbox = item[0]
text_info = item[1]
if len(text_info) >= 2:
text, confidence = text_info[0], text_info[1]
# 跳过空文本
if not text:
continue
# 处理置信度:可能是数字或特殊字符
try:
if isinstance(confidence, str):
# 如果是 '?' 或其他特殊字符,使用默认置信度
conf_float = 0.5
else:
conf_float = float(confidence)
except (ValueError, TypeError):
conf_float = 0.5
results.append({
'time': timestamp,
'text': text,
'confidence': conf_float
})
except Exception as e:
logger.warning(f"帧识别失败 (时间戳 {timestamp}s): {e}")
continue
logger.info(f"原始识别结果数量: {len(results)}")
# 过滤和清洗结果
filtered_results = self._filter_and_clean(results)
logger.info(f"过滤后结果数量: {len(filtered_results)}")
return filtered_results
def _filter_and_clean(self, results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
过滤和清洗识别结果
Args:
results: 原始识别结果列表
Returns:
List[Dict[str, Any]]: 过滤后的结果列表
"""
if not results:
return []
# 步骤 1: 按置信度过滤
filtered = self._filter_by_confidence(results)
logger.debug(f"置信度过滤后: {len(filtered)}")
# 步骤 2: 过滤噪声(纯符号、单字符等)
filtered = self._filter_noise(filtered)
logger.debug(f"噪声过滤后: {len(filtered)}")
# 步骤 3: 去除重复或相似的文本
filtered = self._remove_duplicates(filtered)
logger.debug(f"去重后: {len(filtered)}")
# 步骤 4: 按时间戳排序
filtered.sort(key=lambda x: x['time'])
return filtered
def _filter_by_confidence(self, results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
按置信度过滤结果
Args:
results: 识别结果列表
Returns:
List[Dict[str, Any]]: 高置信度结果
"""
return [r for r in results if r['confidence'] >= self.CONFIDENCE_THRESHOLD]
def _filter_noise(self, results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
过滤噪声结果
过滤条件:
- 文本长度小于最小长度
- 纯符号或空白字符
Args:
results: 识别结果列表
Returns:
List[Dict[str, Any]]: 过滤后的结果
"""
filtered = []
for result in results:
text = result['text'].strip()
# 过滤过短的文本
if len(text) < self.MIN_SUBTITLE_LENGTH:
continue
# 过滤纯符号或空白字符
if self._is_noise(text):
continue
filtered.append(result)
return filtered
def _is_noise(self, text: str) -> bool:
"""
判断文本是否为噪声
Args:
text: 要判断的文本
Returns:
bool: 如果是噪声返回 True
"""
# 使用正则判断是否只包含符号和空白
try:
# 移除标点符号后检查是否为空
# 先替换常见的中文标点
cleaned = re.sub(r'[^\w\u4e00-\u9fff]', '', text)
return len(cleaned) == 0
except:
# 如果正则出错,保守处理,不算噪声
return False
def _remove_duplicates(self, results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
去除重复或相似的文本
基于编辑距离(Levenshtein distance)判断文本相似度
Args:
results: 识别结果列表(假设已按时间戳排序)
Returns:
List[Dict[str, Any]]: 去重后的结果
"""
if not results:
return []
deduped = [results[0]]
for current in results[1:]:
last = deduped[-1]
# 计算相似度
similarity = self._calculate_similarity(current['text'], last['text'])
# 如果相似度低于阈值,则保留
if similarity < self.SIMILARITY_THRESHOLD:
deduped.append(current)
else:
# 相似文本:保留置信度更高的那个
if current['confidence'] > last['confidence']:
deduped[-1] = current
return deduped
def _calculate_similarity(self, text1: str, text2: str) -> float:
"""
计算两个文本的相似度(基于归一化编辑距离)
Args:
text1: 文本1
text2: 文本2
Returns:
float: 相似度 (0-1),1 表示完全相同
"""
if text1 == text2:
return 1.0
len1, len2 = len(text1), len(text2)
if len1 == 0 or len2 == 0:
return 0.0
# 动态规划计算编辑距离
dp = np.zeros((len1 + 1, len2 + 1))
for i in range(len1 + 1):
dp[i, 0] = i
for j in range(len2 + 1):
dp[0, j] = j
for i in range(1, len1 + 1):
for j in range(1, len2 + 1):
if text1[i-1] == text2[j-1]:
dp[i, j] = dp[i-1, j-1]
else:
dp[i, j] = min(
dp[i-1, j] + 1, # 删除
dp[i, j-1] + 1, # 插入
dp[i-1, j-1] + 1 # 替换
)
edit_distance = dp[len1, len2]
max_len = max(len1, len2)
# 归一化相似度:1 - (编辑距离 / 最大长度)
similarity = 1.0 - (edit_distance / max_len)
return similarity
def get_ocr_info(self) -> Dict[str, Any]:
"""
获取 OCR 处理器信息
Returns:
Dict[str, Any]: OCR 配置信息
"""
return {
'engine': 'easyocr',
'lang': self.lang,
'use_gpu': self.use_gpu,
'detail': self.detail,
'confidence_threshold': self.CONFIDENCE_THRESHOLD,
'similarity_threshold': self.SIMILARITY_THRESHOLD,
'min_subtitle_length': self.MIN_SUBTITLE_LENGTH
}