应用场景
在日常工作和生活中,我们经常会遇到需要对大量图片进行重命名的情况。例如,设计师可能需要根据图片内容为设计素材命名,文档管理人员可能需要根据扫描文档中的文字对图片进行分类命名。传统的手动重命名方式效率低下且容易出错,因此开发一款能够自动识别图片中的文字并根据文字内容对图片进行重命名的工具具有很高的实用价值。

界面设计

我们可以设计一个简洁易用的 WPF 界面,主要包含以下元素:
- 顶部:应用程序标题和版本信息
- 中部:
- 左侧:文件选择区域,包含文件夹选择按钮和已选择文件列表
- 右侧:预览区域,显示当前选中的图片和识别结果
- 底部:操作按钮区域,包含开始处理、取消和设置按钮
- 状态栏:显示当前处理进度和状态信息
详细代码步骤
以下是实现咕嘎批量OCR识别图片PDF多区域内容重命名导出表格系统这个功能的详细代码步骤:
- 创建 WPF 应用程序项目
- 设计 XAML 界面
- 实现JD OCR图片识别功能
- 实现文件处理功能
- 实现界面交互逻辑
下面是完整的代码实现:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Win32;
using Newtonsoft.Json;
using RestSharp;
namespace ImageOcrRenameTool
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
// 存储选择的图片文件路径
private List<string> _selectedFiles = new List<string>();
// 当前选中的图片索引
private int _currentIndex = 0;
// 京东OCR配置信息
private OcrConfig _ocrConfig = new OcrConfig();
// 处理进度
private int _processedCount = 0;
private int _totalCount = 0;
public MainWindow()
{
InitializeComponent();
InitializeConfig();
}
// 初始化配置
private void InitializeConfig()
{
try
{
if (File.Exists("config.json"))
{
string json = File.ReadAllText("config.json");
_ocrConfig = JsonConvert.DeserializeObject<OcrConfig>(json);
}
}
catch (Exception ex)
{
MessageBox.Show($"加载配置文件失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
// 保存配置
private void SaveConfig()
{
try
{
string json = JsonConvert.SerializeObject(_ocrConfig);
File.WriteAllText("config.json", json);
}
catch (Exception ex)
{
MessageBox.Show($"保存配置文件失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
// 选择文件夹按钮点击事件
private void BtnSelectFolder_Click(object sender, RoutedEventArgs e)
{
using (var dialog = new System.Windows.Forms.FolderBrowserDialog())
{
System.Windows.Forms.DialogResult result = dialog.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK)
{
string folderPath = dialog.SelectedPath;
LoadImages(folderPath);
}
}
}
// 加载文件夹中的图片
private void LoadImages(string folderPath)
{
try
{
// 清空现有文件列表
_selectedFiles.Clear();
lstFiles.Items.Clear();
// 获取文件夹中所有支持的图片文件
string[] imageExtensions = { ".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff" };
var files = Directory.GetFiles(folderPath)
.Where(f => imageExtensions.Contains(Path.GetExtension(f).ToLower()));
foreach (var file in files)
{
_selectedFiles.Add(file);
lstFiles.Items.Add(Path.GetFileName(file));
}
if (_selectedFiles.Count > 0)
{
_currentIndex = 0;
LoadImage(_selectedFiles[_currentIndex]);
UpdateStatus($"已加载 {_selectedFiles.Count} 张图片");
}
else
{
UpdateStatus("未找到图片文件");
}
}
catch (Exception ex)
{
MessageBox.Show($"加载图片失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
// 加载单张图片
private void LoadImage(string filePath)
{
try
{
// 显示图片
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(filePath);
bitmap.EndInit();
imgPreview.Source = bitmap;
// 显示文件名
txtFileName.Text = Path.GetFileName(filePath);
// 更新文件索引信息
lblFileIndex.Text = $"图片 {_currentIndex + 1} / {_selectedFiles.Count}";
}
catch (Exception ex)
{
MessageBox.Show($"加载图片失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
// 文件列表选择变更事件
private void LstFiles_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (lstFiles.SelectedIndex >= 0)
{
_currentIndex = lstFiles.SelectedIndex;
LoadImage(_selectedFiles[_currentIndex]);
}
}
// 上一张图片按钮点击事件
private void BtnPrev_Click(object sender, RoutedEventArgs e)
{
if (_selectedFiles.Count > 0 && _currentIndex > 0)
{
_currentIndex--;
lstFiles.SelectedIndex = _currentIndex;
}
}
// 下一张图片按钮点击事件
private void BtnNext_Click(object sender, RoutedEventArgs e)
{
if (_selectedFiles.Count > 0 && _currentIndex < _selectedFiles.Count - 1)
{
_currentIndex++;
lstFiles.SelectedIndex = _currentIndex;
}
}
// 开始处理按钮点击事件
private async void BtnProcess_Click(object sender, RoutedEventArgs e)
{
if (_selectedFiles.Count == 0)
{
MessageBox.Show("请先选择图片文件夹", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
if (string.IsNullOrEmpty(_ocrConfig.AppKey) || string.IsNullOrEmpty(_ocrConfig.AppSecret))
{
MessageBox.Show("请先配置京东OCR的AppKey和AppSecret", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
ShowSettingsDialog();
return;
}
// 禁用操作按钮
btnSelectFolder.IsEnabled = false;
btnProcess.IsEnabled = false;
btnSettings.IsEnabled = false;
btnPrev.IsEnabled = false;
btnNext.IsEnabled = false;
// 重置进度
_processedCount = 0;
_totalCount = _selectedFiles.Count;
progressBar.Value = 0;
lblProgress.Text = "0%";
// 异步处理图片
await ProcessImagesAsync();
// 恢复操作按钮
btnSelectFolder.IsEnabled = true;
btnProcess.IsEnabled = true;
btnSettings.IsEnabled = true;
btnPrev.IsEnabled = true;
btnNext.IsEnabled = true;
}
// 异步处理图片
private async Task ProcessImagesAsync()
{
try
{
UpdateStatus("开始处理图片...");
for (int i = 0; i < _selectedFiles.Count; i++)
{
string filePath = _selectedFiles[i];
_currentIndex = i;
// 在UI线程更新文件显示
Dispatcher.Invoke(() =>
{
lstFiles.SelectedIndex = i;
UpdateStatus($"正在处理: {Path.GetFileName(filePath)}");
});
// 执行OCR识别
string ocrResult = await PerformOcrAsync(filePath);
// 在UI线程更新识别结果
Dispatcher.Invoke(() =>
{
txtOcrResult.Text = ocrResult;
});
// 重命名文件
if (!string.IsNullOrEmpty(ocrResult))
{
string newFileName = GenerateNewFileName(ocrResult, filePath);
await RenameFileAsync(filePath, newFileName);
}
// 更新进度
_processedCount++;
Dispatcher.Invoke(() =>
{
double progress = (double)_processedCount / _totalCount * 100;
progressBar.Value = progress;
lblProgress.Text = $"{progress:F0}%";
});
}
UpdateStatus($"处理完成! 共处理 {_processedCount} 张图片");
MessageBox.Show($"处理完成! 共处理 {_processedCount} 张图片", "完成", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
UpdateStatus($"处理失败: {ex.Message}");
MessageBox.Show($"处理失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
// 执行OCR识别
private async Task<string> PerformOcrAsync(string imagePath)
{
try
{
// 创建RestClient和RestRequest
var client = new RestClient("https://aiapi.jd.com/jdai/ocr_plate");
var request = new RestRequest(Method.POST);
// 添加请求参数
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddHeader("appkey", _ocrConfig.AppKey);
// 读取图片文件并转换为Base64
byte[] imageBytes = File.ReadAllBytes(imagePath);
string base64Image = Convert.ToBase64String(imageBytes);
// 添加请求体
request.AddParameter("image", base64Image);
request.AddParameter("appsecret", _ocrConfig.AppSecret);
// 执行请求
IRestResponse response = await client.ExecuteAsync(request);
if (response.IsSuccessful)
{
// 解析JSON响应
var result = JsonConvert.DeserializeObject<JdOcrResponse>(response.Content);
if (result != null && result.code == "0" && result.data != null && result.data.words_result != null)
{
// 提取识别文本
string ocrText = string.Join(" ", result.data.words_result.Select(r => r.words));
return ocrText;
}
else
{
return $"OCR识别失败: {result?.msg ?? "未知错误"}";
}
}
else
{
return $"请求失败: {response.StatusCode}";
}
}
catch (Exception ex)
{
return $"识别过程出错: {ex.Message}";
}
}
// 生成新文件名
private string GenerateNewFileName(string ocrText, string originalPath)
{
try
{
// 移除不允许的文件名字符
string invalidChars = new string(Path.GetInvalidFileNameChars());
string cleanText = ocrText;
foreach (char c in invalidChars)
{
cleanText = cleanText.Replace(c, '_');
}
// 限制文件名长度
if (cleanText.Length > 80)
{
cleanText = cleanText.Substring(0, 80);
}
// 添加时间戳以确保唯一性
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string baseName = string.IsNullOrEmpty(cleanText.Trim()) ? "unknown" : cleanText.Trim();
string newFileName = $"{baseName}_{timestamp}{Path.GetExtension(originalPath)}";
// 获取文件所在目录
string directory = Path.GetDirectoryName(originalPath);
return Path.Combine(directory, newFileName);
}
catch (Exception)
{
// 如果生成新文件名失败,使用原始文件名加上时间戳
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string fileName = Path.GetFileNameWithoutExtension(originalPath);
string extension = Path.GetExtension(originalPath);
return Path.Combine(
Path.GetDirectoryName(originalPath),
$"{fileName}_{timestamp}{extension}"
);
}
}
// 异步重命名文件
private async Task RenameFileAsync(string oldPath, string newPath)
{
await Task.Run(() =>
{
try
{
// 如果新文件名已存在,则添加序号
if (File.Exists(newPath))
{
string directory = Path.GetDirectoryName(newPath);
string fileName = Path.GetFileNameWithoutExtension(newPath);
string extension = Path.GetExtension(newPath);
int counter = 1;
string tempPath;
do
{
tempPath = Path.Combine(directory, $"{fileName}_{counter}{extension}");
counter++;
} while (File.Exists(tempPath));
newPath = tempPath;
}
// 重命名文件
File.Move(oldPath, newPath);
// 更新文件列表
int index = _selectedFiles.IndexOf(oldPath);
if (index >= 0)
{
_selectedFiles[index] = newPath;
// 在UI线程更新列表项
Dispatcher.Invoke(() =>
{
if (lstFiles.Items.Count > index)
{
lstFiles.Items[index] = Path.GetFileName(newPath);
}
});
}
}
catch (Exception ex)
{
Dispatcher.Invoke(() =>
{
MessageBox.Show($"重命名文件失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
});
}
});
}
// 设置按钮点击事件
private void BtnSettings_Click(object sender, RoutedEventArgs e)
{
ShowSettingsDialog();
}
// 显示设置对话框
private void ShowSettingsDialog()
{
var settingsWindow = new SettingsWindow(_ocrConfig);
if (settingsWindow.ShowDialog() == true)
{
_ocrConfig = settingsWindow.OcrConfig;
SaveConfig();
}
}
// 更新状态栏信息
private void UpdateStatus(string message)
{
lblStatus.Text = message;
}
// 鼠标滚轮事件 - 图片缩放
private void ImgPreview_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (imgPreview.Source != null)
{
double scale = e.Delta > 0 ? 1.1 : 0.9;
imgPreview.RenderTransform = new ScaleTransform(scale, scale);
imgPreview.RenderTransformOrigin = new Point(0.5, 0.5);
}
}
}
// OCR配置类
public class OcrConfig
{
public string AppKey { get; set; }
public string AppSecret { get; set; }
public bool UseJdOcr { get; set; } = true;
public string CustomApiUrl { get; set; }
}
// 京东OCR响应模型
public class JdOcrResponse
{
public string code { get; set; }
public string msg { get; set; }
public OcrData data { get; set; }
}
public class OcrData
{
public List<WordsResult> words_result { get; set; }
}
public class WordsResult
{
public string words { get; set; }
}
}
下面是 SettingsWindow 的代码:
using System.Windows;
namespace ImageOcrRenameTool
{
/// <summary>
/// SettingsWindow.xaml 的交互逻辑
/// </summary>
public partial class SettingsWindow : Window
{
public OcrConfig OcrConfig { get; private set; }
public SettingsWindow(OcrConfig config)
{
InitializeComponent();
OcrConfig = new OcrConfig
{
AppKey = config.AppKey,
AppSecret = config.AppSecret,
UseJdOcr = config.UseJdOcr,
CustomApiUrl = config.CustomApiUrl
};
DataContext = OcrConfig;
}
private void BtnSave_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
private void BtnCancel_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
}
}
下面是主窗口的 XAML 代码:
<Window x:Class="ImageOcrRenameTool.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="图片OCR重命名工具 v1.0" Height="600" Width="800"
WindowStartupLocation="CenterScreen">
<Grid>
<!-- 顶部区域 -->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 标题栏 -->
<Border Grid.Row="0" Background="#333" Padding="10">
<TextBlock Text="图片OCR重命名工具" Foreground="White" FontSize="16" FontWeight="Bold"/>
</Border>
<!-- 主内容区域 -->
<Grid Grid.Row="1" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 左侧文件列表区域 -->
<GroupBox Grid.Column="0" Header="文件列表" Margin="0,0,5,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button x:Name="btnSelectFolder" Content="选择文件夹"
HorizontalAlignment="Left" Margin="5" Padding="5,3"
Click="BtnSelectFolder_Click"/>
<ListBox x:Name="lstFiles" Grid.Row="1" Margin="5"
SelectionChanged="LstFiles_SelectionChanged"
ScrollViewer.HorizontalScrollBarVisibility="Auto"/>
</Grid>
</GroupBox>
<!-- 右侧预览和OCR结果区域 -->
<GroupBox Grid.Column="1" Header="预览和OCR结果" Margin="5,0,0,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="5" Text="文件名:"/>
<TextBox x:Name="txtFileName" Grid.Row="0" Margin="60,5,5,5"
IsReadOnly="True"/>
<ScrollViewer Grid.Row="1" Margin="5" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<Image x:Name="imgPreview" Stretch="Uniform" MouseWheel="ImgPreview_MouseWheel"/>
</ScrollViewer>
<TextBlock Grid.Row="2" Margin="5" Text="OCR识别结果:"/>
<TextBox x:Name="txtOcrResult" Grid.Row="3" Margin="5"
TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"
IsReadOnly="True"/>
</Grid>
</GroupBox>
</Grid>
<!-- 图片导航区域 -->
<Grid Grid.Row="2" Margin="10" HorizontalAlignment="Center">
<Button x:Name="btnPrev" Content="上一张" Padding="8,5"
Margin="0,0,10,0" Click="BtnPrev_Click"/>
<Button x:Name="btnNext" Content="下一张" Padding="8,5"
Margin="10,0,0,0" Click="BtnNext_Click"/>
<TextBlock x:Name="lblFileIndex" Margin="10,0"
VerticalAlignment="Center" FontSize="12"/>
</Grid>
<!-- 底部操作区域 -->
<Grid Grid.Row="3" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="lblStatus" Grid.Column="0" Margin="5"
VerticalAlignment="Center" FontSize="12"/>
<ProgressBar x:Name="progressBar" Grid.Column="1" Margin="5"
Width="200" Height="20" VerticalAlignment="Center"/>
<TextBlock x:Name="lblProgress" Grid.Column="2" Margin="5"
VerticalAlignment="Center" FontSize="12" Width="40"/>
<Button x:Name="btnProcess" Grid.Column="3" Content="开始处理"
Padding="8,5" Margin="5" Click="BtnProcess_Click"/>
<Button x:Name="btnSettings" Grid.Column="4" Content="设置"
Padding="8,5" Margin="5" Click="BtnSettings_Click"/>
</Grid>
</Grid>
</Window>
下面是设置窗口的 XAML 代码:
<Window x:Class="ImageOcrRenameTool.SettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="OCR设置" Height="300" Width="400"
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="5" Text="京东OCR设置"/>
<TextBlock Grid.Row="1" Margin="5" Text="AppKey:"/>
<TextBox Grid.Row="1" Margin="80,5,5,5" Text="{Binding AppKey}"/>
<TextBlock Grid.Row="2" Margin="5" Text="AppSecret:"/>
<TextBox Grid.Row="2" Margin="80,5,5,5" Text="{Binding AppSecret}"
PasswordChar="*"/>
<CheckBox Grid.Row="3" Margin="5" Content="使用京东OCR服务"
IsChecked="{Binding UseJdOcr}"/>
<TextBlock Grid.Row="4" Margin="5" Text="自定义API地址:"/>
<TextBox Grid.Row="4" Margin="80,5,5,5" Text="{Binding CustomApiUrl}"/>
<TextBlock Grid.Row="5" Margin="5" TextWrapping="Wrap"
FontSize="10" Foreground="Gray"
Text="注意: 使用京东OCR服务需要先在京东AI开放平台注册并获取AppKey和AppSecret。"/>
<Grid Grid.Row="6" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Content="保存" Padding="8,5"
Margin="0,0,5,0" Click="BtnSave_Click"/>
<Button Grid.Column="2" Content="取消" Padding="8,5"
Margin="5,0,0,0" Click="BtnCancel_Click"/>
</Grid>
</Grid>
</Window>
总结优化
-
功能总结:
- 实现了批量选择图片文件的功能
- 集成了JD OCR 服务进行文字识别
- 根据识别结果自动重命名图片文件
- 提供了友好的用户界面和操作体验
- 支持图片预览和缩放功能
- 提供了JD OCR 服务配置界面
-
性能优化:
- 使用异步处理避免 UI 线程阻塞
- 添加进度显示,提升用户体验
- 对重命名冲突进行了处理,确保不会覆盖已有文件
-
安全考虑:
- 将敏感的 OCR 配置信息保存到本地配置文件
- 添加了异常处理,确保程序在出现错误时不会崩溃
- 文件操作时进行了冲突检测,避免数据丢失
-
可扩展性:
- 设计了可配置的 OCR 服务接口,便于集成其他 OCR 服务
- 可以进一步扩展功能,如添加文件过滤、批量操作日志等
-
使用建议:
- 在使用前需要在京东 AI 开放平台注册账号并获取 AppKey 和 AppSecret
- 对于大量图片的处理,建议分批进行,避免内存占用过高
- 识别结果可能受到图片质量、文字清晰度等因素影响
这个应用程序可以帮助用户高效地对图片进行重命名,提高工作效率。在实际使用中,你可以根据自己的需求进一步扩展和优化这个工具。