OCR 文件文字识别超高精度实现(无废话版|附完整示例代码)
·
下面展示的是一个.NET(其他语言可参考官方文档)可直接使用的 OCR 调用方案,包括:
-
PaddleOCR 调用封装(识别模型:PaddleOCR-VL-1.5,支持账号池轮询)
-
文件识别业务方法示例(示例:
RecognizeAsync) -
完整可运行代码,无废话
一、PaddleOCR 调用封装(支持 Token 自动切换)
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Common.Helper
{
public class PaddleOCRSharpHelper
{
#region 内部模型
private class ApiAccount
{
public string Url { get; init; }
public string Token { get; init; }
}
public class OCRResult
{
public bool IsSuccess { get; set; }
public string AccountUrl { get; set; }
public int? StatusCode { get; set; }
public string StatusDescription { get; set; }
public string Text { get; set; }
public string ErrorMessage { get; set; }
}
#endregion
#region 账号池(后续可迁移到配置)
private static readonly ApiAccount[] Accounts =
{
// 自己去百度PaddleOCR官网复制对应模型的个人API秘钥即可
new()
{
Url = "https://xxxxxxxxx1.aistudio-app.com/layout-parsing",
Token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
};
#endregion
#region 状态码定义
private static readonly Dictionary<int, string> StatusDescriptions = new()
{
{ 403, "Token 错误或 URL 不匹配" },
{ 429, "超出单日解析最大页数" },
{ 500, "参数错误(fileType / payload)" },
{ 503, "服务繁忙,请稍后再试" },
{ 504, "网关超时,请稍后再试" }
};
// 这些错误 → 可以切换账号继续尝试
private static readonly HashSet<int> RetryableStatusCodes =
new() { 403, 429, 503, 504 };
#endregion
#region HttpClient
private static readonly HttpClient HttpClient = new()
{
Timeout = TimeSpan.FromSeconds(180)
};
#endregion
#region 主入口
public static async Task<OCRResult> RecognizeTextWithStatusAsync(string filePath)
{
if (!File.Exists(filePath))
throw new FileNotFoundException("File not found.", filePath);
int fileType = Path.GetExtension(filePath)
.Equals(".pdf", StringComparison.OrdinalIgnoreCase) ? 0 : 1;
byte[] fileBytes = await File.ReadAllBytesAsync(filePath);
string base64File = Convert.ToBase64String(fileBytes);
var payload = new
{
file = base64File,
fileType,
useDocOrientationClassify = false,
useDocUnwarping = false,
useChartRecognition = false
};
string json = JsonSerializer.Serialize(payload);
foreach (var account in Accounts)
{
try
{
using var request = new HttpRequestMessage(HttpMethod.Post, account.Url)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
request.Headers.Add("Authorization", $"token {account.Token}");
var response = await HttpClient.SendAsync(request);
var responseText = await response.Content.ReadAsStringAsync();
int statusCode = (int)response.StatusCode;
StatusDescriptions.TryGetValue(statusCode, out var statusDesc);
if (!response.IsSuccessStatusCode)
{
if (RetryableStatusCodes.Contains(statusCode))
{
Log($"账号切换:{account.Url},状态码:{statusCode},原因:{statusDesc}");
continue;
}
return BuildFailResult(account, statusCode, statusDesc, responseText);
}
return ParseSuccessResult(account, statusCode, statusDesc, responseText);
}
catch (Exception ex)
{
Log($"账号调用异常:{account.Url},{ex.Message}");
}
}
return new OCRResult
{
IsSuccess = false,
ErrorMessage = "所有账号均已尝试,OCR 识别失败。"
};
}
#endregion
#region 私有辅助方法
private static OCRResult BuildFailResult(
ApiAccount account,
int statusCode,
string statusDesc,
string error)
{
return new OCRResult
{
IsSuccess = false,
AccountUrl = account.Url,
StatusCode = statusCode,
StatusDescription = statusDesc,
ErrorMessage = error
};
}
private static OCRResult ParseSuccessResult(
ApiAccount account,
int statusCode,
string statusDesc,
string responseText)
{
var sb = new StringBuilder();
using var doc = JsonDocument.Parse(responseText);
if (doc.RootElement.TryGetProperty("result", out var result) &&
result.TryGetProperty("layoutParsingResults", out var layouts))
{
foreach (var item in layouts.EnumerateArray())
{
if (TryGetMarkdownText(item, out var text))
{
sb.AppendLine(text);
sb.AppendLine();
}
}
}
return new OCRResult
{
IsSuccess = true,
AccountUrl = account.Url,
StatusCode = statusCode,
StatusDescription = statusDesc,
Text = sb.ToString()
};
}
private static bool TryGetMarkdownText(JsonElement item, out string text)
{
text = null;
if (item.TryGetProperty("markdown", out var markdown) &&
markdown.TryGetProperty("text", out var textProp))
{
text = textProp.GetString();
return !string.IsNullOrWhiteSpace(text);
}
return false;
}
private static void Log(string message)
{
// 后续可无缝替换成 ILogger
Console.WriteLine($"[PaddleOCR] {message}");
}
#endregion
}
}
二、业务方法:识别文件并返回 OCR 文本
public async Task<ResultObject<XxxDto>> RecognizeAsync(
IFormFileCollection files)
{
var result = new ResultObject<XxxDto>();
if (files == null || files.Count == 0)
{
result.IsSuccess = false;
result.Msg = "No file uploaded.";
return result;
}
var dto = new XxxDto();
string tempFilePath = null;
try
{
tempFilePath = await SaveTempFileAsync(files[0]);
var ocrResult = await PaddleOCRSharpHelper.RecognizeTextWithStatusAsync(tempFilePath);
if (!ocrResult.IsSuccess)
{
result.IsSuccess = false;
result.Msg = $"OCR 识别失败,状态码: {ocrResult.StatusCode},说明: {ocrResult.StatusDescription}";
return result;
}
string ocrText = ocrResult.Text;
// TODO: 这里处理你自己的文本解析逻辑
result.IsSuccess = true;
result.Data = dto;
return result;
}
catch (Exception ex)
{
result.IsSuccess = false;
result.Msg = "识别异常:" + ex.Message;
return result;
}
finally
{
if (!string.IsNullOrWhiteSpace(tempFilePath) && File.Exists(tempFilePath))
File.Delete(tempFilePath);
}
}
private static async Task<string> SaveTempFileAsync(IFormFile file)
{
if (file == null) throw new ArgumentNullException(nameof(file));
// 保留原始扩展名
var ext = Path.GetExtension(file.FileName);
var tempFilePath = Path.Combine(
Path.GetTempPath(),
$"{Guid.NewGuid()}{ext}"
);
await using var stream = file.OpenReadStream();
await using var fs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write);
await stream.CopyToAsync(fs);
return tempFilePath;
}
三、可直接运行,结构清晰
至此,OCR 调用与业务集成全部完成。
更多推荐
所有评论(0)