
接手过好几个把图片名字批量改掉的需求第一反应都是去搜现成软件结果要么收费离谱要么识别率拉胯要么压根不支持框选区域。做电商的朋友应该深有体会——一摞商品图角落货号各不相同想让文件名直接变成货号靠人工一张张打开复制重命名几百张图能搞一下午。做档案整理的更痛苦扫描件上那个编号位置是固定的但每次都要人工读出来再敲进系统。这种图片固定区域有文字想用文字当文件名的需求其实特别常见。这次我直接带大家做一个自用的小工具WPF 搭界面腾讯云 OCR API 做文字识别支持在预览图上框选固定区域然后批量拖入 jpg 图片程序自动识别区域内的文字并直接用于重命名文件。整个过程不需要你精通 C#按着步骤来就能跑起来后续要扩展成加前缀补日期防重名也都很好改。1. 先把这个需求拆清楚再动手1.1 需求本质和功能边界这类需求表面是找软件本质是个非常典型的流程自动化问题批量读取图片指定区域的文字信息映射到文件名。拆开看其实就三步——定位区域、提取文字、按规则改名。任何一步用人工做量大了都是体力活但用程序做核心就只剩一个难点怎么把图片区域的文字可靠地变成文本。在动手之前我建议大家先把自己的区域定义清楚。到底是一整张图的固定位置还是每张图的位置都不一样如果是前者工具做成用户框选一次后续所有图片都用这个区域效率最高。如果是后者那就得每张图都人工框选这个工具的自动化价值就大打折扣了。更复杂的场景还有图片尺寸不统一固定的矩形区域在不同尺寸下对应位置不一样。这种就需要做归一化处理比如按比例记录区域位置或者强制先统一图片尺寸。我的建议是工具先支持固定像素区域 比例区域两种模式比例区域在图片尺寸杂乱的场景下会更稳。另外还有一个容易忽略的边界问题识别出来的文字不一定干净。可能是货号ABC123这种带前缀的也可能是ABC 123中间带空格的还可能识别出一些杂乱的符号。这些在实际开发中都要处理否则文件名会很难看甚至直接因为非法字符报错。1.2 技术选型为什么是 WPF 加腾讯云 OCR做桌面工具可选的技术栈不少WinForms、WPF、Qt、Electron 都行。我选 WPF 主要三个理由第一C# 在 Windows 桌面开发里生态最成熟文件操作、图片处理这些都有现成的类库第二WPF 的布局和样式能力比 WinForms 强很多做图片预览 框选覆盖层这种交互很顺手第三后续要接 MVVM、做异步任务、绑定进度条WPF 的机制天然合适。当然如果你是 Python 熟练工用 PyQt 也没问题思路完全一样。文字识别部分才是真正的技术分水岭。本地方案有 Tesseract、PaddleOCR云端方案有各家 OCR API。我做了一个对比方案准确率中文支持部署成本适合场景Tesseract中等一般需要额外训练语言包本地运行免费对准确率要求不高、离线环境PaddleOCR较高较好本地运行但依赖安装稍复杂离线、批量大、可以接受环境折腾腾讯云 OCR高效果好印刷体识别很稳注册账号、获取密钥在线、追求准确率和开发效率我这次选腾讯云 OCR不是因为它比 PaddleOCR 强多少而是因为对大多数非算法从业者来说云端 API 的开发成本最低准确率又高得省心。你不需要处理模型文件、不用管 GPU、不用怕依赖冲突只要会用 HTTP 请求或者官方 SDK 就能搞定。唯一的代价是要联网、按量付费但个人批量改名这种量级基本一天也就几分钱到几毛钱忽略不计。1.3 工具体验设计目标开工之前我还给自己列了三条体验目标免得做着做着就跑偏整个流程要在一屏内完成选择图片目录、预览图片、框选区域、开始处理这些操作要集中不需要弹一堆二级窗口。批量处理时界面不能卡死识别是网络请求可能有几百毫秒到几秒的延迟必须做成异步配合进度条和日志让用户知道程序还在干活。操作要可撤销、可重试真实文件改名是有风险的操作最好先做预识别也就是先跑一遍识别并预览将要改成什么名字确认没问题后再真正执行重命名。2. 准备阶段账号、密钥和项目骨架2.1 开通腾讯云 OCR 并拿到密钥腾讯云 OCR 的接入方式非常标准先注册腾讯云账号然后在控制台里找到文字识别 OCR产品开通服务。接着进入访问管理 CAM创建一个 API 密钥拿到 SecretId 和 SecretKey。这两个字符串就是你的身份凭证调用 API 时用来签名鉴权。这里有一个常见的坑很多人开通了 OCR 产品但忘了在文字识别控制台里确认服务状态结果调用时报未开通服务或者套餐包不可用。建议开通后在控制台先手动测试一下通用印刷体识别能出结果再写代码。另外密钥一定要保存在程序配置里不要硬编码上传到公开仓库否则被别人拿去刷接口虽然单次便宜但量上来也能刷出账单。腾讯云 OCR 提供了多个接口我们这次用的是通用印刷体识别也就是 GeneralBasicOCR它对印刷体文字的识别速度适中、成本低非常适合图片货号、单据编号这类场景。如果你识别的是手写体就要换通用手写体识别参数会略有不同但框架完全一样。2.2 创建 WPF 项目并安装 SDK在 Visual Studio 里新建一个 WPF 应用项目目标框架可以选择 .NET Framework 4.7.2 或者 .NET 6/8看你自己环境建议直接用 .NET 6 以上写起来更舒服API 也更现代。项目名就叫 BatchOcrRename命名空间保持整洁。调用腾讯云 OCR 有两种方式。方式一用官方 SDK包名是 TencentCloudSDKNuGet 里搜索就能安装SDK 封装了签名和请求代码比较简洁。方式二直接通过 HttpClient 调用 REST API自己拼参数和签名适合想完全掌控请求过程的人。我个人建议新手用 SDK少踩签名的坑如果你项目里已经有很多 HTTP 基础设施自己拼请求也完全可行。安装 SDK 之后建议把密钥、区域配置写在一个独立的 Config 类里后续改起来方便。代码大概这样public static class OcrConfig { public static string SecretId 你的SecretId; public static string SecretKey 你的SecretKey; public static string Region ap-guangzhou; // 地域一般按资源所在地选 }这里的地域参数指的是腾讯云 API 的接入地域OCR 一般默认 ap-guangzhou 即可不是说你人在广州而是 OCR 服务集群的接入点。有些入参还需要填图片 URL 或 Base64这个后面说。2.3 项目结构规划拿到密钥、建好项目后先在脑子里面把代码分层想清楚这样可以避免后面写成一坨。我习惯这样分Views存放 MainWindow.xaml负责界面展示和用户交互。ViewModels如果你用 MVVM就把业务逻辑放在这里如果项目不大直接在 code-behind 里写也够用。Services封装真正干活的类比如 OcrService调用腾讯 OCR、ImageService裁剪、缩放图片、RenameService校验文件名、处理重名冲突。Models定义几个轻量数据结构比如识别结果、处理日志项。说实话做这种小工具不一定要强行上 MVVM界面逻辑也不复杂直接在 MainWindow.xaml.cs 里写也是完全 OK 的。但把 OCR 调用和文件操作单独抽成 Service 类是很有必要的因为这两个模块你后面一定会在其他项目里复用到抽出来以后调试也方便。在 Service 里写日志也方便定位是哪一层出的问题。3. UI 与交互框选区域是核心体验3.1 主界面布局主界面我采用左配置、右预览、下日志的三段式布局。左侧放目录选择、区域设置、命名规则和开始按钮右侧放图片预览底部是一个日志区域实时滚动显示处理进度。这样整个操作流程是从左到右、从上到下非常自然。左侧区域设置里的关键控件是一个框选区域按钮。点击之后程序进入框选模式此时你可以在右侧预览图上用鼠标拖拽一个矩形松开后矩形参数自动填入到对应的 TextBox 中。这个交互看起来不起眼但直接影响工具好不好用如果让用户手动填 X、Y、宽、高那体验基本就废了。所以框选交互必须做。底部日志区域我用了一个 ListBox 或者 TextBox每处理一张图就追加一行记录当前识别的文字重命名是否成功失败原因。有些人会觉得日志没必要但批量改名这种操作一旦出错你非常需要知道到底是哪张图出了问题所以日志是刚需。3.2 图片预览与缩放图片预览的核心是把一张大图缩放到控件尺寸显示。我用的方案是读取图片后按照 ScrollViewer 视口尺寸等比缩放得到一个缩放比 scale然后把 Bitmap 显示在 Image 控件上同时把坐标体系统一成原始图片像素坐标。这个统一很重要因为用户框选和实际裁剪必须用同一套坐标否则框的是 A 处裁剪的是 B 处就会出现区域错位。缩放比的计算很简单double scale Math.Min(viewWidth / imageWidth, viewHeight / imageHeight);然后在鼠标事件里把鼠标在控件上的位置转成原图坐标Point posInControl e.GetPosition(ImageControl); double originalX posInControl.X / scale; double originalY posInControl.Y / scale;这个逻辑虽然基础但非常容易出错尤其是控件外边距、Image Stretch 模式不一致时坐标换算会偏。我建议在界面上加一个调试开关显示当前鼠标的原始坐标方便你验证换算是否正确。3.3 框选区域交互框选交互我在 WPF 里用了一个覆盖层 Canvas。做法是在预览区的顶层放一个透明 Canvas监听 MouseLeftButtonDown、MouseMove、MouseLeftButtonUp 三个事件。按下时记录起点移动时实时绘制一个 Rectangle 覆盖层松开时把矩形参数写回设置框。覆盖层的绘制要注意一点你框选时希望看到的是一个半透明的矩形区域这个可以用 Rectangle 的 Fill 设置一个带 Alpha 的 Brush。比如用 #3300BFFF 这种带透明度的蓝色既能看清区域又不遮挡底下的图片。这个视觉细节看似小但体验感受很不一样。矩形参数计算完毕还要做一步人性化处理如果用户框的方向是反的从右下往左上拖X 和 Width 就会算出负数。统一用 Math.Min 和 Math.Abs 修正一下见下面这段代码double x Math.Min(start.X, end.X); double y Math.Min(start.Y, end.Y); double w Math.Abs(end.X - start.X); double h Math.Abs(end.Y - start.Y);4. 核心逻辑OCR 识别、图片裁剪、批量改名一条龙4.1 调用腾讯云 OCR 识别文字现在进入最核心的部分把区域内的图片转成文字。整个过程分三步裁剪区域图片、转成 Base64、调用 OCR 接口。裁剪区域图片用 System.Drawing 的 Graphics.DrawImage 就能实现。要注意的是WPF 项目默认没有引用 System.Drawing需要在项目里手动添加这个引用。裁剪的代码不长但有几个细节目标 Bitmap 的尺寸就是裁剪区域的宽高DrawImage 时把源图的裁剪区域映射到目标画布上代码长这样public static Bitmap CropImage(Bitmap source, int x, int y, int width, int height) { var cropRect new Rectangle(x, y, width, height); var target new Bitmap(cropRect.Width, cropRect.Height); using (var g Graphics.FromImage(target)) { g.DrawImage(source, new Rectangle(0, 0, target.Width, target.Height), cropRect, GraphicsUnit.Pixel); } return target; }裁剪完的区域图片还要转成 Base64 字符串才能传给腾讯云 OCR。腾讯云 OCR 接口支持图片 Base64 编码或者图片 URL 两种传参方式本地文件显然用 Base64 更方便。转换前我建议先对图片做一次压缩。原因很现实OCR 接口对图片大小有限制一般图片 Base64 编码后不能超过 7MB但原图如果很大Base64 后很容易就超了。压缩的思路是判断图片尺寸超过一定边长就先等比缩再用 Jpeg 编码器输出低质量图片。这个压缩步骤对识别率影响很小但能避免大量报错。转 Base64 的代码很简单using (var ms new MemoryStream()) { bitmap.Save(ms, ImageFormat.Jpeg); byte[] bytes ms.ToArray(); string base64 Convert.ToBase64String(bytes); }4.2 用 SDK 发起识别请求用腾讯云官方 SDK 发起识别请求的代码写起来很清爽。以 TencentCloudSDK 的 .NET 版本为例关键步骤是创建 Credential 对象、创建 OcrClient、构造 GeneralBasicOCRRequest 并填充图片 Base64然后调用 GeneralBasicOCR 同步或异步方法。大致如下using TencentCloud.Common; using TencentCloud.Common.Profile; using TencentCloud.Ocr.V20181119; using TencentCloud.Ocr.V20181119.Models; public class OcrService { private readonly OcrClient _client; public OcrService(string secretId, string secretKey) { Credential cred new Credential { SecretId secretId, SecretKey secretKey }; ClientProfile profile new ClientProfile(); _client new OcrClient(cred, ap-guangzhou, profile); } public async Taskstring DetectTextFromBase64Async(string base64Image) { GeneralBasicOCRRequest req new GeneralBasicOCRRequest(); req.ImageBase64 base64Image; GeneralBasicOCRResponse resp await _client.GeneralBasicOCR(req); if (resp.TextDetections ! null resp.TextDetections.Length 0) { // 把所有检测到的文本行拼接起来 return string.Join( , resp.TextDetections.Select(t t.DetectedText)); } return string.Empty; } }这里我特别说明一下拼接逻辑。腾讯 OCR 返回的是一个文本检测结果数组每个元素对应图片中的一行文字。如果你框选的区域里只有一行文字那就直接取第一个元素的 DetectedText。但实际中你可能框了一个表头区域包含多行那就需要把每一行用空格或下划线拼接。具体怎么接取决于你后续命名规则我一般用空格连接然后在命名规则阶段再统一清理。注意上面的代码用了异步方法 await _client.GeneralBasicOCR(req)这是避免界面卡死的重点。网络请求最慢可能几秒钟如果同步调用界面直接白屏假死用户体验会非常差。所以整个批量处理流程必须放在 async 方法里跑并且每一张处理完成就用 Dispatcher 或者 Progress 更新界面。4.3 自动改名与重名冲突处理识别出文字之后下一步就是把它变成文件名。这里有几个必须处理的硬问题第一非法字符过滤。Windows 文件系统不允许文件名里出现 \ / : * ? | 这些字符而 OCR 识别结果里很容易混入这些符号尤其是冒号和竖线。我用的过滤方法是正则替换string safeName Regex.Replace(rawText.Trim(), [\\\\/:*?\|], _); safeName safeName.Trim(.);额外要 Trim 掉首尾的空格和点。文件名最后如果带点Windows 虽然能创建但容易引发各种兼容性问题所以我直接去掉。第二长度控制。文件名最长 255 个字符但这么长的文件名基本没法用而且 OCR 偶尔会返回一段很长的乱码。我建议截断到 50 个字符以内超出部分直接剪掉。如果你担心截断后无法区分可以保留前 40 字符 后 10 字符的方式但这个看需求默认用前 50 就行。第三重名处理。批量处理的图片里同名区域文字完全可能重复比如两张图的货号一样。如果你的策略是直接覆盖那后面那张图就会覆盖前面的这是灾难。所以我用了经典的重名递增逻辑如果目标文件名已存在就在后面加 _1、_2、_3。代码是这样的string newPath Path.Combine(directory, safeName .jpg); int i 1; while (File.Exists(newPath)) { newPath Path.Combine(directory, ${safeName}_{i}.jpg); } File.Move(originalPath, newPath);这里还有一个细节重命名最好收集完所有原始路径-新路径的映射统一在最后确认时执行而不是每识别完一张就立即重命名。因为如果你在 UI 上增加了预识别功能用户可以先看到所有新名字确认无误后再批量执行。我的做法是第一轮只做识别并生成一个待处理列表用户点击确认改名之后程序再统一执行。4.4 异步批量处理与进度反馈批量处理的异步流程我用了一个很直观的模式遍历目录下的所有图片文件对每张图执行裁剪 → OCR → 生成新文件名三连每次完成就更新进度条和日志。这里我强烈建议使用 IProgress 来在后台线程和 UI 线程之间传递进度更新而不必手动 Dispatcher.Invoke代码干净很多。核心流程伪代码可以理解成下面这样var progress new ProgressProcessLog(log AddLogToUI(log)); await Task.Run(() ProcessAllFiles(progress)); private async Task ProcessAllFiles(IProgressProcessLog progress) { foreach (var file in imageFiles) { using (var bitmap LoadBitmap(file)) { Bitmap cropped OcrImageHelper.CropImage(bitmap, rect); string base64 OcrImageHelper.ToBase64(cropped); string text await _ocrService.DetectTextFromBase64Async(base64); string newName NameRuleHelper.BuildName(text); var log new ProcessLog(file, text, newName); progress.Report(log); } } }这里每张图的识别是串行的因为太多并发请求可能导致接口限流或者账单飙升。个人工具建议并发控制在 1最多 2。别傻乎乎地 Parallel.For 一下把所有图同时发出去接口可是按次收费的一旦并发触发限流报错还更麻烦。进度条不要只显示处理了多少张更好的方案是双进度当前文件和总进度。比如正在处理 3/120: 商品图_03.jpg这样用户就知道程序不是死机了而是在慢慢跑。我在日志区域还会显示识别出的原始文本方便用户在改名之前确认 OCR 是否准确。4.5 界面上的命名规则配置命名规则看似不起眼但我觉得是整个工具里用户感知最强的一部分。我做了几个常用配置项是否去掉识别结果中的所有空格、是否启用编号前缀、是否追加原文件名后缀。举个例子如果你处理的是商品图识别结果是ABC-201规则配置成追加原文件名最终名字就是ABC-201_商品图_03.jpg。这个小功能在实际生产环境里特别有用因为纯用 OCR 结果改名很简洁但有时候我们想保留原文件的某些线索。命名规则我建议做成一个单选下拉框或几个 CheckBox不要做成让用户填正则的输入框对于多数人来说正则学习成本太高。我们提供几个预设规则就足够了纯文本、纯文本加前缀、纯文本加原文件名、自定义格式。自定义格式里可以用 {text} 和 {original} 两个占位符程序在运行时替换成实际值够灵活也好理解。5. 实机调试与问题排查实录5.1 常见错误与定位手段我在开发和测试这个工具的过程中踩过的坑还挺多的挑几个典型的说说。第一个是InvalidParameterValue.ImageBase64Invalid错误也就是图片 Base64 无效。这个错误十有八九是你把原图的 Base64 传上去了但这个字段接收的不是data:image/jpg;base64,...这种带前缀的完整 Data URI而是纯 Base64 数据。我一开始直接把网上抄来的代码拿过来前缀没去掉结果一直报错排查了半天。现在我在封装 ToBase64 方法时会显式去掉前缀保证只传数据部分。第二个是FailedOperation.NoTextDetected也就是区域内没有识别到文字。这个不一定是代码的问题很可能你框选的区域本身就是空白或者文字太小。我的解决办法是识别前先对裁剪图做一次放大同时转成灰度图提高字迹清晰度。这里给个经验参数如果框选区域的宽度不超过 200 像素建议先按 1.5~2 倍放大识别率提升非常明显。第三个是网络超时。默认的 HTTP 请求超时时间是 100 秒但腾讯 OCR 正常一次请求在 1 秒以内。如果你发现经常超时先检查自己的网络环境再检查是否用了代理我遇到过一次是内网代理把请求转到一个慢节点直接拖垮了速度。另外如果你一次批量处理了几百张图要留意腾讯云的 QPS 限制一般免费额度的 QPS 比较低处理中要适当加个 Sleep 或者用信号量限流避免触发接口限流导致大面积失败。5.2 识别率优化图像预处理是关键很多人以为 OCR 准确率完全取决于 API 底包实际上图像质量占了很大因素。我测试过同一张图不做预处理直接识别和一个简单灰度化加锐化后识别结果差距明显。个人小工具不需要上多复杂的预处理几个步骤就够灰度化去掉颜色干扰让文字更突出。图像锐化可以用简单的卷积算子让文字边缘更锐利。对比度增强如果是扫描件发灰先用 HistogramEqualization 或者简单的自动对比度调整。二值化如果图片背景复杂可以考虑在灰度化之后做自适应阈值变成黑底白字或白底黑字。这些操作在 System.Drawing 里都能实现但要注意性能。对每张图都做完整预处理批量处理速度会肉眼变慢所以我的建议是先试试直接识别如果某类图识别率低再在工具里加一个预处理模式开关只在必要的时候打开。还有一个容易忽略的点框选区域本身要留点边距不要太贴近文字。我遇到过一种情况用户框选时把货号的上半截切掉了结果识别出来是BC-123而不是ABC-123。OCR 对截断字符非常敏感框选时上下左右多留出 10~20 像素的余量错误率会降低不少。5.3 批量改名的安全兜底建议文件重命名一旦批量执行如果规则写错可能几百张图全改乱套。我的兜底方案是在真正执行重命名操作之前先自动创建一个备份映射文件。简单说就是导出 CSV里面记录原始文件名、识别文字、新文件名用户看一眼就能发现问题。万一改错了也可以按 CSV 手工恢复虽然麻烦但不至于彻底完蛋。更进一步工具可以内置一个恢复模式就是读取之前生成的 CSV然后反向把文件改回去。我在实际项目中做过这个功能当时花了整整一下午写代码但后来再也没有因为改名焦虑过。如果你只是自己用不一定要做恢复模式但 CSV 映射一定建议导出。5.4 一些体验层面的小建议工具做完后我在使用中还总结了一些交互惯例一并分享给大家。把框选区域和开始处理这两个高频操作放在主界面的醒目位置按钮本身就应该是大按钮不要把它藏在菜单里。处理过程中允许用户取消。用 CancellationTokenSource 实现这样用户发现框选区域错了不用等 200 张图全部处理完才能改。日志区域显示颜色区分识别成功显示深色重名冲突显示橙色失败显示红色一目了然。支持拖拽多张图片到窗口里直接加入待处理列表比每次点选择文件更顺手。如果你有更进阶的需求比如识别完后还想把识别文字写入图片属性或者 Excel 表格这个工具的架构也完全支持只需要在处理完一张的事件里挂一个新的 Handler 即可业务逻辑不需要大改。6. 一些额外的扩展思路小工具做到这里已经能用了但如果你愿意再进一步有几个方向我觉得特别值得尝试。第一个是把本地 OCR 模型加进来作为离线备选。虽然腾讯云 OCR 很稳但偶尔会有离线场景需求或者大批量识别时想省点接口费用。PaddleOCR 虽然配置麻烦但在离线批量场景下其实很香。你可以把工具设计成在线优先、离线兜底先尝试云端失败或超时再自动切到本地 OCR这样兼顾准确率和可用性。我之前测试过 PaddleOCR 的部署用它的 C 预测库打包确实要费点功夫但功能上完全可以做到等价替换。第二个是加一个定时监控文件夹模式。比如设置一个文件夹程序每隔 10 秒扫描一次发现新图片就用固定区域识别并改名。这个功能对监控设备截图、摄像头抓拍、自动入库系统非常有用等于是把工具从人工触发升级成自动运行。第三个是支持其他图片格式。虽然标题里是 jpg但实际使用中很多人会拖入 png、bmp甚至是从微信导出的 dat 文件。这个工具在读取文件时直接按格式分流就行成本很低。尤其是 dat 文件如果你了解微信的图片存储方式转换回来也只是按字节做一次异或处理工作量不大。我个人做下来的体会是这类小工具最值钱的地方不是技术本身而是你愿意花时间去理解自己的真实流程。OCR API 再好也得有人把它接进一个得心应手的交互界面里规则再简单也得有人把重名、非法字符、异常恢复这些边角料处理干净。等这个工具真正跑起来你拖进去一批图喝口水的功夫看着文件名一行行变成整齐的货号那种感觉还是相当踏实的。