
做批量图片改名的需求很多时候不是因为懒而是因为图太多。手头攒了几百张JPG全是产品截图、票据扫描件或者某种固定版式的单据文件名全是“IMG_20231021_093847.jpg”这种想按内容检索的时候根本没法找。一个个打开图片看内容再手动改名几百张下来人直接就麻了。我当初也是被这个事逼得没办法搜了一圈有没有现成的批量图片识别改名工具结果要么是收费软件还限制识别次数要么是开源项目但只支持整图识别、没法指定区域装上试了几个都不顺手。后来干脆自己动手用C#做了个WPF小程序接腾讯云的OCR接口把“批量识别图片指定区域文字并自动重命名”这件事彻底解决了。这篇文章就把整个方案的思路、选型、实现过程、踩过的坑一次性讲清楚给同样被图片改名折磨的朋友一个可以直接抄作业的参考。1. 需求拆解与方案选型1.1 这个工具到底要解决什么问题先说清楚核心场景。不是所有图片都需要整张识别很多情况下文字只出现在图片的固定区域。比如你有一批扫描的发票发票号码就在右上角那一小块比如你有一批商品截图商品编码固定显示在左下角再比如你从监控或者设备上导出的截图时间戳总是在顶部居中的位置。如果对整张图片做OCR一方面识别结果里混入大量无关文字命名规则很难统一另一方面腾讯云的OCR接口虽然便宜但不是免费识别整图比识别局部区域消耗的资源多批量跑下来费用也会差不少。所以这个工具的第一需求不是“OCR识别”而是“区域识别”。指定一个矩形区域只对这块区域做文字识别然后把识别出来的文字作为文件名主体。第二个需求才是批量能遍历整个文件夹的JPG一张接一张地处理。第三个需求是改名识别结果还要经过清洗——去掉换行、空格、特殊符号再拼上日期或者其他前缀后缀最后安全地重命名文件。把这三个需求串起来就是一套完整的自动化流程。如果你只是偶尔处理三五张图那手动改名也不算什么但一旦量级到几十上百张手动操作的低效就会被无限放大。我做这个工具的触发点就是一次要处理两百多张设备巡检截图每张图右上角都有设备编号和拍摄时间我光改名就改了快两个小时中途还改错了好几个。所以这类需求的本质是把重复性的“看图-记内容-改名”这个机械劳动交给程序去完成。1.2 为什么选C# WPF而不是Python或Electron方案选型这块我确实纠结过。最先想到的是Python因为Python写脚本处理这种批量任务是最快的PIL打开图片、裁剪区域、requests调HTTP接口几十行代码就能跑起来。但问题是纯命令行工具对日常使用不友好。你可能今天想处理A文件夹明天想处理B文件夹每次都要改脚本里的路径参数用起来很别扭。而且如果你不是天天写Python的人过两个月再回来看这段脚本还得重新回忆逻辑。Electron倒是能做界面但为了一个批量改名的小工具塞一个Chromium进去打包出来两三百MB我觉得太臃肿了。相比之下C# WPF是更合适的选择Windows原生环境跑启动快内存占用小做文件批量处理这种IO密集型任务性能也够而且微软官方的文件对话框、拖拽支持、列表控件都很好用开发效率其实不低。还有一个现实原因我平时的工作环境就是Windows目标是做一个双击就能跑、不依赖Python环境、不需要命令行操作的小工具。WPF打包出来一个exe扔给同事也能直接用不需要对方装解释器或者配环境变量。如果你手头有Visual Studio或者Rider建一个WPF项目也就是几分钟的事成本并不比写Python高多少。1.3 为什么选腾讯云OCR而不是本地Tesseract或PaddleOCR这个是很多人都会问的问题本地明明有开源的OCR方案为什么要花钱调云端接口我的回答是看你要识别的图片质量和要求的准确率。本地开源的方案里Tesseract是知名度最高的但说实话它对中文印刷体的识别效果只能说差强人意尤其遇到图片背景复杂、文字颜色和背景接近、分辨率不高的情况识别出来的结果经常是乱码。PaddleOCR的准确率要好很多但部署起来有门槛需要Python环境、依赖库如果你要用C#调用还得多一层进程间通信开发成本一下就上去了。云端OCR接口的优势恰恰体现在这里。腾讯云的通用印刷体识别对中文的支持非常好像宋体、黑体、仿宋这些常见字体都能准确识别还支持竖排文字、生僻字甚至对倾斜文字也有一定的容错能力。而且接口返回的数据结构很规整直接给出行、字、置信度你可以很方便地筛选出识别结果。准确率方面我自己实测正常清晰度的截图几乎是99%以上这比本地Tesseract强了不止一个档次。另一个原因是成本。腾讯云OCR新用户有免费额度日常跑个几百张图几乎是零成本。就算超过免费额度也是按次计费单价很低。对一个批量改名工具来说这个成本完全可以忽略不计。我当时算了一笔账处理一万张图如果每张只裁剪一小块区域识别费用大概几块钱比我自己手动改名的机会成本低太多了。2. 腾讯云OCR接入的完整准备2.1 开通OCR服务与拿到密钥腾讯云OCR的接入需要三个东西SecretId、SecretKey以及开通对应产品的服务。详细操作流程是登录腾讯云控制台搜索“文字识别OCR”进入产品页面后点击“开通服务”。新用户会要求实名认证认证通过后就能领取免费额度。然后在“访问管理 API密钥管理”里新建一个API密钥会生成一对SecretId和SecretKey。这两个字符串就是你调用接口的凭证SecretKey尤其敏感千万不要提交到Git仓库或者发给别人。如果你之前没用过腾讯云这个步骤可能会绕一下但整体还算顺畅。需要注意的是开通服务的时候要确认你选的接口是“通用印刷体识别”不是“通用手写体识别”或者其他子产品。不同接口的免费额度和计费标准不一样开通错了后面调用会报“接口未开通”之类的错误。密钥拿到之后建议直接写进程序的配置文件里不要硬编码在代码中。我后面会在界面上留一个设置入口让用户自己填密钥这样程序发给别人用的时候别人用自己的密钥不会产生费用归属的纠纷。密钥的保管原则就一条谁调用谁付费密钥不进代码。2.2 理解腾讯OCR的通用印刷体识别接口腾讯云通用印刷体识别的接口调用方式有两种一种是直接调HTTP API按照签名规则生成请求参数另一种是使用腾讯云官方提供的SDK比如TencentCloudSDK它封装了签名和请求细节开发起来更省心。我做的时候用的是SDK方式因为签名算法如果自己实现要处理HMAC-SHA256、排序、编码这些问题虽然不难但容易出错没必要重复造轮子。SDK调用的大致流程是构造一个客户端对象传入SecretId和SecretKey然后构造一个识别请求对象传入图片的Base64编码和识别参数最后发起请求从响应对象里取出识别结果。这个过程中图片Base64编码是个关键点接口要求图片经过Base64编码后的大小不能超过7MB分辨率不能超过6000×8000像素实际裁剪出来的小图一般都是几十KB远远在限制范围内。接口返回的TextDetections是一个数组每个元素代表检测到的一行文字包含DetectedText字符串、置信度Confidence、以及文字在图片中的坐标信息。对于区域识别来说我们主要关心的是DetectedText因为裁剪出来的区域一般只有一行或几行文字直接把识别到的文本拼接起来就行。如果识别出来多行可能需要按行拼接或者按置信度过滤这个我后面在命名规则处理里详细说。2.3 把密钥安全地放进配置密钥配置这一步看似小事但处理不好会埋坑。最简单的方式是在程序目录下放一个config.json或者appsettings.json里面存SecretId、SecretKey、识别区域、命名模板这些信息。程序启动时读取配置界面上显示当前配置状态用户随时可以修改并保存。这样既不用改代码也方便把程序复制到别的电脑上用。如果你打算把这个工具分享给别人用更好的方案是做成“首次启动填写配置”的交互。我见过有的工具直接把密钥写在代码里然后发给同事结果人家用了半个月费用全算在你头上这事真的会发生。防呆设计很重要密钥填一次保存到本地配置文件后续启动自动加载界面上用密码框隐藏显示。即便别人拿到你的程序只要密钥不是明文暴露在界面上风险就可控。我自己的做法是把配置文件放在exe同级的Config目录下内容大概是{ SecretId: 你的SecretId, SecretKey: 你的SecretKey, Region: ap-guangzhou, RecognizeArea: 0,0,400,100, NameTemplate: {text}_{date}, SourceFolder: C:\\Images\\Input, OutputFolder: C:\\Images\\Output }Region一般填ap-guangzhou就行这个不影响OCR的功能区域只是服务接入点。RecognizeArea是识别区域的四个坐标值用逗号分隔分别代表左上角X、左上角Y、区域宽度、区域高度。NameTemplate是命名模板{text}代表识别出来的文字{date}代表拍摄日期或者文件修改日期。SourceFolder和OutputFolder的配置让我在切换不同批次图片时特别方便不用每次弹窗选目录直接改配置然后点“开始”就行。3. WPF界面与批量处理核心实现3.1 界面布局与交互逻辑WPF做界面自由度很高XAML写起来比WinForms优雅很多。我这个工具的界面不算复杂分几个区顶部是文件夹选择区域放两个输入框加“浏览”按钮一个选源文件夹一个选输出文件夹中间是区域设置区域用四个数字输入框分别填X、Y、宽度、高度旁边放个“预览”按钮可以打开当前选中的图片并把矩形区域画出来给人看下面是一块参数区包括腾讯云的SecretId、SecretKey输入框、命名模板输入框再往下是日志输出区用一个TextBox或者ListBox实时显示当前处理到哪个文件、识别出什么字、改成什么名最底部是“开始处理”按钮和大进度条。布局用Grid划分行每行高度用Auto或者Star控制窗口最小尺寸设成900×700这样在1080P的屏幕上看着舒服也不会因为控件太多显得拥挤。整个过程没有特别炫酷的样式就是简洁实用。WPF的绑定机制很好用虽然做这个小工具直接事件处理也够但我还是用了MVVM的基础框架把界面的数据绑定和业务逻辑解耦这样后续想加功能也好维护。交互逻辑上有一个关键点开始处理之后所有输入控件要禁用掉防止用户中途修改参数导致状态混乱。进度条用ProgressBar绑定当前处理的文件编号和总数日志区把每个文件的处理结果打出来。处理过程中用户可以随时点“取消”这个取消事件要放在每个文件处理的间隙检查而不是在处理中途硬切线程否则容易出现文件句柄没释放的问题。3.2 图片区域裁剪让OCR只识别指定区域区域裁剪是实现“区域识别”的关键环节。WPF里读取图片的常用方式是BitmapImage或者System.Drawing.Bitmap我建议用System.Drawing.Bitmap因为裁剪操作更直接。核心代码是using System.Drawing; using System.Drawing.Imaging; public static Bitmap CropBitmap(Bitmap source, Rectangle region) { Bitmap cropped new Bitmap(region.Width, region.Height, PixelFormat.Format24bppRgb); using (Graphics g Graphics.FromImage(cropped)) { g.DrawImage(source, new Rectangle(0, 0, region.Width, region.Height), region, GraphicsUnit.Pixel); } return cropped; }这段代码的逻辑很直白创建一个目标尺寸的空白位图用Graphics.DrawImage把源图的指定区域绘制到新位图上。注意PixelFormat我用了Format24bppRgb这样输出的图片固定24位RGB格式统一不会出现有些PNG带透明通道导致编码异常的情况。裁剪之前需要先加载原图。加载的时候有个常见问题Bitmap.FromFile会锁住文件导致后面重命名文件的时候报“文件正由另一进程使用”。解决办法是用FileStream打开文件再创建Bitmap用完立刻释放using var fs new FileStream(filePath, FileMode.Open, FileAccess.Read); using var bitmap new Bitmap(fs); var region GetRegionFromConfig(); using var cropped CropBitmap(bitmap, region);这个坑我踩过一次第一次写完工具跑起来识别明明成功了但改名一直失败日志里报“文件被占用”排查了好一会儿才发现是Bitmap.FromFile锁住了文件。换成FileStream之后世界安静了。从这里也能看出写文件处理的代码时一定要留意文件句柄生命周期任何可能打开文件的API都要确认它不会一直占着资源不放。坐标还要注意边界检查。如果配置的识别区域超出了图片的实际尺寸Graphics.DrawImage不会报错但裁剪出来会是空白区域识别结果自然为空。所以代码里要做一次IntersectsWith判断var imageRect new Rectangle(0, 0, bitmap.Width, bitmap.Height); if (!imageRect.Contains(region)) { region Rectangle.Intersect(imageRect, region); }裁剪前尽量把区域限制在图片范围内这个逻辑放在公共方法里后续处理每张图片都会自动执行。3.3 批量遍历文件夹与图片解码遍历文件夹的核心逻辑分两步拿到所有JPG文件逐个处理。文件枚举用Directory.EnumerateFiles遍历可以传SearchOption.AllDirectories来决定是否递归子文件夹。我个人建议默认只处理当前文件夹因为子文件夹里的图片如果也自动处理很容易误伤。如果你确实需要递归在界面上加一个“包含子目录”的复选框就够了。图片解码这一步需要注意JPG文件虽然扩展名是.jpg但文件内容不一定是真正的JPEG编码。有的文件名被改成.jpg实际内容可能是PNG或者BMP。用System.Drawing.Bitmap的构造函数加载时多数情况下它能自动识别但偶尔遇到格式异常的图片会抛异常。为了不让一张异常图片中断整个批量流程处理循环里必须加try-catch异常信息记录到日志后继续下一张。foreach (var file in files) { try { ProcessSingleFile(file, config); progress.Report(index); } catch (Exception ex) { Log($[错误] {Path.GetFileName(file)}: {ex.Message}); } index; }批量处理还有一个线程模型的问题。如果整个循环跑在UI线程上界面会卡死拖动窗口时没有任何响应这对用户体验是致命的。所以处理过程必须放到后台线程用async/await或者Task.Run跑循环同时通过IProgress 把进度消息回传到UI线程。我在工具里用了Progress 和IProgress 的组合进度更新的回调里用Dispatcher.Invoke或者直接用Progress 自动切换到UI线程界面就能实时刷新了。图片解码成Base64字符串是调用腾讯OCR接口前的最后一步。把裁剪后的Bitmap转成MemoryStream再调用Convert.ToBase64Stringusing var ms new MemoryStream(); cropped.Save(ms, ImageFormat.Jpeg); string base64 Convert.ToBase64String(ms.ToArray());这里有个小优化技巧如果裁剪出来的区域文字简单其实可以适当降低图片质量来压缩Base64大小QA参数设成90已经足够再低可能会影响识别准确率。实测下来一个300×80的裁剪区域JPEG质量90编码出来的Base64字符串大约10~20KBHTTP请求毫无压力。3.4 调用OCR接口识别文字腾讯云官方SDK的NuGet包名是TencentCloudSDK我用到的是Ocr模块。安装之后核心调用代码大致是using TencentCloud.Common; using TencentCloud.Ocr.V20181119; using TencentCloud.Ocr.V20181119.Models; public async Taskstring RecognizeTextAsync(string base64Image, string secretId, string secretKey) { var client new OcrClient(new Credential { SecretId secretId, SecretKey secretKey }, ap-guangzhou); var request new GeneralBasicRecognizeRequest(); request.ImageBase64 base64Image; var response await client.GeneralBasicRecognize(request); var text string.Join(, response.TextDetections .Where(d d.Confidence 80) .Select(d d.DetectedText)); return text; }注意几个细节。第一是SDK版本要选对腾讯云的C# SDK版本更新比较频繁用NuGet装最新稳定版就行。第二是GeneralBasicRecognize对应的是“通用印刷体识别”这个接口返回的TextDetections是按从上到下、从左到右的顺序排序的但如果你裁剪的区域是竖排文字可能需要按坐标排序再拼接。第三是Confidence过滤我设了80作为阈值实测下来低于80的识别结果确实容易出现错字但代价是漏掉一些低清图片的有效信息这个阈值可以根据实际效果调整。并行识别这块值得讨论。批量处理图片时一张一张串行调用接口虽然稳但速度确实慢——每张图从裁剪到识别完成大约要一两秒200张图就是三四分钟。腾讯云OCR的QPS默认限制是10也就是说理论上你可以在QPS限制内并行发请求。我在程序里用了SemaphoreSlim控制并发数设置成5个并发实测速度提升接近三倍也没有触发限流。但并发高了之后要留意SDK的线程安全每个线程里创建独立的OcrClient实例更稳妥。另外还要考虑网络异常。HTTP调用受网络波动影响很大比如超时、连接中断、服务端限流返回429等。为了提升批量的健壮性我在调用方法里加了一个简单的重试机制遇到网络类异常或限流类错误码时最多重试3次每次间隔2秒、4秒、8秒递增。这个退避策略虽然简单但对付偶尔的网络抖动非常有效。3.5 命名规则处理与非法字符过滤识别出文字后直接用来做文件名是不行的。Windows文件名不允许包含以下字符\ / : * ? |。而OCR识别出来的内容里经常会出现斜杠、冒号、问号这些符号比如识别出“2023/10/21”这种日期格式如果你不处理File.Move直接抛异常。所以命名处理有一个清洗函数核心逻辑是public static string SanitizeFileName(string raw) { var invalidChars Path.GetInvalidFileNameChars(); var builder new StringBuilder(raw.Trim()); foreach (var c in invalidChars) { builder.Replace(c, _); } while (builder.ToString().Contains(__)) { builder.Replace(__, _); } return builder.ToString().TrimEnd(., ); }路径非法字符统一替换成下划线连续下划线合并成一个结尾的点和空格去掉——Windows资源管理器不允许文件名以点或空格结尾。这些细节写代码时容易忽略但真跑起来会遇到大量这种问题。命名模板的解析也很关键。我支持几个占位符{text}表示识别文本{date}表示文件修改日期{time}表示修改时间{index}表示序号。举个例子如果你设置命名模板为“{date}{text}{index}”那么识别出来“SN-20231021”文件修改日期是2023年10月21日序号从001开始生成的文件名就是“2023-10-21_SN-20231021_001.jpg”。这种带序号的模板很重要因为识别结果重复时不加序号就会互相覆盖。我在解析模板时用正则替换代码很简洁string BuildFileName(string rawText, string template, int index, DateTime fileTime) { string text SanitizeFileName(rawText); string date fileTime.ToString(yyyy-MM-dd); string time fileTime.ToString(HHmmss); string seq index.ToString(D3); string result template .Replace({text}, text) .Replace({date}, date) .Replace({time}, time) .Replace({index}, seq); if (string.IsNullOrWhiteSpace(result) || result .jpg) { result unnamed_ seq; } return result .jpg; }这里要注意如果识别结果为空或者清洗后被清空了必须有个兜底命名否则文件就会变成.jpg这种没有任何信息量的名字。我统一用“unnamed_序号”做兜底至少在日志里能清楚地看出哪张图识别失败。3.6 异步与并发别把界面卡死WPF程序的界面卡死是个老生常谈的问题。批量处理时如果你把整个循环写进按钮的Click事件里同步执行界面上那个进度条就会变成“假死”状态鼠标移过去还显示转圈用户根本不知道程序是在跑还是崩溃了。解决办法就是开异步任务UI线程只负责刷新界面。我用的是async/await模式按钮点击事件标记成async void循环体内用await Task.Run包住同步的耗时操作或者干脆把整个处理循环写成一个异步方法。关键代码大概是private async void BtnStart_Click(object sender, RoutedEventArgs e) { SetControlsEnabled(false); var progress new ProgressProcessReport(report UpdateUI(report)); try { await Task.Run(() ProcessBatch(progress, cts.Token)); } catch (OperationCanceledException) { Log(用户取消了操作。); } finally { SetControlsEnabled(true); } }进度类ProcessReport里包含当前处理文件路径、识别文本、目标文件名等字段Progress 的回调会自动在UI线程执行这样日志区就能实时滚动。还有一个小细节处理完成后弹出一个MessageBox提示“处理完成共成功X张失败Y张”这个提示要放在await之后、finally之前确保此时UI线程已经解除锁定不然MessageBox弹不出来。类似的坑看起来小实际遇到时很耽误时间写程序时最好把所有UI交互都放在异步方法的正确位置。4. 常见问题与排查实录4.1 OCR识别不到文字批量跑的时候发现某几张图识别出来始终是空的先别急着怀疑API。最常见的原因是识别区域设置不对尤其是从不同的图片来源切到同一套区域配置时图片分辨率变了原来的区域可能完全落在图片外面或者只覆盖了空白背景。这时可以先在程序里加一个“导出裁剪预览”功能——处理前把每一张图的裁剪结果保存到临时目录人眼扫一眼就知道区域是否准确。这个功能虽然简单但在调试阶段价值巨大。另一个原因是图片本身没有EXIF或颜色空间异常极少数情况下Bitmap加载出来的图片内容是一张纯黑底的图OCR当然什么都识别不到。这时可以试着用图片编辑软件打开原始文件确认内容是否存在排除了图片本身的问题再检查Base64编码这一步有没有出错。还有一种容易被忽略的情况识别区域内文字颜色和背景对比度极低。比如浅灰色文字印在白色背景上OCR模型识别不出是人之常情连人眼都要仔细看。这种情况只能改进图片采集方式尽量保证文字清晰、对比度高靠程序后处理改善的空间很有限。4.2 并发请求被限流腾讯云OCR的限流逻辑我之前遇到过并发数开太高短时间内打了几百个请求突然开始大量报429或者RequestLimitExceeded。排查之后发现是触发了QPS限制。解决办法除了前面说的SemaphoreSlim控制并发数之外还可以做一层“请求优先级”控制也就是按顺序提交任务而不是一股脑全丢进线程池。我最终把并发数设成5重试逻辑设成3次跑了一千多张图没有被限流过。如果你处理的量更大建议把并发数降到3配合一个简单的令牌桶算法控制请求速率。令牌桶实现不复杂网上有很多现成代码但说实话对个人工具来说SemaphoreSlim已经够用了不需要上太复杂的方案。另一个实际经验不要把并发设置成可以随便调大的参数暴露给用户。用户看到“并发数”输入框很容易填个50进去然后接口直接被限流体验反而更差。我后来把并发数固定成编译期常量界面上不展示用户不需要理解底层限制工具自己按最稳的方式跑就行。4.3 重命名冲突与重复文件名批量处理时经常遇到一种情况两张图上同一个区域识别出的文字完全一样比如都是“合格”两个字如果命名模板里没有序号第二张图就会覆盖第一张文件全丢。这个问题非常致命还好在测试的时候发现了。解决方法是加一个重名检测字典在处理每一张图片之前先把已生成的最终文件名放进HashSet如果后续生成的名字已经在集合里就自动追加序号。注意这个“追加序号”的时机要放在模板解析之后、文件真正重命名之前而且日志区要把冲突的情况明确打出来方便用户知道哪些文件被自动加了序号。string finalPath Path.Combine(outputFolder, fileName); string finalName fileName; int dupIndex 1; while (usedNames.Contains(finalPath)) { string withoutExt Path.GetFileNameWithoutExtension(fileName); finalName ${withoutExt}_{dupIndex:D2}.jpg; finalPath Path.Combine(outputFolder, finalName); dupIndex; } usedNames.Add(finalPath);这段处理逻辑不算复杂但没有它的话后果很严重。所以做批量文件操作的程序重名检测是必修课不是可选项。另一种冲突是目标文件夹里已经存在同名文件。我提供的策略是新建一个输出文件夹来存放改名后的图片原始文件夹里的文件不动。这样即使输出文件夹之前跑过一次里面已经有同名文件也不会覆盖历史数据最多是重复运行会跳过同名文件。日志里记录“跳过已存在文件”的状态用户自己决定要不要先清空输出目录。这种“源目录只读、输出目录独立”的设计避免了大量文件操作的潜在事故。4.4 WPF界面不刷新和批量跑太久的问题WPF界面刷新滞后多数原因是UI线程被阻塞但还有一个很隐蔽的情况日志区用的TextBox如果绑定了大量文本每次追加一行都会触发完整的布局计算几千行日志下来界面响应会明显变慢。优化办法是把日志控件换成带虚拟化的ListBox只保存最近500条日志超过就自动移除。如果坚持用TextBox那就限制最大行数比如超过300行就清掉前半部分。批量跑太久这个问题本质上是受限于API并发和图片处理速度。如果你有几千张图要处理跑完可能要大半小时期间程序不能退出否则进度全丢。所以我给程序加了一个简单的状态文件机制每处理完一张图就把文件名和结果记录到一个txt里下次启动时如果检测到状态文件可以选择“继续上一次未完成的任务”跳过已经记录过的文件。这个功能不算刚需但遇到超大批量时是真的救命。4.5 其他零碎但值得说的坑还有一个容易踩的坑是图片文件名的编码问题。文件系统里的文件名是UnicodeJPG文件里嵌入的EXIF信息可能是GBK或其他编码如果你的程序在读取EXIF时间时直接按默认编码解析中文可能乱码。我用的是System.Text.Encoding.UTF8做默认解析大多数现代图片没问题但老旧图片偶尔还是会出乱码。稳妥做法是解析EXIF时做一次编码检测不过对批量改名工具来说时间部分只用来拼文件名乱码影响不大可以接受。另外一个值得注意的问题是腾讯云SDK的依赖库更新。NuGet包更新后有的版本会改变命名空间或者某些API签名升级SDK时建议先编译一次看看有没有破坏性变更。我遇到过从5.x升到6.x的时候Credential构造函数变了编译直接报错。遇到这种情况不要慌去官方GitHub仓库看Release Notes就行。5. 这个工具后续还能怎么扩展工具写到这里其实已经能满足日常大量的批量图片改名需求了。如果你有更高的要求还可以在现在这个基础上继续扩展。比如加入“识别结果二次编辑”功能批量处理完生成一个Excel或者CSV里面列出原文件名、识别结果、目标文件名用户可以在表格里手动微调后再执行改名。这类流程在一些ERP系统的数据导入场景里非常有用。再比如加入“模板预设”功能。不同批次的任务有不同的区域坐标和命名模板把它们保存成不同名字的配置方案下次直接下拉选中。我现在的配置文件是单份的换任务时手动改JSON如果你有十几种不同类型的图片需要处理预设功能能省下大量配置时间。还有更进一步的玩法是支持更多图片格式比如PNG、BMP、TIFF甚至直接从PDF里提取页面转成图片再识别。腾讯云OCR本身也支持PDF识别但那个是另一套接口命名逻辑需要调整。如果处理的是扫描版PDF可以直接把每页转成图片再走现在这套流程效果也不错。这个工具的扩展空间实际上是很大的。核心的“裁剪区域 批量识别 模板命名”这套逻辑一旦搭好其他功能都是在这条主线上做增量。我自己后续打算加一个“识别结果预览表格”的功能先把所有图片的识别结果批量跑出来在界面上形成列表勾选或者修改后再统一改名。这样既能保留当前的全自动模式又能增加一个人工确认的环节对重要文件的处理会更稳妥。最后分享一个使用上的小技巧如果你的图片来源比较固定比如都是某个设备导出、都是某个系统截图建议把区域坐标和命名模板固定好之后先在二三十张样本上跑一遍检查识别准确率和命名效果确认无误之后再跑全量。批量处理工具最怕的就是跑完一两百张后才发现区域偏移所有文件名都没意义那时再想恢复原文件名就麻烦了。好在原始文件名的日志我都有记录真出问题也能通过日志反向映射回去但能避免的返工还是尽量避免。