ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

C#调用百度OCR:从access_token到文字识别的完整实践

C#调用百度OCR:从access_token到文字识别的完整实践 简介该资源是一份基于C#调用百度OCR接口的桌面端图像文字识别示例包适合想快速上手AI文字识别应用开发的初中级.NET开发者。压缩包共29个文件以Form1.cs、Ocr.cs等C#源码为主并附带可执行程序、调试符号、界面设计资源、项目配置文件及说明文档既可直接运行体验识别效果也可对照源码理解API调用、参数封装与JSON结果解析流程。借助百度AI平台提供的通用文字识别能力开发者可将其扩展应用于证照信息提取、票据数字化、截图转文本等常见办公场景。目前已有225人学习下载适合用此案例作为入门百度OCR与C#程序集成的实践参考。1. 用 C# 调百度 OCR 识别图片这个压缩包里到底封了什么把一个扫描件里的文字变成可编辑的文本Windows 桌面工具里最常见的做法就是用 C# 写一个 WinForms 小程序调百度 AI 开放平台的 OCR 接口。OCR.rar 里的这套示例工程项目名是 OCR_Try它把整条调用链路拆得很干净按钮选图、图片转 Base64、获取 access_token、POST 识别请求、解析 JSON、把识别出来的句子写回文本框。适合刚接触百度 AI、不想啃官方 SDK 文档的 C# 开发者也适合手里有扫描合同或票据需要批量转文字的办公场景。注意包里不包含你自己的 API Key动手前需要去百度 AI 开放平台申请两个字符串——后面会专门讲怎么填、填在哪。整个项目最值钱的地方不是界面而是Ocr.cs里那段简化后的调用封装。它没有引入百度官方 SDK直接拿HttpClient手写请求从access_token到words_result全是显式控制。对于想搞明白「百度 OCR 到底怎么工作」的人来说这种写法比黑匣子 SDK 好懂十倍。2. 百度 OCR 调用链路先弄懂 access_token 和通用文字识别的报文格式2.1 识别接口的调用层次与 C# 侧它对应谁百度 AI 的文字识别不是 SDK 式的本地库而是一组 REST API。C# 程序要做的事就是按照百度定义的报文格式发 HTTP 请求然后解析返回的 JSON。整体分两步先用 API Key 和 Secret Key 换一个access_token再拿这个 token 去调识别接口。OCR_Try 工程里的Ocr.cs就是在干这两件事Form1.cs负责触发和展示。第一步是获取 token地址固定https://aip.baidubce.com/oauth/2.0/token需要带三个参数grant_typeclient_credentials、client_id填 API Key、client_secret填 Secret Key。百度返回的 JSON 里有一个access_token字段这个 token 的有效期大约是 30 天所以严谨的做法是第一次拿到后缓存到本地文件过期再重新拉取。我自己习惯把它存在程序目录下的baidu_token.txt里调用前先检查文件时间戳超过 28 天就重新拉一次。OCR_Try 示例里没有做缓存每次启动会重新获取做演示够用但放在长时间运行的服务里你会想给它加上的。第二步是调通用文字识别接口地址是https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic请求方式为 POST内容类型是application/x-www-form-urlencoded。关键参数是image它的值既可以是图片的 Base64 字符串也可以是图片 URL但两者只能提供一个同时给会报参数错误。另外还有两个高频可选参数detect_direction表示是否检测图像方向并自动纠偏language_type用来指定识别语言默认是CHN_ENG中英文混合。OCR_Try 的界面里只暴露了识别按钮但代码里已经留了这两个参数的传参位置后面会说到。2.2 动手前先验证用 curl 跑通一次标准识别写 C# 代码之前我强烈建议先用 curl 把接口跑通这样可以先排除「代码写错」和「Key 不对」两大类问题。你先在百度 AI 开放平台的控制台里找到应用的 API Key 和 Secret Key然后执行下面这两步# 第一步用 API Key 和 Secret Key 换 access_token替换成你自己的值 curl https://aip.baidubce.com/oauth/2.0/token?grant_typeclient_credentialsclient_id你的APIKeyclient_secret你的SecretKey这一步会返回一个 JSON里面带着access_token字段。你把它复制出来下一步要用。如果返回error相关字段通常是client_id或client_secret复制错了多一个空格都不行。接下来第二步是把一张测试图片转成 Base64再调识别接口# 先把图片转成 base64Linux/Mac 下直接这样干 base64 test.png test.txt # 再把 base64 内容放到 POST 请求里 curl -X POST \ https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic?access_token你上一步拿到的token \ -H Content-Type: application/x-www-form-urlencoded \ -d image$(cat test.txt)命令里的$(cat test.txt)是把文件内容原样拼到请求体里。注意这份 Base64 必须是干净的字符串不能带换行否则百度会返回image format error。正常情况下你会收到一段 JSON核心结构是这样的{ words_result: [ { words: 这是识别出的第一行文字, location: { top: 12, left: 20, width: 100, height: 30 } } ], words_result_num: 1 }words_result是一个数组每个元素是一行识别结果words是该行文字内容location给出了这行文字在图片上的外接矩形坐标top和left是起始点width和height是矩形的宽高。OCR_Try 的Ocr.cs只用了words把识别文本逐行拼起来返回但location其实很有用第五章会单独讲。这里有一个容易忽略的点general_basic是标准版接口免费配额下并发有限如果图片特别多建议改调accurate_basic高精度版正确率更高但价格也更高。Ocr.cs 里的接口地址目前写的是标准版想换高精度版的话把 URL 里的general_basic改成accurate_basic就行其余参数完全一致。3. 逐文件拆解 OCR_Try 工程Form1、Ocr.cs 与 JSON 解析是怎么配合的3.1 从 .sln/.csproj 看依赖WinForms 工程与 Newtonsoft.Json压缩包解开后是一个完整的 VS 解决方案OCR_Try.sln是解决方案入口OCR_Try目录下是项目本体。里面有几个文件需要先分清职责我把它们的角色列一下文件作用OCR_Try.sln解决方案文件双击用 Visual Studio 打开OCR_Try.csproj项目文件记录编译配置和引用Form1.cs 与 Form1.Designer.cs主界面的逻辑代码与设计器代码Ocr.cs百度 OCR 调用的封装类核心文件Program.csWinForms 程序入口Main函数所在OCR_Try.suo用户选项文件记录打开状态和断点删掉会自动重建bin / obj编译输出目录可随时重新生成.suo文件有个常见坑如果你打开解决方案时 VS 提示「未能加载某个包」或直接崩溃先把这个文件删了再重新打开九成情况能好。它记录的只是你上次开了哪些窗口、断点停在哪儿删了对项目没有任何影响。OCR_Try.csproj里除了默认程序集之外只多了一个关键引用Newtonsoft.Json。它是个开源的 JSON 序列化库C# 解析百度返回结果全靠它。如果官网下载的压缩包没带 packages 目录你需要用 NuGet 重新拉一次。在 VS 里右键项目 → 管理 NuGet 程序包 → 搜索Newtonsoft.Json安装即可。而我更推荐的做法是打开packages.config文件确认版本号再通过命令行安装对应版本避免 NuGet 自动装了最新版后代码编译不过。OCR_Try 里用到的 API 都是JObject和JArray这两个类型从 8.x 到 13.x 都稳定存在所以你装哪个版本都能跑但别低于 8.0。3.2 Form1.cs 的主流程选图、发请求、把识别结果填回界面Form1.cs的结构和大多数 WinForms 工具一样界面上一个 PictureBox 用来预览图片一个 Button 触发选图另一个 Button 触发识别最后用 TextBox 展示结果。打开图片的代码如下private void btnOpen_Click(object sender, EventArgs e) { using (OpenFileDialog dialog new OpenFileDialog()) { dialog.Filter 图片文件|*.jpg;*.jpeg;*.png;*.bmp; if (dialog.ShowDialog() DialogResult.OK) { // 把选中的图片加载进预览框 pictureBox1.Image Image.FromFile(dialog.FileName); } } }OpenFileDialog是 WinForms 自带的文件选择对话框Filter限制只能选常见图片格式。Image.FromFile直接按路径加载图片注意它会把源文件一直锁住直到调用pictureBox1.Image.Dispose()。如果你识别完还要对原文件做移动或删除记得在btnOpen_Click里用Stream解耦。识别按钮的代码是核心交互private async void btnRecognize_Click(object sender, EventArgs e) { if (pictureBox1.Image null) { MessageBox.Show(请先选择一张图片); return; } // 把图片转成 JPEG 编码的 Base64 字符串 using (MemoryStream ms new MemoryStream()) { pictureBox1.Image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); byte[] bytes ms.ToArray(); string base64 Convert.ToBase64String(bytes); Ocr ocr new Ocr(在这里填你的APIKey, 在这里填你的SecretKey); try { string result await ocr.RecognizeAsync(base64); textBoxResult.Text result; } catch (Exception ex) { MessageBox.Show(识别失败 ex.Message); } } }这段代码有三个细节值得说。第一MemoryStream和按钮事件都套了using和try/catch前者是为了及时释放非托管资源后者是为了防止百度返回错误后程序直接崩溃。第二图片统一转成 JPEG是因为 JPEG 对拍照扫描件压缩率高Base64 体积小传输更快但如果你处理的是带透明通道的 PNG 截图转 JPEG 会丢失透明区域变成黑底反而干扰识别这种情况应该改成ImageFormat.Png。第三await保证了 UI 不卡死——如果不加async/await而是在同步代码里直接调HttpClient你会发现点完按钮窗体立刻变成「未响应」这个坑第四章还会再讲。3.3 Ocr.cs 封装类图片转 Base64 与响应解析的核心代码Ocr.cs是整套工程里含金量最高的文件。它的结构很简单两个字段存 API Key 和 Secret Key一个私有方法拿access_token一个公开方法RecognizeAsync完成识别。完整代码大致是这样using Newtonsoft.Json.Linq; using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; namespace OCR_Try { public class Ocr { private readonly string _apiKey; private readonly string _secretKey; private readonly HttpClient _httpClient new HttpClient(); private string _accessToken; public Ocr(string apiKey, string secretKey) { _apiKey apiKey; _secretKey secretKey; } /// summary /// 获取 access_token有效期约 30 天 /// /summary private async Taskstring GetAccessTokenAsync() { string url $https://aip.baidubce.com/oauth/2.0/token $?grant_typeclient_credentials $client_id{_apiKey}client_secret{_secretKey}; string response await _httpClient.GetStringAsync(url); JObject json JObject.Parse(response); if (json[error] ! null) { throw new Exception($token 获取失败{json[error_description]}); } _accessToken json[access_token]?.ToString(); return _accessToken; } /// summary /// 识别图片 Base64 字符串返回纯文本 /// /summary public async Taskstring RecognizeAsync(string base64Image, bool detectDirection true) { // 如果没拿到 token先取一次 if (string.IsNullOrEmpty(_accessToken)) { await GetAccessTokenAsync(); } string url $https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic $?access_token{_accessToken}; // 用 FormUrlEncodedContent 构造请求体避免中文和特殊符号的编码问题 var requestBody new FormUrlEncodedContent(new[] { new KeyValuePairstring, string(image, base64Image), new KeyValuePairstring, string(detect_direction, detectDirection ? true : false) }); HttpResponseMessage response await _httpClient.PostAsync(url, requestBody); string responseText await response.Content.ReadAsStringAsync(); JObject json JObject.Parse(responseText); // 如果返回错误码直接抛异常方便上层捕获 if (json[error_code] ! null) { throw new Exception($百度 OCR 返回错误{json[error_msg]}error_code{json[error_code]}); } StringBuilder sb new StringBuilder(); JArray words (JArray)json[words_result]; foreach (var item in words) { string line item[words]?.ToString(); if (!string.IsNullOrEmpty(line)) { sb.AppendLine(line); } } return sb.ToString(); } } }代码逻辑按顺序拆开看构造函数把 API Key 和 Secret Key 存进私有字段_httpClient是HttpClient实例程序生命周期内复用避免每次请求都新建连接。GetAccessTokenAsync里把 token 请求拼好GetStringAsync拿到响应后直接扔给JObject.Parse解析如果返回 JSON 里带着error字段就说明 Key 有问题这里主动抛异常。RecognizeAsync的第一步是检查_accessToken是否为空空就先拿 token。第二步拼出识别接口的 URLtoken 放在 query string 里请求体用FormUrlEncodedContent构造比手动拼字符串可靠因为百度要求的image参数值是 Base64非得用application/x-www-form-urlencoded编码方式传输FormUrlEncodedContent会自动做好转义。第三步用PostAsync发请求把响应当成字符串读出来再 parse。最后遍历words_result数组把每一条words字段追加到StringBuilder按行返回。这里有个细节值得注意detect_direction参数默认是true意味着百度会自动判断图片是不是旋转过。但打开它会让响应时间变长如果你的图片全部来自机器生成的截图、方向本来就正建议在调用时传detectDirection: false来提速。language_type参数这段代码里没加你在FormUrlEncodedContent的新数组里再塞一项new KeyValuePairstring, string(language_type, CHN_ENG)即可这就是预留的扩展位。4. 避坑与常见问题百度 OCR 的返回码、图片大小和识别质量的真实边界4.1 三个高频翻车点token 失效、图片太大、UI 假死坑一请求返回 error_code 110提示 access_token 无效现象昨天跑得好好的今天一启动程序就报110 access_token invalid或者刚拿到 token 调识别接口就失败。原因access_token有效期约 30 天过期后必须重新获取。另一种情况是你把 token 拼到HttpClient请求头时写成了Bearer token而百度要求把它放在 URL 的 query string 里两者混用就会报这个错。解决在Ocr.cs里做一个简单的过期处理——把_accessToken保存到本地文件每次启动先读文件带时间戳超过 28 天就强制重新拉取否则直接用缓存值。拼 token 时严格按百度要求放 query string不要自己加Authorization头。如果两者排查完还报 110检查 API Key 的 Key 字符串有没有复制进不可见字符比如行尾的空格或换行这个坑我踩过一次排查了半小时。坑二返回 error_msg: file format error明明图片是 jpg现象用手机拍的图片能识别但用扫描仪导出的 jpg 传上去就报file format error。原因百度通用文字识别接口对图片大小有限制Base64 编码后的数据超过 4MB 会被拒绝扫描仪导出的图片分辨率高、体积大转成 Base64 后轻松超限。解决传输之前先把图片压缩。最稳妥的做法是在 C# 侧用Image对象的Save方法重新编码为 JPEG 并降低质量参数或者等比例缩放到最长边不超过 4096 像素。我的习惯是先用Bitmap加载原图判断宽度超过 2000 就按比例缩小再转 Base64。另外检查你拼接请求体时是不是用了StringBuilder直接拼image再接 Base64如果 Base64 字符串中间混入了文件读取时保留的换行符也会报这个错——Convert.ToBase64String默认不会换行但如果你用文件流手动读再转换就要注意清理。坑三点击识别按钮后窗体假死几秒后才恢复现象程序一运行点识别按钮整个窗口变成「未响应」白屏识别结果出来后界面才恢复。原因HttpClient的PostAsync是异步方法如果在同步事件里直接调用.Result或.Wait()WinForms 的 UI 线程会被阻塞界面消息循环卡住看不到任何响应。解决按钮事件用async void配合await调用RecognizeAsync就像 3.2 节代码里那样。如果你是非 UI 的桌面服务或控制台程序同步调用问题不大但在 WinForms 里永远不要对HttpClient的异步方法用.Result这是我见过的初学者最容易踩的 WinForms 网络编程坑。还有一个连带问题HttpClient不要用using包在每次请求里创建销毁socket 会被大量 TIME_WAIT 状态占用高并发时会报端口耗尽。4.2 图像质量与接口选型为什么有些图识别出来是一串乱码坑四白底金字、艺术字体识别率骤降现象普通文档截图识别得很好但海报、Logo、发票上的艺术字识别出来错字连篇甚至整行都是乱码。原因general_basic针对印刷体常规字做了优化对艺术字形、背景干扰、低对比度文字的能力有限。解决两个方向。一是换高精度版接口把 URL 从general_basic改成accurate_basic识别率有明显提升但注意这是付费接口免费额度少别拿一堆测试图去跑。二是预处理图片把图像先转成灰度再二值化增强文字和背景的对比度这个在 C# 里用System.Drawing的ColorMatrix就能做不用上 OpenCV。通常我先转灰度再用阈值 128 做二值化。处理后再调识别接口比直接扔原图成功率高不少。坑五竖排文字识别顺序是乱的现象一张竖排的古籍扫描件识别结果东一句西一句完全不是阅读顺序。原因general_basic默认按横排文字输出竖排场景需要专门用detect_direction配合方向检测或者调用了general_basic但没开方向矫正。解决识别请求里把detect_direction设为true百度会先检测图像方向自动摆正再执行识别。如果你处理的图片会旋转 90 度、180 度上传这个参数必须开。另外注意detect_direction只解决图像摆正不解决竖排排版——竖排文字要调百度专门的「竖排文字识别」接口字段是v1/ocr/handwriting或general下的direction参数。OCR_Try 这个示例里默认开的是detect_directiontrue覆盖了旋转场景但竖排书刊场景你得换接口。我按错误码整理了一张速查表放在手边可以少走弯路错误码含义处理方式17每天调用量超限换高精度版或提配额18QPS 超限加线程锁控制请求频率110access_token 无效重新获取 token检查拼写216201image 格式错误压缩图片清理 Base64 换行符216630识别错误确认图片清晰度、旋转方向5. 进阶用法把 location 坐标用起来做阅读顺序排序和批量导出Ocr.cs目前只把words字段拼成了文本但百度返回的每个location都带着top、left、width、height四个值。如果你在真实业务里需要按人类的阅读顺序组织文本而不是按识别接口返回的原始顺序就必须用这些坐标做二次排序。常见的做法是先按top聚类成行再按left在行内排序。下面是替换RecognizeAsync内 JSON 解析部分的一种写法var sortedLines ((JArray)json[words_result]) .Select(x new { Text x[words]?.ToString(), Top (int)x[location][top], Left (int)x[location][left], Height (int)x[location][height] }) // 按行高聚类同一行文字的 top 值通常落在同一个区间 .GroupBy(x x.Top / x.Height) .OrderBy(g g.Key) .SelectMany(g g.OrderBy(x x.Left)) .ToList(); foreach (var line in sortedLines) { sb.AppendLine(line.Text); }GroupBy(x x.Top / x.Height)是一个粗略的分行算法核心思想是同一行文字中心点的top坐标相近除以各自行高能把不同行区隔开。这个阈值不是万能的行间距特别小的时候会串行你可以把除数改成固定值 30 或 40根据你处理的图片实际情况调整——操作上就是多测几张图看看哪一组数值能把行分得干净。排序后的sortedLines保持了阅读顺序接下来可以直接写文件string savePath Path.Combine(Application.StartupPath, result.txt); File.WriteAllLines(savePath, sortedLines.Select(x x.Text), Encoding.UTF8);File.WriteAllLines带上Encoding.UTF8是因为 Windows 默认 ANSI 编码不指定的话识别出的中文在别的编辑器里会乱码。如果你要导成 CSV 做表格分析把一行的多个字段用逗号拼起来再写同样适用。这种坐标排序的方法做合同关键字段抽取、试卷填空批改、票据录入这类场景都能用上。最后一次提示OCR_Try 本质是「能跑的最小演示」不需要一次把它的代码全看懂再动手。你先把 API Key 填进Form1.csF5 跑起来选一张清晰的中文截图看能不能识别出来。再打开Ocr.cs把general_basic改成accurate_basic对比一次识别结果的变化。跟着这套步骤走一遍你对 C# 调百度 OCR 的整个链路就有底了。我自己每换一台机器拉这个项目都会先跑一遍 curl 验证 Key再跑程序验证代码——两条路对比着看问题出在哪一层马上就清楚。希望这篇拆解帮到你动手跑通一个接口比读十篇接口文档都管用。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进