
简介这是一个基于C#实现的百度OCR图像文字识别示例项目面向有一定C#基础、希望快速接入百度AI开放平台OCR能力的开发者。项目完整展示如何申请API密钥、构造HTTP请求上传图像支持URL与Base64编码、设置语言等可选参数并解析百度OCR返回的JSON结果最终将识别文字导出为TXT等可编辑文本同时可利用返回的文字框位置信息进行版面分析。压缩包共29个文件以7个.cs源码文件为核心覆盖窗体界面设计、OCR调用逻辑与程序入口另含2个dll运行库、3个exe调试版本及pdb调试符号方便对照运行调试还附带项目工程文件与配置文件可直接在Visual Studio中打开学习整个调用流程。包体仅261KB轻量无冗余适合新手快速上手。目前已有225人学习下载特别适合希望结合具体代码理解AI接口调用流程、掌握图像文字识别落地方式的开发者参考。1. C# 调百度 OCR这个 RAR 里装的是什么以及它值得你动手做OCR.rar 这个压缩包在不少 C# 老工程师的网盘和项目交接里出现频率不低。打开 rar 之后通常是一个 C# 工程里面做了一件很聚焦的事把本地图片交给百度 OCR百度 AI 的「图像识别」产品族的 REST 接口再把识别出来的文字取回来。它适合三类人上位机软件里要读仪表铭牌、设备序列号的进销存系统里要批量录入票据的手里攒了一堆图片想转成文字、又不打算自己训练模型的桌面工具开发者。这个方案最反直觉的一点是真正花在识别接口上的时间很少大头全在图片格式处理、access_token 过期、QPS 限流这几个容易翻车的地方。整条链路完整跑通一遍之后你会发现 C# 调云端 OCR 不过是三次 HTTP 请求的事。2. 百度 OCR 的调用链路先分清三个产品再写代码2.1 为什么 C# 工程师优先选百度 OCR 而不是本地 Tesseract很多人一开始会纠结要不要用 Tesseract 本地识别。我的看法是先看你的图片来源。Tesseract 对扫描版印刷体表现还行一旦遇到手机拍照、倾斜透视、弯曲文本、手写体或者屏幕上带噪点的图识别率会掉得很厉害而且 C# 里调 Tesseract 还得处理训练数据路径、语言包、DLL 版本这些历史遗留问题。百度 OCR 的优势是云端模型在持续迭代复杂场景的识别率靠服务端兜底C# 端不需要维护任何模型文件。另外一个关键点是调用成本。百度 AI 开放平台把 OCR 能力放在「图像识别」这个产品目录下创建应用之后拿到的 API Key 和 Secret Key 可以同时用于通用文字识别、高精度识别和身份证、银行卡这类专用接口。同一套凭证、同一种调用方式只是 URL 路径不同这让整个调用链路的学习成本变得非常低。它不是某个单一接口而是一整族可以按需切换的接口。还有一点值得注意因为底层是标准 REST API这个方案不只 C# 能用。VBA 里用 WinHttp 调、Java 里用 OkHttp 调、Python 里用 requests 调走的都是同一套鉴权和请求格式。C# 工程的价值在于把图片编码、token 管理、结果解析这些重复劳动封装好了换语言只需要重写 HTTP 那一段。2.2 一次识别请求要经过的四个环节整个调用过程可以拆成四段搞清楚这四段比背代码更重要。第一段是拿 access_token。百度 AI 用的是 OAuth2.0 的 client_credentials 模式用 API Key 和 Secret Key 换一个 token。这个 token 的默认有效期是 2592000 秒也就是 30 天。如果你的程序每天跑一次完全可以把 token 缓存在内存或文件里避免每次启动都重新换。第二段是准备图片。本地文件要先读成字节数组再转 base64 字符串。需要注意的是 base64 文本里不能带换行、不能带data:image/png;base64,这种前缀直接贴原始编码值。网络图片则可以直接传 URL走url参数而不是image参数。第三段是发 HTTP POST 请求。请求头里带Content-Type: application/x-www-form-urlencoded请求体里放access_token、image或url参数。这里有个新手很容易踩的坑百度 OCR 接口接收的不是 JSON body而是表单格式。你用StringContent传 JSON 字符串接口会直接给你报参数错误。第四段是解析返回的 JSON。返回结构里words_result是数组每个元素是一个识别结果行核心字段是words。如果要拿坐标做版面还原每个元素里还有location对象包含 top、left、width、height 四个值。另外还有一个words_result_num字段告诉你一共识别出多少行调试的时候很有用。2.3 控制台里创建应用你只需要记三个参数在动手写代码之前先去百度智能云控制台完成三件事注册账号并完成实名认证在「图像识别」下创建应用然后在应用详情页把 API Key 和 Secret Key 复制出来。创建应用时选的产品线是「文字识别」还是「图像识别」都不用纠结最终拿到的都是同一套鉴权参数。这三个参数的关系是API Key 和 Secret Key 是长期凭证相当于账号密码access_token 是短期凭证用 Key 换来的敲门砖。正式项目里 API Key 和 Secret Key 不要硬编码在 .cs 文件里然后连同 rar 一起发出去我见过太多次 rar 包里躺着真实密钥的事故了。接手这套代码的第一件事就是把这些值挪到 appsettings.json 或者环境变量里。参数作用有效期存放建议API Key应用标识换取 token 用长期appsettings.json / 环境变量Secret Key应用密钥换取 token 用长期appsettings.json / 环境变量禁止入库access_token请求识别接口的凭证约 30 天内存缓存或本地缓存文件表里这三项就是整个 C# 工程里你唯一需要关心的账号参数。接下来第 3 章的代码就是围绕这三个参数展开的。3. 用 C# 跑通百度 OCR 最小识别从获取 access_token 到 JSON 解析3.1 新建工程与 HttpClient 的两点准备我习惯先用控制台工程把链路跑通再移植到 WinForms 或 WPF。.NET 版本建议用 .NET 6 或更高如果是老机器上的 .NET Framework至少要用 4.7.2 以上否则 HttpClient 默认走 TLS 1.0百度接口会直接拒绝握手。需要引的包就两个System.Net.Http做请求JSON 解析用Newtonsoft.Json。新项目用System.Text.Json也可以但百度返回的字段名是下划线风格System.Text.Json要用JsonPropertyName逐个标注Newtonsoft 用JsonProperty标注一次就行我建议老工程直接上 Newtonsoft。这里还有一个容易忽视的点HttpClient 不要每次 new 一个。高频调用下频繁创建 HttpClient 会耗尽 socket 资源正确做法是定义成 static 字段复用或者直接用IHttpClientFactory。控制台小程序里 static 就够了。3.2 获取并缓存 access_token 的 C# 代码先写 token 的获取和缓存逻辑这段代码是整个工程的基石。using System; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json.Linq; public static class BaiduAuth { private static readonly HttpClient Http new HttpClient(); private static string _cachedToken; private static DateTime _tokenExpireTime DateTime.MinValue; public static async Taskstring GetAccessTokenAsync(string apiKey, string secretKey) { // token 还在有效期内直接返回缓存避免每次启动都走一次网络 if (_cachedToken ! null DateTime.Now _tokenExpireTime) { return _cachedToken; } // 百度 OAuth2.0 的 token 端点grant_type 固定为 client_credentials var url https://aip.baidubce.com/oauth/2.0/token; var form new FormUrlEncodedContent(new[] { new KeyValuePairstring, string(grant_type, client_credentials), new KeyValuePairstring, string(client_id, apiKey), new KeyValuePairstring, string(client_secret, secretKey) }); var resp await Http.PostAsync(url, form); var body await resp.Content.ReadAsStringAsync(); var json JObject.Parse(body); if (json[error] ! null) { throw new Exception($token 获取失败: {json[error_description]}); } _cachedToken json[access_token].ToString(); // 服务器返回 expires_in 秒这里提前 5 分钟刷新避免边界过期 var expiresIn Convert.ToInt32(json[expires_in]); _tokenExpireTime DateTime.Now.AddSeconds(expiresIn - 300); return _cachedToken; } }逻辑说明这段代码做了两件事。第一是内存缓存_cachedToken不为空且未过期就直接返回避免每次识别都重新换 token第二是用FormUrlEncodedContent拼出标准的表单请求体这是百度 OAuth 端点要求的格式。参数说明grant_type必须是client_credentials这是固定值client_id填 API Keyclient_secret填 Secret Key。expires_in是服务器返回的有效期秒数我这里减了 300 秒作为提前量防止刚好卡在过期临界点。注意error_description字段只在失败时出现如果 API Key 写错了这里抛出的异常信息就是你排查的第一条线索。3.3 识别一张本地图片的完整调用代码拿到 token 之后核心的识别方法就只剩一个 POST 请求加一段 JSON 解析。using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json.Linq; public class OcrResult { public string Text { get; set; } public int Top { get; set; } public int Left { get; set; } public int Width { get; set; } public int Height { get; set; } } public static class BaiduOcr { private static readonly HttpClient Http new HttpClient(); public static async TaskListOcrResult RecognizeAsync( string imagePath, string accessToken, bool detectDirection false) { // 读取本地图片并转 base64注意不能带前缀和换行 var bytes File.ReadAllBytes(imagePath); var base64 Convert.ToBase64String(bytes); // 通用文字识别标准版接口 var url https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic; var form new FormUrlEncodedContent(new[] { new KeyValuePairstring, string(access_token, accessToken), new KeyValuePairstring, string(image, base64), // 传入 true 会自动检测图像旋转角度并纠正 new KeyValuePairstring, string(detect_direction, detectDirection ? true : false) }); var resp await Http.PostAsync(url, form); var body await resp.Content.ReadAsStringAsync(); var json JObject.Parse(body); // 接口报错时 error_msg 会直接给出原因例如 file format error if (json[error_code] ! null) { throw new Exception($OCR 调用失败: {json[error_code]} {json[error_msg]}); } var results new ListOcrResult(); var words (JArray)json[words_result]; foreach (var item in words) { var loc item[location]; results.Add(new OcrResult { Text item[words].ToString(), Top (int)loc[top], Left (int)loc[left], Width (int)loc[width], Height (int)loc[height] }); } return results; } }逻辑说明RecognizeAsync的流程是读文件、转 base64、构造表单、POST、解析 JSON。FormUrlEncodedContent会自动处理 URL 编码base64 字符串里的和/字符会被正确转义这一点比自己手动拼字符串安全得多。解析阶段把words_result数组逐条转成强类型的OcrResult对象后续无论是拼接纯文本还是画框标记坐标都基于这个对象操作。参数说明detect_direction设为 true 时接口会多花一点时间检测图像朝向如果你的图片来源是手机随手拍、扫描件方向不定建议打开如果图片已经做过预处理、方向固定关掉可以省一点响应时间。image参数和url参数二选一传了image就不能再传url否则接口会优先读url导致识别内容不是你本地的图。另外一个注意点是通用标准版接口返回的words_result不带置信度只有高精度接口带probability字段这个差异在第 6 章会具体讲。3.4 把识别结果拼接成文本一个够用的行序处理识别结果默认是按图片从上到下的顺序排列的直接遍历OcrResult列表拼接文本就能得到一份顺序基本正确的纯文本。这里给一个够用的拼接方法public static string ToPlainText(ListOcrResult results) { var sb new StringBuilder(); foreach (var line in results) { sb.AppendLine(line.Text); } return sb.ToString(); }逻辑说明这段代码简单到不需要解释但我想强调一个边界百度返回的words_result顺序是「按坐标从上到下」这对单栏文本完全没问题但遇到报纸、合同这种多栏版式左右两栏的文字会交错混排。处理多栏文本时不能只依赖接口返回顺序要按location.top分组、再按location.left排序这个进阶逻辑在第 4 章批量任务里会顺带讲到。参数说明如果识别结果里有表格数据用AppendLine直接拼会把行结构丢了。常见的处理方式是每行文本之间用制表符分隔然后输出成 CSV。但通用接口只返回纯识别文本不带表格结构想要表格还原得用百度专门的表格文字识别接口那个接口的返回 JSON 里有cells数组描述单元格位置复杂度高一个量级。4. 从单张图片到批量任务PDF、多页、高精度与图像识别接口的落地选择4.1 批量识别一整个文件夹串行起步限流并发单张通了之后批量识别就是顺理成章的事。文件夹里有几百张票据要转文字第一版我建议先串行跑一遍把链路彻底跑稳再考虑并发。核心是控制 QPS百度免费额度对单接口的 QPS 限制通常在个位数并发开太猛会直接撞上限流返回 429 或者QPS limit reached。一个稳妥的串行批量版本大概是这个结构foreach (var file in Directory.GetFiles(folderPath, *.jpg)) { var ocrList await BaiduOcr.RecognizeAsync(file, token); var text ToPlainText(ocrList); // 每条识别结果单独存一个 txt文件名与图片名保持一致 var outputPath Path.ChangeExtension(file, .txt); File.WriteAllText(outputPath, text); // 等 300ms把请求频率压在 3 QPS 以内 await Task.Delay(300); }逻辑说明循环中间的Task.Delay(300)是给请求限速用的把调用频率压到每秒 3 次左右遇到免费额度限流也能从容重试。等你确认接口稳定了再改成用SemaphoreSlim控制并发度、同时跑 2 到 3 个任务。参数说明这里的 QPS 不是你想跑到多少就是多少。百度免费额度的并发限制是接口维度的同一个 access_token 下所有请求共享这个配额。如果批量任务量很大、又不想为了 QPS 去申请付费额度一个实用的技巧是把识别任务分散到多个百度 AI 应用账号下用不同的 apiKey 并行跑相当于把额度横向扩了。这个做法不能突破单账号的日调用总量限制但能把吞吐量提上去。4.2 PDF 和多页 TIFF先拆页再识别这是 C# OCR PDF 的必经之路有读者会问百度 OCR 能不能直接传 PDF 文件答案是通用文字识别接口不支持 PDF 入参传上去会报file format error。处理 PDF 的正确路径是先用 C# 把每一页渲染成 PNG 图片再按 4.1 的流程逐页识别。C# 里渲染 PDF 页面的常见做法是用 PdfiumViewer 或 PdfPig 这类开源库PdfiumViewer 在 WinForms 老项目里用得很多。拆页之前有一个必须先做判断这个 PDF 是文本型还是扫描型。文本型 PDF 里本来就嵌着字符数据直接用 PdfPig 之类工具抽文本就行不需要走 OCR识别速度和准确率完全碾压云端接口。只有扫描型 PDF每一页是图片才需要渲染成位图再调 OCR。判断方法也很简单用 PdfPig 打开 PDF提取每一页的文本长度如果所有页提取出来的文本加起来不足几十个字符基本就是扫描件需要走 OCR。渲染 PDF 页面的参数上有一个坑默认 72 DPI 渲染出来的图片可能只有几百像素宽文字识别率会明显下降。我一般用 150 DPI 渲染既能保证识别率又不会让图片体积瞬间涨到几 MB 撞上接口的 4MB 限制。页面特别大时可以分段渲染先在最上层把整页转图识别失败再降采样重试。4.3 高精度接口和专用图像识别接口什么时候值得换批量任务跑起来之后你会开始想一个问题通用接口识别效果不满意要不要换高精度。我的建议是分层选择不要把整个系统钉死在一个接口上。普通发票、购物小票、屏幕截图这种印刷体内容general_basic标准版就够了速度快、免费额度充足。手写体、复杂背景、模糊老照片用accurate_basic高精度接口价格贵一截但识别率确实高一截。身份证、银行卡、营业执照这类有固定版式的证照直接用专用接口别用通用接口去识别再自己正则解析。场景接口路径返回特点适用判断印刷体、屏幕截图/rest/2.0/ocr/v1/general_basicwords_result 纯文本 坐标免费额度够用优先选它手写体、复杂背景/rest/2.0/ocr/v1/accurate_basic多一个 probability 置信度识别率要求高、能接受付费身份证/rest/2.0/ocr/v1/idcard结构化姓名、身份证号等字段需要提取固定字段时直接上银行卡/rest/2.0/ocr/v1/bankcard结构化卡号、银行名金融类业务专用这个表里的接口路径和调用关系和你在百度 AI 控制台看到的「图像识别」产品列表是对应的。换接口时第 3 章的RecognizeAsync方法唯一要改的是 URL 路径和传入参数其他的 HTTP 流程、token 逻辑、表单构造全部复用。我自己维护的 C# 工程就是把这些接口路径放在一个静态配置类里按场景选择。专用接口返回的结构化 JSON 是它最大的价值。比如身份证接口返回的不是一行行文本而是姓名、性别、公民身份号码这种字段级结果直接映射到对象属性就能入库省掉了正则劈词的工序。这也意味着批量跑证照类任务时第 3 章的ToPlainText方法不再适用得按专用接口各自的字段结构写解析。5. 百度 OCR 常见问题与避坑从 file format error 到 QPS 限流的四条血泪经验5.1 file format error图片格式被接口拒收现象是接口返回error_code: 216201error_msg: file format error这大概是整套流程里出现频率最高的报错。原因通常是三类传了接口不支持的格式PDF、webp、某些奇怪的 bmp 变体base64 字符串里带了换行符base64 字符串带了data:image/jpeg;base64,前缀。第一类在批量识别 PDF 转出的图片时特别常见有些截图工具保存的 webp 图也会触发。解决方法是统一在 C# 端把图片转成 PNG 或 JPEG 再识别。PNG 优先因为它对文字边缘的保真度好。转格式用System.Drawing的Image.FromStream读进来再Save成 PNG 到内存流然后重新取 base64。base64 字符串用Replace(\n, ).Replace(\r, )清掉换行检查开头没有data:前缀再去请求接口。提示排这类错最快的办法是打印出实际发出的 image 参数长度和原始文件字节数对比。base64 长度应该是原始字节数的 4/3 倍左右如果长度明显不对问题一定出在编码环节。5.2 access_token 过期突然返回错误码 110现象是程序跑得好好的突然某一天所有请求都开始报error_code: 110error_msg: Access token invalid or no longer valid。原因有三个方向token 确实过了 30 天有效期的自然过期手动在控制台重置了 API Key 导致旧 token 全部失效服务器时钟和本地时间相差过大。这里最坑的是第一种尤其是程序按 7x24 小时常驻跑的任务token 过期时间点很难人工盯。解决方法是代码里做两层兜底。第一层就是第 3 章的缓存逻辑有效期提前 5 分钟刷新。第二层是捕获到错误码 110 时清掉缓存 token重新换一次 token 再重试当前请求。这两层配合下来token 过期对上层业务基本无感。5.3 QPS 限流批量任务跑到一半开始连环 429现象是批量识别到第几十张时突然连续报error_code: 18error_msg: QPS limit reached而且之后几乎每一张都报错程序像是被拉黑了。原因是单账号接口的 QPS 配额被瞬间打满尤其是你用了并行任务批量跑图。百度对 QPS 超限的策略是直接拒绝请求不会自动排队等待。解决方法是代码里必须做限流和重试。限流用SemaphoreSlim控制并发在 1 到 2 个或者直接串行加Task.Delay。重试要做退避第一次等 1 秒重试第二次等 3 秒最多重试 3 次。我踩过的坑是只做了重试没做退避导致瞬间又打满 QPS形成恶性循环。5.4 图片过大或像素超限识别结果直接为空现象是接口没有报错error_code和error_msg都没有但words_result是空数组或者返回参数错误。原因是图片超过了接口的像素或体积限制。通用接口要求图片 base64 后不超过 4MB最长边不超过 4096 像素。从手机相册直接读出来的原图经常 3000 万像素体积 6MB 多直接传上去就在限制边缘。解决方法是识别前统一做预处理。用System.Drawing或 ImageSharp 等比缩放到最长边 2048 像素以内JPEG 质量压到 85 再转 base64。这样做不但规避了体积限制实际上还提升了识别速度。图片太大时云端做下采样反而可能丢失细节。5.5 识别结果乱序多栏版式拼接顺序不对现象是识别出来的文字齐全但拼接顺序完全不对两栏文本交叉排列读都读不通。原因是接口返回的words_result按坐标从上到下排多栏排版里左栏和右栏的 top 值接近接口会把左右两栏的行混在一起返回。解决方法是拿location做二次排序。先按 top 值把文本行聚类成几个条带条带内按 left 值从左到右排序再按条带的 top 顺序输出。代码如下var ordered ocrList .GroupBy(o o.Top / 30) // 每 30 像素高度视为同一行 .OrderBy(g g.Key) // 条带从上到下 .Select(g g.OrderBy(o o.Left)) // 条带内从左到右 .SelectMany(x x); var text string.Join(\n, ordered.Select(o o.Text));逻辑说明GroupBy(o o.Top / 30)的含义是每 30 像素视为同一水平带这个阈值可以根据实际版面调整行高较大的文档可以放宽到 50。先对条带排序、再对条带内元素排序最终还原出人类的阅读顺序。参数说明30 这个阈值不是万能的表格场景里行高变化大我最常用的做法是先用 top 值聚类再人工检查条带边界稳定之后再固化阈值。6. 给识别结果加一道本地校验Tesseract 兜底与置信度过滤的实操技巧云端 OCR 识别率再高也不是 100%直接入库会污染数据。我现在的习惯是给识别流程加一道本地校验先用百度高精度接口拿结果和置信度再把置信度低于阈值的区域丢给本地 Tesseract 做第二遍识别两个结果一致才入库不一致就标记为待人工复核。高精度接口accurate_basic返回的words_result里每个元素带probability字段值范围 0 到 1。C# 里读取就多一行代码var prob (double)item[probability]; if (prob 0.8) { // 置信度不足标记该行进入复核队列 }逻辑说明0.8 这个阈值是我在票据场景下试出来的经验值印刷体可以放松到 0.7手写体建议收紧到 0.9。参数说明阈值不要固定死先跑 100 张样本统计正确行和错误行的置信度分布再选分界点。本地 Tesseract 兜底最省事的接入方式是直接调命令行不需要引用 NuGet 包规避了不少 Tesseract 版本兼容性问题tesseract low_confidence.png stdout -l chi_sim --psm 6逻辑说明stdout让结果直接输出到控制台而不是写文件-l chi_sim指定简体中文语言包--psm 6声明这是一整块文本。参数说明--psm取值影响很大识别单行文字用 7识别整页用 3默认的 3 在单行场景反而不准。交叉验证的做法是把百度识别结果和 Tesseract 识别结果做字符串比对如果两者完全一致或相似度超过 0.9视为通过否则把这一行连同图片原始区域一起写入一个 JSONL 文件后续人工复核。这个 JSONL 文件就是你的后悔药出现批量识别质量问题时能精确回放每一行结果来自哪张图的哪个区域而不是翻半天日志。抽检验证方面我习惯每次调整阈值或接口后固定抽 50 张已知道正确答案的图片跑一遍算字符级准确率低于 95% 就回调参数。这套验证流程花费不超过半小时但能避免大批量任务跑完才发现识别质量不达标、全部返工的局面。希望帮到你。本文还有配套的精品资源点击获取