-
Notifications
You must be signed in to change notification settings - Fork 241
Description
Describe the bug
`using BlazorBot.Core;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
// 👇 引入 PaddleOCR 相关命名空间
using Sdcb.PaddleInference;
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models;
using Sdcb.PaddleOCR.Models.Local;
namespace BlazorBot.Funcs
{
[FunctionalDescription("提供基于摄像头的文字识别(OCR)能力,支持读取文档、标签、屏幕截图中的文字内容")]
public class OcrVision : BaseSkillSet
{
// 这里使用 LocalFullModels.ChineseV3,它是目前效果最好的轻量级中文模型之一
private readonly FullOcrModel _ocrModel = LocalFullModels.ChineseV3;
[KernelFunction("识别图片中的所有文字信息。支持传入本地路径或网络URL,如果不传路径,将自动调用摄像头拍照识别。")]
public async Task<string> RecognizeText(
[KernelParam("待识别的图片路径(可以是本地文件路径,也可以是 http 开头的网络 URL)")] string imageSource = "")
{
try
{
Mat srcImage = null;
string sourceDescription = "";
// =========================================================================
// 1. 获取图片源 (网络URL / 本地路径 / 摄像头)
// =========================================================================
if (!string.IsNullOrWhiteSpace(imageSource) && (imageSource.StartsWith("http://") || imageSource.StartsWith("https://")))
{
// A. 网络图片模式
Console.WriteLine($"🌐 正在下载网络图片: {imageSource}");
using (HttpClient http = new HttpClient())
{
byte[] imageData = await http.GetByteArrayAsync(imageSource);
srcImage = Cv2.ImDecode(imageData, ImreadModes.Color);
sourceDescription = "网络图片";
}
}
else if (!string.IsNullOrWhiteSpace(imageSource) && File.Exists(imageSource))
{
// B. 本地图片模式
Console.WriteLine($"📂 正在加载本地图片: {imageSource}");
srcImage = Cv2.ImRead(imageSource, ImreadModes.Color);
sourceDescription = "本地文件";
}
else
{
// C. 摄像头模式 (当路径为空或文件不存在时)
Console.WriteLine("📷 未提供有效图片,正在打开摄像头拍摄当前画面...");
string capturedPath = await CaptureImageFromCameraAsync("OcrScan");
if (string.IsNullOrEmpty(capturedPath))
{
return "❌ OCR 失败:无法获取图片或摄像头画面。";
}
srcImage = Cv2.ImRead(capturedPath, ImreadModes.Color);
sourceDescription = "摄像头拍摄";
}
if (srcImage == null || srcImage.Empty())
{
return "❌ 图片加载失败,数据为空。";
}
// =========================================================================
// 2. 初始化 PaddleOCR 引擎并执行推理
// =========================================================================
Console.WriteLine("🧠 正在加载 PaddleOCR 模型并提取文字...");
// 使用 Mkldnn 加速 (CPU)
using var all = new PaddleOcrAll(_ocrModel, PaddleDevice.Mkldnn())
{
AllowRotateDetection = true, // 允许识别有角度的文字
Enable180Classification = false, // 是否检测文字翻转
};
// 执行识别
// 注意:PaddleOCR 也是计算密集型,建议放入 Task.Run
PaddleOcrResult result = await Task.Run(() => all.Run(srcImage));
// 释放图片资源
srcImage.Dispose();
if (result == null || result.Regions.Count() == 0)
{
return "👀 OCR 完成,但图片中未检测到任何可读文字。";
}
// =========================================================================
// 3. 数据解析与文本/JSON混合返回
// =========================================================================
StringBuilder sb = new StringBuilder();
sb.AppendLine($"✅ OCR 识别完成 ({sourceDescription})!共发现 {result.Regions.Count()} 处文本区域。");
sb.AppendLine("---");
// 3.1 生成自然语言摘要 (直接拼接文字,方便 LLM 阅读语意)
sb.AppendLine("📄 **识别到的完整文本内容**:");
sb.AppendLine("```text");
sb.AppendLine(result.Text);
sb.AppendLine("```");
sb.AppendLine("---");
// 3.2 生成结构化 JSON 数据 (包含位置信息和置信度)
// 这对于 LLM 需要知道“文字在哪里”或者“是标题还是正文”时非常有用
sb.AppendLine("🎯 **详细数据清单** (JSON格式,含坐标与置信度):");
sb.AppendLine("```json");
sb.AppendLine("[");
var regions = result.Regions.ToList();
for (int i = 0; i < regions.Count; i++)
{
var r = regions[i];
// 简化坐标:将旋转矩形转换为中心点和宽高,保留2位小数
string jsonLine = $@" {{ ""id"": {i + 1}, ""text"": ""{JsonEscape(r.Text)}"", ""score"": {r.Score:F2}, ""center"": {{ ""x"": {r.Rect.Center.X:F0}, ""y"": {r.Rect.Center.Y:F0} }}, ""size"": {{ ""w"": {r.Rect.Size.Width:F0}, ""h"": {r.Rect.Size.Height:F0} }} }}";
sb.Append(jsonLine);
if (i < regions.Count - 1) sb.Append(",");
sb.AppendLine();
}
sb.AppendLine("]");
sb.AppendLine("```");
return sb.ToString();
}
catch (Exception ex)
{
return $"❌ OCR 识别过程发生异常: {ex.Message} \n {ex.StackTrace}";
}
}
// =========================================================================
// 🛠️ 辅助方法:JSON 字符转义
// =========================================================================
private string JsonEscape(string original)
{
if (string.IsNullOrEmpty(original)) return "";
return original
.Replace("\\", "\\\\")
.Replace("\"", "\\\"")
.Replace("\n", "\\n")
.Replace("\r", "\\r")
.Replace("\t", "\\t");
}
// =========================================================================
// 🛠️ 辅助方法:跨平台摄像头拍照 (复用自 YoloVision)
// =========================================================================
private Task<string> CaptureImageFromCameraAsync(string prefix)
{
return Task.Run(() =>
{
try
{
// 0 号摄像头通常是默认摄像头
using var capture = new VideoCapture(0);
if (!capture.IsOpened())
{
Console.WriteLine("⚠️ 无法连接到摄像头。");
return string.Empty;
}
using var frame = new Mat();
// 预热几帧,让摄像头自动对焦和调整白平衡
for (int i = 0; i < 5; i++) capture.Read(frame);
capture.Read(frame);
if (frame.Empty())
{
Console.WriteLine("⚠️ 摄像头捕获到了空帧。");
return string.Empty;
}
string folderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "OcrCaptures");
if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath);
string fileName = $"{prefix}_{DateTime.Now:yyyyMMddHHmmss}.jpg";
string fullPath = Path.Combine(folderPath, fileName);
// 保存图片以便后续读取
Cv2.ImWrite(fullPath, frame);
return fullPath;
}
catch (Exception ex)
{
Console.WriteLine($"📸 摄像头调用异常: {ex.Message}");
return string.Empty;
}
});
}
}
}`
Steps to reproduce the bug
代码执行到 PaddleOcrResult result = await Task.Run(() => all.Run(srcImage)); 这行时候 直接 看到的返回结果是 乱码
Expected behavior
No response
Screenshots
No response
Release version
No response
IDE
No response
OS version
No response
Additional context
No response