应用场景
在企业文档管理、数字图书馆、电商商品管理等场景中,经常需要处理大量图片中的文字信息。例如:
- 电商平台需要将商品图片中的型号、规格等信息提取出来作为文件名
- 图书馆需要将扫描的图书页面识别为文字并整理归档
- 企业需要将纸质文档电子化并按内容分类
使用 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 表格
这个应用程序可以根据实际需求进一步定制和扩展,成为一个强大的图片文字处理工具。