應用場景
在企業文檔管理、數字圖書館、電商商品管理等場景中,經常需要處理大量圖片中的文字信息。例如:
- 電商平臺需要將商品圖片中的型號、規格等信息提取出來作為文件名
- 圖書館需要將掃描的圖書頁面識別為文字并整理歸檔
- 企業需要將紙質文檔電子化并按內容分類
使用 WPF 和京東 OCR 接口可以開發一個高效的桌面應用程序JD圖片文字識別與重命名工具,實現圖片文字識別和批量重命名功能。
界面設計
一個功能完善的圖片文字識別與重命名工具界面應包含以下元素:
- 文件選擇區域:支持拖放或點擊選擇圖片文件 / 文件夾
- 處理隊列顯示:展示待處理和已處理的圖片列表
- OCR 配置區域:設置識別語言、API 參數等
- 重命名規則設置:自定義文件名格式和替換規則
- 處理進度顯示:實時展示處理進度和狀態
- 日志輸出區域:記錄處理過程和錯誤信息
下面是一個基于 WPF 的實現方案:
image-ocr-rename-wpf基于WPF和京東OCR的圖片文字識別與重命名工具
<Window x:Class="ImageOcrRenameTool.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="JD圖片文字識別與重命名工具" Height="700" Width="900"WindowStartupLocation="CenterScreen"><Grid><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="*"/><RowDefinition Height="Auto"/></Grid.RowDefinitions><!-- 標題區域 --><Border Background="#3498db" Padding="10"><TextBlock Text="JD圖片文字識別與重命名工具" FontSize="20" FontWeight="Bold" Foreground="White"/></Border><!-- 文件選擇區域 --><GroupBox Header="文件選擇" Grid.Row="1" Margin="10" Padding="10"><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="Auto"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions><TextBox x:Name="txtFilePath" TextWrapping="Wrap" Margin="0,0,5,0" IsReadOnly="True"/><Button x:Name="btnSelectFile" Content="選擇文件" Margin="5,0" Padding="10,5" Click="BtnSelectFile_Click"/><Button x:Name="btnSelectFolder" Content="選擇文件夾" Margin="5,0" Padding="10,5" Click="BtnSelectFolder_Click" Grid.Column="2"/></Grid></GroupBox><!-- OCR配置區域 --><GroupBox Header="JD_OCR配置" Grid.Row="2" Margin="10" Padding="10"><Grid><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"/><ColumnDefinition Width="*"/><ColumnDefinition Width="Auto"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions><TextBlock Text="AppKey:" VerticalAlignment="Center" Margin="0,5"/><TextBox x:Name="txtAppKey" TextWrapping="Wrap" Margin="5,5" Text="{Binding AppKey, Mode=TwoWay}"/><TextBlock Text="AppSecret:" VerticalAlignment="Center" Margin="0,5" Grid.Column="2"/><TextBox x:Name="txtAppSecret" TextWrapping="Wrap" Margin="5,5" Text="{Binding AppSecret, Mode=TwoWay}"Grid.Column="3"/><TextBlock Text="識別語言:" VerticalAlignment="Center" Margin="0,5" Grid.Row="1"/><ComboBox x:Name="cmbLanguage" Margin="5,5" Grid.Row="1" Grid.Column="1"ItemsSource="{Binding AvailableLanguages}" SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}"/><TextBlock Text="重命名規則:" VerticalAlignment="Center" Margin="0,5" Grid.Row="2"/><TextBox x:Name="txtRenameRule" TextWrapping="Wrap" Margin="5,5" Grid.Row="2" Grid.Column="1"Text="{Binding RenameRule, Mode=TwoWay}" ToolTip="使用{text}表示識別的文字,{index}表示序號,{original}表示原文件名"/></Grid></GroupBox><!-- 處理區域 --><GroupBox Header="處理隊列" Grid.Row="3" Margin="10" Padding="10"><Grid><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions><StackPanel Orientation="Horizontal" Margin="0,0,0,5"><Button x:Name="btnStartProcess" Content="開始處理" Padding="10,5" Click="BtnStartProcess_Click" IsEnabled="{Binding CanStartProcessing}"/><Button x:Name="btnStopProcess" Content="停止處理" Padding="10,5" Margin="5,0,0,0" Click="BtnStopProcess_Click" IsEnabled="{Binding IsProcessing}"/><ProgressBar x:Name="progressBar" Width="200" Height="20" Margin="10,0,0,0" Value="{Binding ProgressValue}" Maximum="{Binding ProgressMax}"/><TextBlock x:Name="txtProgress" Text="{Binding ProgressText}" VerticalAlignment="Center" Margin="5,0,0,0"/></StackPanel><DataGrid x:Name="dataGrid" AutoGenerateColumns="False" ItemsSource="{Binding ImageItems}" CanUserAddRows="False" CanUserDeleteRows="False" SelectionMode="Single"HorizontalGridLinesBrush="LightGray" VerticalGridLinesBrush="LightGray"><DataGrid.Columns><DataGridTextColumn Header="序號" Binding="{Binding Index}" Width="50"/><DataGridTextColumn Header="原文件名" Binding="{Binding OriginalFileName}" Width="*"/><DataGridTextColumn Header="識別文字" Binding="{Binding RecognizedText}" Width="*"/><DataGridTextColumn Header="新文件名" Binding="{Binding NewFileName}" Width="*"/><DataGridTextColumn Header="狀態" Binding="{Binding Status}" Width="100"/></DataGrid.Columns></DataGrid></Grid></GroupBox><!-- 日志區域 --><GroupBox Header="處理日志" Grid.Row="4" Margin="10" Padding="10"><TextBox x:Name="txtLog" TextWrapping="Wrap" AcceptsReturn="True" IsReadOnly="True" VerticalScrollBarVisibility="Auto" Height="100"/></GroupBox></Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;namespace ImageOcrRenameTool
{public class MainViewModel : INotifyPropertyChanged{private readonly JdOcrService _ocrService;private string _appKey;private string _appSecret;private string _selectedLanguage = "CHN_ENG";private string _renameRule = "{text}_{index}";private bool _isProcessing;private double _progressValue;private double _progressMax;private string _progressText = "就緒";public event PropertyChangedEventHandler PropertyChanged;public ObservableCollection<ImageItem> ImageItems { get; } = new ObservableCollection<ImageItem>();public List<string> AvailableLanguages { get; } = new List<string>{"CHN_ENG", "ENG", "JAP", "KOR", "FRE", "SPA", "POR", "GER", "ITA", "RUS"};public string AppKey{get => _appKey;set{_appKey = value;OnPropertyChanged();}}public string AppSecret{get => _appSecret;set{_appSecret = value;OnPropertyChanged();}}public string SelectedLanguage{get => _selectedLanguage;set{_selectedLanguage = value;OnPropertyChanged();}}public string RenameRule{get => _renameRule;set{_renameRule = value;OnPropertyChanged();}}public bool IsProcessing{get => _isProcessing;set{_isProcessing = value;OnPropertyChanged();OnPropertyChanged(nameof(CanStartProcessing));}}public bool CanStartProcessing => !IsProcessing && ImageItems.Any();public double ProgressValue{get => _progressValue;set{_progressValue = value;OnPropertyChanged();}}public double ProgressMax{get => _progressMax;set{_progressMax = value;OnPropertyChanged();}}public string ProgressText{get => _progressText;set{_progressText = value;OnPropertyChanged();}}public MainViewModel(){_ocrService = new JdOcrService();}public void AddImageFiles(string[] filePaths){if (filePaths == null || !filePaths.Any())return;var supportedExtensions = new[] { ".jpg", ".jpeg", ".png", ".bmp", ".gif" };foreach (var filePath in filePaths){if (File.Exists(filePath) && supportedExtensions.Contains(Path.GetExtension(filePath).ToLower())){var index = ImageItems.Count + 1;ImageItems.Add(new ImageItem{Index = index,FilePath = filePath,OriginalFileName = Path.GetFileName(filePath)});}}ProgressMax = ImageItems.Count;OnPropertyChanged(nameof(CanStartProcessing));}public void AddImageFolder(string folderPath){if (string.IsNullOrEmpty(folderPath) || !Directory.Exists(folderPath))return;var supportedExtensions = new[] { ".jpg", ".jpeg", ".png", ".bmp", ".gif" };var filePaths = Directory.GetFiles(folderPath).Where(f => supportedExtensions.Contains(Path.GetExtension(f).ToLower())).ToList();AddImageFiles(filePaths.ToArray());}public async Task ProcessImagesAsync(CancellationToken cancellationToken){IsProcessing = true;ProgressValue = 0;try{if (string.IsNullOrEmpty(AppKey) || string.IsNullOrEmpty(AppSecret)){throw new InvalidOperationException("請先設置京東OCR的AppKey和AppSecret");}_ocrService.Initialize(AppKey, AppSecret);var processedCount = 0;foreach (var item in ImageItems){if (cancellationToken.IsCancellationRequested)throw new OperationCanceledException();try{item.Status = "處理中";ProgressText = $"正在處理: {item.OriginalFileName}";// 執行OCR識別var ocrResult = await _ocrService.RecognizeImageAsync(item.FilePath, SelectedLanguage, cancellationToken);item.RecognizedText = ocrResult;// 生成新文件名item.NewFileName = GenerateNewFileName(item, processedCount + 1);// 重命名文件RenameFile(item);item.Status = "已完成";processedCount++;}catch (OperationCanceledException){item.Status = "已取消";throw;}catch (Exception ex){item.Status = "失敗";item.ErrorMessage = ex.Message;}finally{ProgressValue++;ProgressText = $"已完成: {processedCount}/{ImageItems.Count}";}}ProgressText = $"處理完成! 共處理 {processedCount} 個文件";}finally{IsProcessing = false;}}private string GenerateNewFileName(ImageItem item, int index){if (string.IsNullOrEmpty(RenameRule))return item.OriginalFileName;var originalName = Path.GetFileNameWithoutExtension(item.OriginalFileName);var extension = Path.GetExtension(item.OriginalFileName);// 替換模板變量var newName = RenameRule.Replace("{text}", SanitizeFileName(item.RecognizedText)).Replace("{index}", index.ToString()).Replace("{original}", originalName);// 確保文件名不超過最大長度if (newName.Length > 240) // NTFS文件名最大長度為255字符,留出空間給擴展名newName = newName.Substring(0, 240);return newName + extension;}private void RenameFile(ImageItem item){if (string.IsNullOrEmpty(item.NewFileName) || item.NewFileName == item.OriginalFileName)return;var directory = Path.GetDirectoryName(item.FilePath);if (string.IsNullOrEmpty(directory))return;var newFilePath = Path.Combine(directory, item.NewFileName);try{// 如果新文件名已存在,添加序號if (File.Exists(newFilePath)){var baseName = Path.GetFileNameWithoutExtension(item.NewFileName);var extension = Path.GetExtension(item.NewFileName);var counter = 1;do{newFilePath = Path.Combine(directory, $"{baseName}_{counter}{extension}");counter++;} while (File.Exists(newFilePath));item.NewFileName = Path.GetFileName(newFilePath);}File.Move(item.FilePath, newFilePath);item.FilePath = newFilePath;}catch (Exception ex){throw new Exception($"重命名文件失敗: {ex.Message}");}}private string SanitizeFileName(string fileName){if (string.IsNullOrEmpty(fileName))return "未識別文字";// 移除或替換文件路徑中的非法字符var invalidChars = Path.GetInvalidFileNameChars();var sanitized = new string(fileName.Select(c => invalidChars.Contains(c) ? '_' : c).ToArray());// 替換連續的空格和下劃線sanitized = Regex.Replace(sanitized, @"[_\s]+", "_");// 去除首尾空格和下劃線return sanitized.Trim('_', ' ');}protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}
}
詳細代碼步驟
這個應用程序主要包含以下幾個部分:
-
界面設計:使用 WPF 創建了一個直觀的用戶界面,包括文件選擇、OCR 配置、處理隊列和日志顯示等區域。
-
數據模型:
ImageItem
類:表示待處理的圖片項,包含原始文件名、識別文字、新文件名和處理狀態等屬性JdOcrResponse
系列類:用于解析京東OCR接口的返回數據
-
視圖模型:
MainViewModel
類實現了主要的業務邏輯:- 管理圖片文件列表
- 與京東 OCR 服務交互
- 生成新文件名并執行重命名操作
- 管理處理進度和狀態
-
OCR 服務:
JdOcrService
類封裝了與京東OCR API的通信:- 初始化 API 憑證
- 將圖片轉換為 Base64 格式
- 發送請求并解析響應
-
文件名處理:實現了安全的文件名生成和清理邏輯,確保生成的文件名符合文件系統規則。
總結優化
這個應用程序提供了一個功能完整的圖片文字識別與重命名解決方案,但還有一些可以優化的地方:
-
性能優化:
- 可以實現多線程處理,提高大量圖片的處理速度
- 添加圖片緩存機制,避免重復處理相同圖片
- 實現斷點續傳功能,支持中斷后繼續處理
-
錯誤處理:
- 增強對網絡異常的處理,實現自動重試機制
- 提供更詳細的錯誤日志和調試信息
- 添加文件沖突解決策略選項
-
擴展功能:
- 支持更多 OCR 服務提供商,實現服務切換功能
- 添加圖片預處理功能,如旋轉、裁剪、增強對比度等
- 支持導出識別結果到文本文件或 Excel 表格
這個應用程序可以根據實際需求進一步定制和擴展,成為一個強大的圖片文字處理工具。