拓冰建站拓冰建站
首页 / 资讯中心 / 正文

WPF+腾讯云OCR实现批量图片区域文字识别与自动重命名

先交代一下背景。上个月帮朋友整理一批产品标签扫描件文件夹里几百张 JPG文件名全是“IMG_20240312_113045.jpg”这种肉眼根本分不清哪张是哪个型号。当时脑子里第一个念头是有没有现成工具能自动识别图片指定区域里的文字然后直接用识别结果改文件名搜了一圈要么只支持整张图识别要么改名规则太死板要么是付费在线工具还限制张数。那干脆自己做一个小工具技术栈选 WPF 加腾讯云 OCR API整个过程就是批量遍历图片、手动框选要识别的区域、调用接口拿文字、按规则自动改名。做完之后我把这套方案的完整思路和踩坑过程整理出来供有同样需求的朋友参考。1. 需求拆解与方案选型1.1 三个核心诉求背后的真实场景先搞清楚标题里“批量图片区域识别改名”这句话到底在说什么。拆开看就是三个动作批量、区域识别、改名。批量不是处理一两张图而是几十上百张。这意味着程序必须支持文件夹遍历、自动过滤图片格式、连续处理同时要有进度提示否则跑起来像死机一样。区域识别不是整张图全部识别而是只识别图片中某一块指定区域。比如一张产品标签上有 logo、有型号、有二维码、有生产日期我们需要的可能只是型号那一行。如果整张图都识别返回几十条文字结果还要额外做过滤逻辑而且容易把干扰文字混进来。更关键的是同一批图片通常结构是固定的型号都出现在同一位置所以“区域”应该可以框选一次应用到整批图片。改名用识别出来的文字重命名文件。这里有个容易被忽略的坑——识别出来的文字不一定合法可能包含/ \ : * ? |这些 Windows 文件名非法字符也可能超长、为空、包含多余空格。命名规则还得考虑重名冲突否则直接 File.Move 会抛异常。这三个点组合在一起决定了整个工具的形态一个带图形界面用来框选区域、能跑批量任务、内置命名规则的桌面程序。1.2 为什么选 WPF 加腾讯云 OCR而不是其他搭配先说 UI 框架的选型。当时列了几个候选Python PyQtElectronWinFormsWPF。Python PyQt 其实也能做但对最终用户不友好要装 Python 环境打包成 exe 体积大还容易被杀毒误报。Electron 更重一个 Hello World 都要一两百 MB为了一个改文件名的小工具不值当。WinForms 虽然轻但界面做框选交互不如 WPF 灵活画布、绑定、样式这些能力 WPF 明显更强。WPF 是 Windows 原生框架C# 写逻辑顺手发布单文件 exe 也很简单最终选了 WPF。再说 OCR 引擎。本来想用 Tesseract毕竟开源免费但实际测下来中文精度一般尤其在复杂背景下效果很惨。又不想自己训练模型所以转向云 API。对比了腾讯、百度、阿里三家最终选腾讯云原因有三个新用户有免费额度够把工具调通官方提供 .NET SDK直接 NuGet 装包不用自己实现签名接口返回结构清晰识别结果里每行文字带置信度和坐标方便做二次过滤。2. 环境准备与腾讯云 OCR 接入2.1 开发环境与 NuGet 依赖基础环境是 Visual Studio 2022目标框架 .NET 6Windows 桌面应用选 .NET Core 版本方便后续跨平台思路不过 WPF 本身只在 Windows 上跑。创建一个 WPF 应用程序项目然后通过 NuGet 装三个包TencentCloudSDK腾讯云官方 SDK 主包里面包含 OCR 的接口定义。System.Drawing.Common用于处理图片裁剪、旋转等操作。注意 .NET 6 里 Windows 平台要用这个包得额外安装。Microsoft.Toolkit.Mvvm可选如果不想写一堆事件处理器用 MVVM 模式会舒服很多。但为了降低理解门槛下面的示例代码直接用 code-behind 写逻辑不引入额外框架。装包时有个小提示TencentCloudSDK 这个包版本更新比较频繁建议装最新的稳定版。我一开始装了老版本结果 OCR 接口里缺少部分字段排查了半天才发现是 SDK 版本太旧。2.2 开通腾讯云 OCR 服务与获取密钥打开腾讯云控制台在“访问管理 访问密钥 API 密钥管理”里创建一对 SecretId 和 SecretKey。这两串密钥要保存好代码里不要硬编码我习惯放在一个 local.settings.json 文件里并加入 .gitignore避免误传仓库。然后在“产品与服务 人工智能 文字识别”里开通通用印刷体识别服务。控制台会显示免费调用额度一般新用户赠送一个月免费额度足够测试使用。如果只是个人小批量整理文件免费额度基本上够用。2.3 在 WPF 中封装一个 OCR 调用类直接在主窗口里写调用逻辑会让代码越来越乱最好单独封装一个OcrHelper类。核心代码大致如下using TencentCloud.Common; using TencentCloud.Common.Profile; using TencentCloud.Ocr.V20181119; using TencentCloud.Ocr.V20181119.Models; public class OcrHelper { private static readonly string SecretId your-secret-id; private static readonly string SecretKey your-secret-key; public static async Taskstring RecognizeTextFromBytes(byte[] imageBytes) { Credential cred new Credential { SecretId SecretId, SecretKey SecretKey }; HttpProfile httpProfile new HttpProfile { Endpoint ocr.tencentcloudapi.com }; ClientProfile clientProfile new ClientProfile { HttpProfile httpProfile }; OcrClient client new OcrClient(cred, ap-guangzhou, clientProfile); GeneralBasicOCRRequest req new GeneralBasicOCRRequest { ImageBase64 Convert.ToBase64String(imageBytes) }; GeneralBasicOCRResponse resp await client.GeneralBasicOCR(req); if (resp.TextDetections ! null resp.TextDetections.Length 0) { // 默认取第一行识别文本也可以根据坐标、置信度做过滤 return resp.TextDetections[0].DetectedText?.Trim(); } return string.Empty; } }这里有个细节构造OcrClient时第二个参数是地域OCR 服务不需要区分地域填ap-guangzhou即可。接口选择上基础需求用GeneralBasicOCR就够了如果图片清晰度低或者文字小可以换GeneralAccurateOCR不过单价会高一些。返回结果里TextDetections是一个数组每个元素是识别出的一块文字包含DetectedText和Confidence两个常用字段。我建议不只是取第一行而是把整个数组都返回出来在 UI 上展示让用户决定用哪一行作为文件名后面会讲这个交互设计。3. 核心功能实现图片加载、区域框选与裁剪3.1 批量加载图片文件在窗口左侧放一个 ListBox通过 FolderBrowserDialog 选择一个文件夹然后用Directory.EnumerateFiles过滤出图片扩展名private void BtnLoadFolder_Click(object sender, RoutedEventArgs e) { using var dialog new System.Windows.Forms.FolderBrowserDialog(); if (dialog.ShowDialog() System.Windows.Forms.DialogResult.OK) { string folder dialog.SelectedPath; var extensions new HashSetstring { .jpg, .jpeg, .png, .bmp }; _fileList Directory.EnumerateFiles(folder) .Where(f extensions.Contains(Path.GetExtension(f).ToLowerInvariant())) .OrderBy(f f) .ToList(); ListBoxFiles.ItemsSource _fileList.Select(f Path.GetFileNameWithoutExtension(f)); if (_fileList.Count 0) { ShowImage(0); } } }注意这里用EnumerateFiles而不是GetFiles前者是流式返回处理几千个文件时内存占用更小。用户选中的文件夹里可能混着子目录我暂时没有递归遍历有需要可以加SearchOption.AllDirectories。另外图片格式虽然标题写的是 JPG但实际场景里往往还有 PNG、BMP所以扩展名过滤尽量放宽。3.2 图片预览与区域框选交互中间部分是一块大区域用来显示当前图片和画矩形选区。我用一个Grid底层放Image上层放一个透明的Canvas鼠标事件都挂在 Canvas 上Grid Image x:NameImgPreview StretchUniform / Canvas x:NameCanvasOverlay BackgroundTransparent MouseLeftButtonDownCanvasOverlay_MouseLeftButtonDown MouseMoveCanvasOverlay_MouseMove MouseLeftButtonUpCanvasOverlay_MouseLeftButtonUp / /Grid框选逻辑按下、移动、抬起三步。鼠标按下时记录起点移动时动态更新矩形位置和大小抬起时确定选区并保存为Rect对象。为了直观我在 Canvas 上画一个紫色半透明的Rectangleprivate Point _startPoint; private Rectangle? _currentRect; private void CanvasOverlay_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { _startPoint e.GetPosition(CanvasOverlay); _currentRect new Rectangle { Fill new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)), Stroke Brushes.Red, StrokeThickness 1.5 }; Canvas.SetLeft(_currentRect, _startPoint.X); Canvas.SetTop(_currentRect, _startPoint.Y); CanvasOverlay.Children.Add(_currentRect); } private void CanvasOverlay_MouseMove(object sender, MouseEventArgs e) { if (_currentRect null) return; var pos e.GetPosition(CanvasOverlay); double x Math.Min(_startPoint.X, pos.X); double y Math.Min(_startPoint.Y, pos.Y); double w Math.Abs(pos.X - _startPoint.X); double h Math.Abs(pos.Y - _startPoint.Y); _currentRect.Width w; _currentRect.Height h; Canvas.SetLeft(_currentRect, x); Canvas.SetTop(_currentRect, y); }这里有一个常见误区Image控件的显示区域和实际位图尺寸通常不一致。当StretchUniform时图片会在控件里等比例缩放四周可能留白鼠标在Canvas上的坐标不能直接当原图像素坐标用必须换算。换算公式是原图X 选区左边距 - 图片显示区域左边距) × (原图宽度 / 图片显示区域宽度) 原图Y 选区上边距 - 图片显示区域上边距) × (原图高度 / 图片显示区域高度)我在代码里封装了一个转换方法避免每次框选都用手算private Rect ConvertDisplayRectToOriginalRect(Rect displayRect) { double displayWidth ImgPreview.ActualWidth; double displayHeight ImgPreview.ActualHeight; double originalWidth _currentBitmap.PixelWidth; double originalHeight _currentBitmap.PixelHeight; // 计算图片在控件内实际显示的位置Uniform 模式下可能留有空白 double ratio Math.Min(displayWidth / originalWidth, displayHeight / originalHeight); double shownWidth originalWidth * ratio; double shownHeight originalHeight * ratio; double offsetX (displayWidth - shownWidth) / 2; double offsetY (displayHeight - shownHeight) / 2; double x (displayRect.X - offsetX) / shownWidth * originalWidth; double y (displayRect.Y - offsetY) / shownHeight * originalHeight; double width displayRect.Width / shownWidth * originalWidth; double height displayRect.Height / shownHeight * originalHeight; // 边界裁剪防止越界 x Math.Max(0, x); y Math.Max(0, y); width Math.Min(originalWidth - x, width); height Math.Min(originalHeight - y, height); return new Rect(x, y, width, height); }如果没用Uniform而是直接拉伸填满控件上述换算公式就变成(displayRect.X / displayWidth) * originalWidth简单一些。但实际图片比例未必和控件一致所以Uniform更合理。3.3 裁剪选区并生成 OCR 图片字节拿到原图坐标后使用CroppedBitmap裁剪并转成字节数组。这里也要小心一点CroppedBitmap需要源是BitmapSource大多数情况下我们用BitmapImage加载图片可以直接传给CroppedBitmap。但为了后续能处理 EXIF 旋转我统一先转成Bitmap再转成BitmapSource展示。裁剪逻辑private byte[] CropRegionToBytes(BitmapSource source, Int32Rect region) { var cropped new CroppedBitmap(source, region); var encoder new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(cropped)); using var ms new MemoryStream(); encoder.Save(ms); return ms.ToArray(); }注意输出格式我用的是 PNG 而不是 JPG因为 PNG 无损OCR 识别精度会好一点。虽然文件体积大一些但图片区域通常很小影响不大。4. 批量识别、命名规则与冲突处理4.1 批量识别区域文字并预览结果区域框选确定后点击“开始识别”按钮程序就遍历_fileList里所有图片对每一张裁剪对应区域调用OcrHelper.RecognizeTextFromBytes把识别出来的文字和原文件名一起显示在右侧列表里。具体执行时我加了两个小设计一个是用进度条展示当前进度另一个是识别结果先不直接写回文件名而是全部展示出来等用户确认后再执行重命名。原因是 OCR 识别总有可能出错如果直接写回可能把好文件改成坏名字等发现时原文件名已经丢了。这个“二次确认”步骤虽然多了一步操作但安全系数高很多。执行代码大致如下private async void BtnRecognizeAll_Click(object sender, RoutedEventArgs e) { _recognizeResults new List(string OriginalFile, string RecognizedText)(); ProgressBar.Visibility Visibility.Visible; ProgressBar.Maximum _fileList.Count; for (int i 0; i _fileList.Count; i) { string file _fileList[i]; string fileName Path.GetFileName(file); string recognizedText string.Empty; try { var bitmap new BitmapImage(new Uri(file)); // 这里需要处理 EXIF 旋转后面讲 var region ConvertDisplayRectToOriginalRect(_selectedRegion); byte[] cropBytes CropRegionToBytes(bitmap, new Int32Rect( (int)region.X, (int)region.Y, (int)region.Width, (int)region.Height)); recognizedText await OcrHelper.RecognizeTextFromBytes(cropBytes); } catch (Exception ex) { MessageBox.Show($图片 {fileName} 识别失败{ex.Message}); } _recognizeResults.Add((file, recognizedText)); ListBoxResults.Items.Add(${fileName} - {recognizedText}); ProgressBar.Value i 1; } ProgressBar.Visibility Visibility.Collapsed; }这里最大的问题是没有异常重试机制。实际调用云接口时偶尔网络抖动或者 QPS 超限就会失败一旦失败当前图片直接返回空字符串整个流程继续跑。我建议加上一个for重试次数比如每张图最多重试 3 次每次间隔 300 毫秒。如果重试 3 次还是失败再记录日志并继续下一张。4.2 组装合法文件名过滤非法字符与长度限制识别结果拿来直接用会出事。Windows 文件名不允许包含\ / : * ? |这 9 个字符识别出来的文本里如果带了反斜杠或者冒号比如货号“AB:01”重命名时File.Move会直接抛异常。所以要先清洗private static readonly char[] InvalidFileNameChars Path.GetInvalidFileNameChars(); private static string SanitizeFileName(string input, int maxLength 80) { if (string.IsNullOrWhiteSpace(input)) return string.Empty; string cleaned new string(input.Where(ch !InvalidFileNameChars.Contains(ch)).ToArray()); cleaned cleaned.Trim(); cleaned cleaned.Replace( , _); if (cleaned.Length maxLength) cleaned cleaned.Substring(0, maxLength); return cleaned; }Path.GetInvalidFileNameChars()已经包含了 Windows 和 .NET 层面的所有非法字符直接用最省事。识别文字里的换行符也要处理掉我通常在前面替换空格时把\r和\n一起替换成空字符串。为了保留原始文件信息命名规则不要只放识别文字我使用{识别文字}_{原文件名前若干位}这种组合方式。比如原文件名IMG_20240312_113045.jpg识别文字是型号A100新文件名叫型号A100_IMG_20240312_1130.jpg。这样既能快速辨识又不容易因为重名冲突。4.3 重名冲突与目标文件存在检测批量重命名时最容易踩的坑是重名。两张图可能识别出同一个文字比如两张发票都写着“快递单号”如果都改成快递单号_xxx.jpg第二张就会因为目标文件已存在而失败。所以重命名逻辑必须检查目标文件是否存在存在的话自动追加序号private static string GetUniqueFileName(string directory, string baseName, string extension) { string candidate Path.Combine(directory, baseName extension); int index 1; while (File.Exists(candidate)) { candidate Path.Combine(directory, ${baseName}_{index}{extension}); index; } return candidate; }还有一个更深的坑如果原文件在D:\tags目录目标文件名也在同一个目录那么D:\tags\A.jpg改成D:\tags\A_1.jpg时必须确保新名字不等于当前正在处理的某个其他文件的原始名字否则可能出现 A.jpg 先改名然后处理 B.jpg 时新名字又叫 A.jpg导致 A 和 B 的文件内容混合。规避方案是先把所有目标文件名全部计算出来再统一在内存里检查是否有重复目标名如果有在批处理开始前就提示用户哪些文件会冲突。我实测了一轮最常见的冲突是识别文字全部为空。这时候如果还按_原文件名的规则生成那所有空结果的文件名都会被改成_IMG_xxx.jpg出现大量重复。我的方案是识别文本为空时保持原名不动并给该行标记一个颜色让用户手动处理。4.4 进度展示与取消机制批量处理如果卡在一张超大图上整个程序就像死机。我加了一个CancellationTokenSource配合按钮“取消”使用。每次调用RecognizeTextFromBytes时传入CancellationTokenSDK 内部支持取消请求。界面上的进度条和 TextBlock 显示“3/78”让用户心里有数。这里说明一下为什么这里要专门讲进度和取消。做批量工具最烦人的就是任务跑到一半想停停不下来以及进度条不动让人心慌。我一开始没加取消跑 100 张图时误点了缩小窗口程序直接未响应体验非常糟糕。后来加了asyncCancellationToken整个流程顺畅多了。5. 处理图片方向与 OCR 精度优化5.1 EXIF 旋转问题手机或相机拍出来的 JPG 图片可能带有一个 EXIF 方向信息Orientation但图片本身的像素数据并没有被旋转。Windows 照片查看器和 WPF 会读取 EXIF 自动旋转但底层 OCR 接口拿到的字节流没有这个信息所以可能出现“预览图是正的识别结果却是竖着排”的情况。解决办法是在裁剪前手动旋转public static BitmapFrame NormalizeOrientation(BitmapSource source, uint orientation) { BitmapFrame result; switch (orientation) { case 2: // 水平翻转 result new TransformedBitmap(source, new ScaleTransform(-1, 1)); break; case 3: // 旋转180 result new TransformedBitmap(source, new ScaleTransform(-1, -1)); break; case 4: // 垂直翻转 result new TransformedBitmap(source, new ScaleTransform(1, -1)); break; case 5: // 水平翻转后旋转90具体取决于实现 result new TransformedBitmap(source, new TransformGroup()); // 上面例子只是示意实际请根据 EXIF 标准实现 break; case 6: // 旋转90 result new TransformedBitmap(source, new RotateTransform(90)); break; case 7: result new TransformedBitmap(source, new TransformGroup()); break; case 8: // 旋转270 result new TransformedBitmap(source, new RotateTransform(270)); break; default: result (BitmapFrame)source; break; } return BitmapFrame.Create(result); }注意 EXIF 方向字段的值与旋转角度对应关系容易记混。我建议用现成库比如MetadataExtractor读取Orientation字段然后调用上面的转换方法。这个处理放在加载图片展示阶段保证用户框选区域时看到的是正图裁剪出来发送给 OCR 的也是正图。5.2 框选区域的适度放大有些图片上的文字区域像素不够大直接裁剪识别率不高。一个有效技巧是裁剪时在外围扩充一圈边距把部分背景也带进去比如int padding 10; int x Math.Max(0, (int)region.X - padding); int y Math.Max(0, (int)region.Y - padding); int width Math.Min(source.PixelWidth - x, (int)region.Width padding * 2); int height Math.Min(source.PixelHeight - y, (int)region.Height padding * 2); var cropRect new Int32Rect(x, y, width, height);这样做的原理是 OCR 引擎对周围有少量背景的文字识别效果通常比纯文字区域更好因为上下文信息更多。但 padding 不能太大否则会混入无关文字导致返回结果变多。实测 10 到 20 像素比较合适。5.3 置信度过滤OCR 接口返回的TextDetections每条记录都有Confidence字段取值范围 0 到 100。如果某条文字识别置信度很低比如 60 以下说明结果不可靠应该过滤掉。我的策略是只取置信度最高的那条作为最终命名文本并在 UI 上把置信度显示出来resp.TextDetections .OrderByDescending(t t.Confidence) .FirstOrDefault(t !string.IsNullOrWhiteSpace(t.DetectedText))这个方法在实践中有个附带好处如果一张图里同时有型号和二维码识别内容置信度最高的往往是人类视觉上最显眼的那行正好符合“提取这个区域最核心文字”的需求。6. 常见问题与排查技巧实录6.1 API 签名错误或请求超时新手最容易犯的错是把 SecretId 填到 SecretKey 位置或者 SecretKey 末尾多了空格。腾讯云的签名算法校验很严格多一个空格都会抛 AuthFailure.SignatureFailure 错误。排查思路先检查密钥复制是否完整再确认代码里没有对密钥做 Trim最后确认地域参数是否正确OCR 服务固定用ap-guangzhou就行。请求超时方面如果图片体积太大Base64 字符串很长会拖慢接口响应。解决办法是在调用 SDK 时设置更长的超时时间比如把HttpProfile的Timeout设为 30 秒。另外如果单张图片超过 7MB需要先用压缩算法降采样再调用接口。我一般把裁剪出来的区域图片做一个等比缩放让最长边不超过 2000 像素识别精度基本不受影响。6.2 批量任务中途失败如何排查大批量处理时如果第 30 张图突然失败程序应该在日志文件中记录是哪个文件名、失败原因。我在代码里加了一个简单的日志类把每条识别记录写入 txt原文件名、识别文本、耗时、是否成功。这样跑完一轮后可以快速定位失败图片不需要在界面上翻找。排错日志的格式类似2024-03-12 10:15:33 | IMG_2045.jpg | 识别成功 | 型号A100 | 耗时 1.2s 2024-03-12 10:15:35 | IMG_2046.jpg | 识别失败 | 请求超时 | 耗时 30s如果大量图片连续失败先检查网络和 API 配额如果只有特定图片失败大概率是图片本身有问题比如损坏或者区域太小。6.3 区域坐标在缩放图片时的万一致命问题前面讲坐标换算时我默认图片在控件里是Uniform缩放。但如果用户手动拖动窗口大小ImgPreview.ActualWidth会变换算关系也变了。所以框选结束后不要再改变窗口大小或者把选区保存成相对比例0 到 1 的浮点数批量识别时再根据原始图片尺寸换算回绝对坐标。相对比例是更稳妥的方式public record RegionNormalized(double X, double Y, double Width, double Height); private RegionNormalized DisplayRectToNormalized(Rect displayRect) { double displayWidth ImgPreview.ActualWidth; double displayHeight ImgPreview.ActualHeight; return new RegionNormalized( displayRect.X / displayWidth, displayRect.Y / displayHeight, displayRect.Width / displayWidth, displayRect.Height / displayHeight); }批量处理时再乘回原始宽高。这样即使换了不同分辨率的图片框选区域也能保持相对位置。6.4 误把二维码识别结果当文件名有些标签上二维码紧挨着型号文字OCR 可能把二维码旁边的小字也识别出来并排在第一位。遇到这种情况可以在框选区域时尽量缩小范围只框型号那段文字。如果已经跑完一轮发现结果不对就在右侧“识别结果”列表里手动编辑文字再执行重命名我的工具里加入了 ListBox 项双击编辑功能不重新调接口直接改文本。6.5 免费额度到底够不够个人整理几百张图用GeneralBasicOCR免费额度是够的。但如果照片结构复杂需要调高精度版本费用会上升。我实测高精度版本对模糊图片有帮助但对清晰的打印体文字基础版和高精度版识别结果差别不大不建议为了省事直接上高精度版先基础版跑一轮发现问题再单独重处理。7. 让工具更顺手后续可扩展的细节分享一下我在这套工具上后续加的小功能算是对“批量图片区域识别改名”这个需求的延伸理解。首先是一个命名模板功能。不同用户的命名习惯差别很大有人喜欢识别文字_原文件名有人喜欢原文件名_识别文字还有人希望加日期前缀。我加了一个简单的模板输入框支持{text}{source}{date}占位符运行时替换成实际内容。比如模板填{date}_{text}_{source}生成的文件名就是20240312_型号A100_IMG_1234.jpg。其次是支持多区域识别。有些标签需要同时提取两个区域比如型号和批次号然后拼成型号A100_批次B作为文件名。做法是维护一个ListRect区域列表每个区域可单独命名标签批量识别时对每个区域分别调用 OCR最后合并字符串。代码逻辑上并不复杂就是多一层循环。最后是导出 CSV。整理完一批文件后把原文件名、新文件名、识别文本、置信度、处理状态统一导出到 CSV方便后期做台账。这个功能其实很简单就是写一个StreamWriter循环输出但对整理发票、合同扫描件这类场景很有价值。如果你有大量图片需要定期整理建议把识别结果缓存到本地数据库或 JSON 文件里避免每次重复调用 OCR。同一张图片如果只改了命名规则不需要重新识别直接读缓存即可。我最初没意识到这点重复跑了三轮免费额度差点用完后来改成缓存方案速度立刻上来了。写到最后这个项目从头到尾做下来我最大的感受是“自动改文件名”这个需求本身不难难的是处理各种边界情况——图片方向、坐标系换算、文件名非法字符、重名冲突、API 调用失败。每解决一个边界问题工具就离“真正给人用”更近一步。如果你也在找类似的批量图片改名工具照着这套方案做基本能覆盖 90% 的场景。上线使用之前千万别忘了在测试文件夹里放几张不同方向的图片把最常见的坑提前踩一遍。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门