feat: Add KB knowledge base series skills
- kb-link-parser: Multi-platform URL parser (Bilibili, YouTube, Zhihu) - kb-bilibili-metadata: Bilibili video/article/up主 metadata extractor - kb-zhihu-metadata: Zhihu question/answer/user metadata extractor - kb-bilibili-content: Bilibili video download and content extraction - kb-ocr-processor: OCR text extraction from video frames - kb-deep-analyzer: AI-powered deep content analysis - kb-workflow: Workflow orchestration (inbox, archive, subscriptions, report) Also add: - ARCHITECTURE_FLOWCHART.md: System architecture diagrams - DETAILED_DESIGN.md: Complete design documentation
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
# KB OCR Processor Skill
|
||||
|
||||
**kb-ocr-processor** - Extract text from video frames using OCR.
|
||||
|
||||
## Features
|
||||
|
||||
- Batch frame processing
|
||||
- PaddleOCR/Tesseract support
|
||||
- Confidence scoring
|
||||
- Text merging
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Process frames
|
||||
python ocr_processor.py --input ./frames/ --output ocr.json
|
||||
|
||||
# With custom FPS
|
||||
python ocr_processor.py --input ./frames/ --fps 0.1 --output ocr.json
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"frames": {
|
||||
"frame_0001.jpg": {
|
||||
"texts": [{"text": "字幕", "confidence": 0.95}]
|
||||
}
|
||||
},
|
||||
"merged_text": "合并的字幕文本..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
cp -r kb-ocr-processor/ ~/skills/
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
OCR Processor - 视频帧文字识别工具
|
||||
支持 EasyOCR 和 Tesseract 引擎
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import glob
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional
|
||||
import easyocr
|
||||
import pytesseract
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class OCRProcessor:
|
||||
"""OCR 文字识别处理器"""
|
||||
|
||||
def __init__(self, engine: str = "easyocr", languages: List[str] = ["ch_sim", "en"]):
|
||||
"""
|
||||
初始化 OCR 处理器
|
||||
|
||||
Args:
|
||||
engine: OCR 引擎 ("easyocr" 或 "tesseract")
|
||||
languages: 语言列表
|
||||
"""
|
||||
self.engine = engine
|
||||
self.languages = languages
|
||||
self.reader = None
|
||||
|
||||
if engine == "easyocr":
|
||||
self.reader = easyocr.Reader(languages, gpu=False)
|
||||
|
||||
def process_image(self, image_path: str) -> Dict:
|
||||
"""
|
||||
处理单张图片进行 OCR 识别
|
||||
|
||||
Args:
|
||||
image_path: 图片路径
|
||||
|
||||
Returns:
|
||||
包含 text 和 confidence 的字典
|
||||
"""
|
||||
if not os.path.exists(image_path):
|
||||
return {"text": "", "confidence": 0.0, "error": "File not found"}
|
||||
|
||||
try:
|
||||
if self.engine == "easyocr":
|
||||
return self._process_with_easyocr(image_path)
|
||||
elif self.engine == "tesseract":
|
||||
return self._process_with_tesseract(image_path)
|
||||
else:
|
||||
return {"text": "", "confidence": 0.0, "error": f"Unknown engine: {self.engine}"}
|
||||
except Exception as e:
|
||||
return {"text": "", "confidence": 0.0, "error": str(e)}
|
||||
|
||||
def _process_with_easyocr(self, image_path: str) -> Dict:
|
||||
"""使用 EasyOCR 处理图片"""
|
||||
results = self.reader.readtext(image_path)
|
||||
|
||||
if not results:
|
||||
return {"text": "", "confidence": 0.0}
|
||||
|
||||
texts = []
|
||||
confidences = []
|
||||
|
||||
for bbox, text, conf in results:
|
||||
texts.append(text)
|
||||
confidences.append(conf)
|
||||
|
||||
merged_text = "\n".join(texts)
|
||||
avg_confidence = sum(confidences) / len(confidences) if confidences else 0.0
|
||||
|
||||
return {
|
||||
"text": merged_text,
|
||||
"confidence": round(avg_confidence, 4),
|
||||
"details": [
|
||||
{"text": text, "confidence": round(conf, 4)}
|
||||
for text, conf in zip(texts, confidences)
|
||||
]
|
||||
}
|
||||
|
||||
def _process_with_tesseract(self, image_path: str) -> Dict:
|
||||
"""使用 Tesseract 处理图片"""
|
||||
image = Image.open(image_path)
|
||||
text = pytesseract.image_to_string(image, lang="+".join(self.languages))
|
||||
|
||||
# Tesseract 不直接返回置信度,返回 0.0
|
||||
return {
|
||||
"text": text.strip(),
|
||||
"confidence": 0.0
|
||||
}
|
||||
|
||||
def process_frames(self, input_dir: str, fps: float = 0.1) -> List[Dict]:
|
||||
"""
|
||||
处理目录下所有帧图片
|
||||
|
||||
Args:
|
||||
input_dir: 帧图片目录
|
||||
fps: 采样帧率
|
||||
|
||||
Returns:
|
||||
OCR 结果列表
|
||||
"""
|
||||
if not os.path.isdir(input_dir):
|
||||
print(f"Error: Directory not found: {input_dir}")
|
||||
return []
|
||||
|
||||
# 支持的图片格式
|
||||
image_extensions = ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.webp"]
|
||||
image_files = []
|
||||
|
||||
for ext in image_extensions:
|
||||
image_files.extend(glob.glob(os.path.join(input_dir, ext)))
|
||||
image_files.extend(glob.glob(os.path.join(input_dir, "**", ext), recursive=True))
|
||||
|
||||
# 按文件名排序
|
||||
image_files = sorted(image_files)
|
||||
|
||||
if not image_files:
|
||||
print(f"No images found in {input_dir}")
|
||||
return []
|
||||
|
||||
print(f"Found {len(image_files)} images in {input_dir}")
|
||||
print(f"Processing with {self.engine} engine...")
|
||||
|
||||
results = []
|
||||
for i, image_path in enumerate(image_files):
|
||||
print(f"Processing [{i+1}/{len(image_files)}]: {image_path}")
|
||||
ocr_result = self.process_image(image_path)
|
||||
ocr_result["source"] = os.path.basename(image_path)
|
||||
results.append(ocr_result)
|
||||
|
||||
return results
|
||||
|
||||
def merge_texts(self, results: List[Dict]) -> str:
|
||||
"""
|
||||
合并多个 OCR 结果的文本
|
||||
|
||||
Args:
|
||||
results: OCR 结果列表
|
||||
|
||||
Returns:
|
||||
合并后的文本
|
||||
"""
|
||||
texts = []
|
||||
for result in results:
|
||||
if result.get("text"):
|
||||
texts.append(result["text"])
|
||||
return "\n\n---\n\n".join(texts)
|
||||
|
||||
def save_results(self, results: List[Dict], output_path: str, merge: bool = False):
|
||||
"""
|
||||
保存 OCR 结果到 JSON 文件
|
||||
|
||||
Args:
|
||||
results: OCR 结果列表
|
||||
output_path: 输出文件路径
|
||||
merge: 是否同时保存合并后的文本文件
|
||||
"""
|
||||
output_data = {
|
||||
"engine": self.engine,
|
||||
"total_frames": len(results),
|
||||
"results": results
|
||||
}
|
||||
|
||||
# 保存 JSON
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(output_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"Results saved to {output_path}")
|
||||
|
||||
# 如果需要,同时保存合并的文本
|
||||
if merge:
|
||||
text_path = output_path.replace(".json", ".txt")
|
||||
merged_text = self.merge_texts(results)
|
||||
with open(text_path, "w", encoding="utf-8") as f:
|
||||
f.write(merged_text)
|
||||
print(f"Merged text saved to {text_path}")
|
||||
|
||||
|
||||
def main():
|
||||
"""命令行入口"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="OCR 视频帧文字识别工具",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
示例:
|
||||
# 使用 EasyOCR 处理帧目录
|
||||
python ocr_processor.py --input ./frames/ --output ./ocr.json
|
||||
|
||||
# 使用 Tesseract 处理
|
||||
python ocr_processor.py --input ./frames/ --output ./ocr.json --engine tesseract
|
||||
|
||||
# 指定采样帧率
|
||||
python ocr_processor.py --input ./frames/ --output ./ocr.json --fps 0.5
|
||||
|
||||
# 同时生成合并的文本文件
|
||||
python ocr_processor.py --input ./frames/ --output ./ocr.json --merge
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument("--input", "-i", required=True, help="输入帧图片目录")
|
||||
parser.add_argument("--output", "-o", required=True, help="输出 JSON 文件路径")
|
||||
parser.add_argument("--fps", "-f", type=float, default=0.1, help="采样帧率 (默认: 0.1)")
|
||||
parser.add_argument("--engine", "-e", choices=["easyocr", "tesseract"],
|
||||
default="easyocr", help="OCR 引擎 (默认: easyocr)")
|
||||
parser.add_argument("--merge", action="store_true", help="同时保存合并的文本文件")
|
||||
parser.add_argument("--languages", "-l", nargs="+",
|
||||
default=["ch_sim", "en"], help="语言列表")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 初始化处理器
|
||||
processor = OCRProcessor(engine=args.engine, languages=args.languages)
|
||||
|
||||
# 处理帧
|
||||
results = processor.process_frames(args.input, fps=args.fps)
|
||||
|
||||
if results:
|
||||
# 保存结果
|
||||
processor.save_results(results, args.output, merge=args.merge)
|
||||
|
||||
# 打印摘要
|
||||
total_texts = sum(1 for r in results if r.get("text"))
|
||||
avg_conf = sum(r.get("confidence", 0) for r in results) / len(results) if results else 0
|
||||
|
||||
print(f"\n=== 摘要 ===")
|
||||
print(f"处理帧数: {len(results)}")
|
||||
print(f"识别到文字的帧: {total_texts}")
|
||||
print(f"平均置信度: {avg_conf:.2%}")
|
||||
else:
|
||||
print("未处理到任何结果")
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
@@ -0,0 +1,3 @@
|
||||
easyocr>=1.7.0
|
||||
pytesseract>=0.3.10
|
||||
Pillow>=9.0.0
|
||||
Reference in New Issue
Block a user