【竖排繁体识别】如何将竖排繁体图片文字识别转横排繁体,转横排简体导出文本文档,基于WPF和腾讯OCR的实现方案

一、应用场景

在古籍数字化、繁体文档处理、两岸三地文化交流等场景中,经常需要将竖排繁体文字转换为横排文字。例如:

古籍研究人员需要将竖排繁体文献转换为现代横排简体格式以便编辑和研究
出版行业需要将繁体竖排排版转换为简体横排格式
两岸三地交流中需要将繁体竖排文档转换为简体横排格式

二、界面设计

基于 WPF 的界面设计可以包含以下元素:

主窗口标题:”咕嘎OCR竖排繁体简体转换工具”
菜单栏:文件 (打开图片、保存文本)、设置 (API 密钥)、帮助
工具栏:包含打开图片、识别、转换、保存等快捷按钮
左侧区域:图片预览区,显示上传的竖排繁体图片
中间区域:结果显示区,上方显示识别的繁体文本,下方显示转换后的简体文本
右侧区域:设置面板,可选择识别语言、转换方式等
状态栏:显示当前操作状态、进度和错误信息

三、详细代码步骤

下面是基于 WPF 和腾讯 OCR 实现该功能的详细代码步骤:

创建 WPF 应用程序
添加必要的 NuGet 包:Newtonsoft.Json、Microsoft.NET.Http
设计 XAML 界面
实现腾讯 OCR 认证和调用
实现竖排文字识别和处理
实现繁简体转换
实现文本导出功能

以下是完整的代码实现:

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using Microsoft.Win32;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace VerticalOCRConverter
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        // 腾讯云OCR配置
        private string appId = "";
        private string secretId = "";
        private string secretKey = "";
        private string imagePath = "";

        public MainWindow()
        {
            InitializeComponent();
            InitializeSettings();
        }

        private void InitializeSettings()
        {
            // 从配置文件或其他存储中加载设置
            // 这里简化处理,实际应用中应添加配置管理
        }

        private async void BtnOpenImage_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp";
            if (openFileDialog.ShowDialog() == true)
            {
                imagePath = openFileDialog.FileName;
                BitmapImage bitmap = new BitmapImage();
                bitmap.BeginInit();
                bitmap.UriSource = new Uri(imagePath);
                bitmap.EndInit();
                imgPreview.Source = bitmap;

                // 自动开始识别
                if (chkAutoRecognition.IsChecked == true)
                {
                    await RecognizeImageAsync();
                }
            }
        }

        private async void BtnRecognize_Click(object sender, RoutedEventArgs e)
        {
            await RecognizeImageAsync();
        }

        private async Task RecognizeImageAsync()
        {
            if (string.IsNullOrEmpty(imagePath))
            {
                MessageBox.Show("请先选择图片", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            try
            {
                // 显示加载状态
                txtStatus.Text = "正在识别...";
                btnRecognize.IsEnabled = false;
                btnConvert.IsEnabled = false;

                // 调用腾讯OCR
                string result = await CallTencentOCRAsync(imagePath);
                
                // 处理识别结果
                string traditionalText = ProcessOCRResult(result);
                txtTraditional.Text = traditionalText;
                
                // 如果启用了自动转换
                if (chkAutoConvert.IsChecked == true)
                {
                    ConvertText();
                }

                txtStatus.Text = "识别完成";
            }
            catch (Exception ex)
            {
                txtStatus.Text = "识别失败: " + ex.Message;
                MessageBox.Show("识别失败: " + ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            finally
            {
                btnRecognize.IsEnabled = true;
                btnConvert.IsEnabled = true;
            }
        }

        private async Task<string> CallTencentOCRAsync(string imagePath)
        {
            // 检查配置
            if (string.IsNullOrEmpty(secretId) || string.IsNullOrEmpty(secretKey))
            {
                throw new Exception("请先配置腾讯云OCR密钥");
            }

            // 读取图片文件
            byte[] imageBytes = File.ReadAllBytes(imagePath);
            string base64Image = Convert.ToBase64String(imageBytes);

            // 生成签名
            string timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
            string nonce = new Random().Next(10000, 99999).ToString();
            string stringToSign = $"POSTtencentcloudapi.com/?Action=GeneralBasicOCR&Version=2018-11-19&Region=ap-guangzhou&SecretId={secretId}&Timestamp={timestamp}&Nonce={nonce}&SignatureMethod=HmacSHA256";
            string signature = HmacSHA256(stringToSign, secretKey);

            // 准备请求数据
            var requestData = new
            {
                ImageBase64 = base64Image,
                LanguageType = "zh",  // 中文
                IsVerticalText = true  // 竖排文字
            };

            // 发送请求
            using (HttpClient client = new HttpClient())
            {
                client.DefaultRequestHeaders.Add("X-TC-Action", "GeneralBasicOCR");
                client.DefaultRequestHeaders.Add("X-TC-Version", "2018-11-19");
                client.DefaultRequestHeaders.Add("X-TC-Region", "ap-guangzhou");
                client.DefaultRequestHeaders.Add("X-TC-Timestamp", timestamp);
                client.DefaultRequestHeaders.Add("X-TC-Nonce", nonce);
                client.DefaultRequestHeaders.Add("X-TC-SecretId", secretId);
                client.DefaultRequestHeaders.Add("X-TC-Signature", signature);
                client.DefaultRequestHeaders.Add("X-TC-SignatureMethod", "HmacSHA256");
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                var content = new StringContent(JsonConvert.SerializeObject(requestData), Encoding.UTF8, "application/json");
                HttpResponseMessage response = await client.PostAsync("https://ocr.tencentcloudapi.com/", content);
                
                if (response.IsSuccessStatusCode)
                {
                    string result = await response.Content.ReadAsStringAsync();
                    return result;
                }
                else
                {
                    throw new Exception($"API请求失败: {response.StatusCode}");
                }
            }
        }

        private string HmacSHA256(string data, string key)
        {
            using (System.Security.Cryptography.HMACSHA256 hmac = new System.Security.Cryptography.HMACSHA256(Encoding.UTF8.GetBytes(key)))
            {
                byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
                return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
            }
        }

        private string ProcessOCRResult(string jsonResult)
        {
            try
            {
                JObject resultObj = JObject.Parse(jsonResult);
                JToken response = resultObj["Response"];
                
                if (response["Error"] != null)
                {
                    string errorMsg = response["Error"]["Message"].ToString();
                    throw new Exception($"OCR识别失败: {errorMsg}");
                }

                StringBuilder textBuilder = new StringBuilder();
                JArray textDetections = (JArray)response["TextDetections"];
                
                // 处理竖排文字识别结果
                foreach (JObject item in textDetections)
                {
                    string detectedText = item["DetectedText"].ToString();
                    textBuilder.AppendLine(detectedText);
                }

                return textBuilder.ToString();
            }
            catch (Exception ex)
            {
                throw new Exception("处理OCR结果失败: " + ex.Message, ex);
            }
        }

        private void BtnConvert_Click(object sender, RoutedEventArgs e)
        {
            ConvertText();
        }

        private void ConvertText()
        {
            try
            {
                string traditionalText = txtTraditional.Text;
                if (string.IsNullOrEmpty(traditionalText))
                {
                    MessageBox.Show("请先识别文字", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
                    return;
                }

                // 竖排繁体转横排繁体
                string horizontalTraditional = ConvertVerticalToHorizontal(traditionalText);
                
                // 繁体转简体
                string simplifiedText = ConvertToSimplifiedChinese(horizontalTraditional);
                
                txtSimplified.Text = simplifiedText;
                txtStatus.Text = "转换完成";
            }
            catch (Exception ex)
            {
                txtStatus.Text = "转换失败: " + ex.Message;
                MessageBox.Show("转换失败: " + ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private string ConvertVerticalToHorizontal(string verticalText)
        {
            // 处理竖排文字转为横排文字
            // 这里是一个简化的实现,实际应用中可能需要更复杂的算法
            // 例如,处理标点符号的位置、段落等
            
            string[] lines = verticalText.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
            
            if (lines.Length == 0)
                return "";
                
            // 计算最长行的长度
            int maxLength = 0;
            foreach (string line in lines)
            {
                if (line.Length > maxLength)
                    maxLength = line.Length;
            }
            
            StringBuilder horizontalText = new StringBuilder();
            
            // 按列读取竖排文字
            for (int i = 0; i < maxLength; i++)
            {
                StringBuilder row = new StringBuilder();
                // 从右到左读取每一列
                for (int j = lines.Length - 1; j >= 0; j--)
                {
                    if (i < lines[j].Length)
                    {
                        row.Append(lines[j][i]);
                    }
                }
                horizontalText.AppendLine(row.ToString());
            }
            
            return horizontalText.ToString();
        }

        private string ConvertToSimplifiedChinese(string traditionalText)
        {
            // 这里使用了简化的繁简转换方法
            // 实际应用中建议使用专业的繁简转换库,如Microsoft.VisualBasic.dll中的Strings.StrConv
            
            try
            {
                // 由于WPF默认不引用Microsoft.VisualBasic,这里使用反射调用
                Type vbType = Type.GetType("Microsoft.VisualBasic.Strings, Microsoft.VisualBasic, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3");
                if (vbType != null)
                {
                    object[] parameters = new object[] { traditionalText, 2, 0 }; // 2 表示VbStrConv.SimplifiedChinese
                    return (string)vbType.InvokeMember("StrConv", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.InvokeMethod, null, null, parameters);
                }
                else
                {
                    // 如果无法使用Microsoft.VisualBasic,返回原文本并提示
                    MessageBox.Show("无法加载繁简转换库,将使用内置简单转换。可能会有部分字符转换不准确。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
                    return SimpleConvertToSimplified(traditionalText);
                }
            }
            catch
            {
                // 如果出现异常,返回原文本并提示
                MessageBox.Show("繁简转换失败,将使用内置简单转换。可能会有部分字符转换不准确。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
                return SimpleConvertToSimplified(traditionalText);
            }
        }

        private string SimpleConvertToSimplified(string traditionalText)
        {
            // 这是一个非常简化的繁简转换实现,仅作示例
            // 实际应用中应使用完整的繁简字符映射表或专业库
            
            // 这里只列出了一小部分常用字的映射
            string[] traditionalChars = { "簡", "繁", "體", "漢", "字", "轉", "換", "試", "驗" };
            string[] simplifiedChars = { "简", "繁", "体", "汉", "字", "转", "换", "试", "验" };
            
            StringBuilder result = new StringBuilder(traditionalText);
            
            for (int i = 0; i < traditionalChars.Length; i++)
            {
                result.Replace(traditionalChars[i], simplifiedChars[i]);
            }
            
            return result.ToString();
        }

        private void BtnSave_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog();
            saveFileDialog.Filter = "文本文件|*.txt";
            saveFileDialog.Title = "保存转换后的文本";
            
            if (saveFileDialog.ShowDialog() == true)
            {
                try
                {
                    string textToSave = txtSimplified.Text;
                    if (string.IsNullOrEmpty(textToSave))
                    {
                        MessageBox.Show("没有可保存的文本", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
                        return;
                    }
                    
                    File.WriteAllText(saveFileDialog.FileName, textToSave, Encoding.UTF8);
                    txtStatus.Text = "保存成功: " + saveFileDialog.FileName;
                }
                catch (Exception ex)
                {
                    txtStatus.Text = "保存失败: " + ex.Message;
                    MessageBox.Show("保存文件失败: " + ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        private void MenuItemSettings_Click(object sender, RoutedEventArgs e)
        {
            // 显示设置对话框
            SettingsWindow settingsWindow = new SettingsWindow(appId, secretId, secretKey);
            if (settingsWindow.ShowDialog() == true)
            {
                appId = settingsWindow.AppId;
                secretId = settingsWindow.SecretId;
                secretKey = settingsWindow.SecretKey;
                
                // 保存设置
                SaveSettings();
                
                txtStatus.Text = "设置已保存";
            }
        }

        private void SaveSettings()
        {
            // 保存设置到配置文件
            // 这里简化处理,实际应用中应添加配置保存逻辑
        }

        private void MenuItemAbout_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("竖排繁体OCR转换工具 v1.0

基于WPF和腾讯云OCR API开发

将竖排繁体图片文字识别并转换为横排简体文本", "关于", MessageBoxButton.OK, MessageBoxImage.Information);
        }

        private void TxtTraditional_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            // 允许在文本框中使用鼠标滚轮滚动
            if (!e.Handled)
            {
                e.Handled = true;
                var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
                eventArg.RoutedEvent = UIElement.MouseWheelEvent;
                eventArg.Source = sender;
                ((Control)sender).RaiseEvent(eventArg);
            }
        }
    }
}

对应的 XAML 代码:

<Window x:Class="VerticalOCRConverter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="竖排繁体OCR转换工具" Height="700" Width="900"
        WindowStartupLocation="CenterScreen">
    <Grid>
        <!-- 菜单栏 -->
        <Menu Height="23" HorizontalAlignment="Left" Name="menu1" VerticalAlignment="Top" Width="884">
            <MenuItem Header="文件">
                <MenuItem Header="打开图片" Click="BtnOpenImage_Click" />
                <MenuItem Header="保存文本" Click="BtnSave_Click" />
                <Separator />
                <MenuItem Header="退出" Click="MenuItemExit_Click" />
            </MenuItem>
            <MenuItem Header="设置">
                <MenuItem Header="API密钥" Click="MenuItemSettings_Click" />
            </MenuItem>
            <MenuItem Header="帮助">
                <MenuItem Header="关于" Click="MenuItemAbout_Click" />
            </MenuItem>
        </Menu>
        
        <!-- 工具栏 -->
        <ToolBar Height="30" HorizontalAlignment="Left" Name="toolBar1" VerticalAlignment="Top" Width="884" Margin="0,23,0,0">
            <Button Content="打开图片" Height="23" Name="btnOpenImage" Width="75" Click="BtnOpenImage_Click">
                <Button.Icon>
                    <Image Source="pack://application:,,,/Images/open.png" Height="16" Width="16" />
                </Button.Icon>
            </Button>
            <Button Content="识别" Height="23" Name="btnRecognize" Width="75" Click="BtnRecognize_Click">
                <Button.Icon>
                    <Image Source="pack://application:,,,/Images/recognize.png" Height="16" Width="16" />
                </Button.Icon>
            </Button>
            <Button Content="转换" Height="23" Name="btnConvert" Width="75" Click="BtnConvert_Click">
                <Button.Icon>
                    <Image Source="pack://application:,,,/Images/convert.png" Height="16" Width="16" />
                </Button.Icon>
            </Button>
            <Button Content="保存" Height="23" Name="btnSave" Width="75" Click="BtnSave_Click">
                <Button.Icon>
                    <Image Source="pack://application:,,,/Images/save.png" Height="16" Width="16" />
                </Button.Icon>
            </Button>
            <Separator />
            <CheckBox Content="自动识别" Height="16" Name="chkAutoRecognition" VerticalAlignment="Center" IsChecked="True" />
            <CheckBox Content="自动转换" Height="16" Name="chkAutoConvert" VerticalAlignment="Center" IsChecked="True" />
        </ToolBar>
        
        <!-- 状态栏 -->
        <StatusBar Height="22" HorizontalAlignment="Left" Name="statusBar1" VerticalAlignment="Bottom" Width="884">
            <TextBlock Name="txtStatus" Text="就绪" />
        </StatusBar>
        
        <!-- 左侧图片预览区 -->
        <GroupBox Header="图片预览" Height="590" HorizontalAlignment="Left" Margin="10,59,0,0" Name="grpImage" VerticalAlignment="Top" Width="300">
            <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                <Image Name="imgPreview" Stretch="Uniform" />
            </ScrollViewer>
        </GroupBox>
        
        <!-- 中间结果显示区 -->
        <GroupBox Header="识别结果" Height="590" HorizontalAlignment="Left" Margin="320,59,0,0" Name="grpResult" VerticalAlignment="Top" Width="554">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                
                <GroupBox Header="横排繁体" Grid.Row="0" Margin="5">
                    <TextBox Name="txtTraditional" AcceptsReturn="True" AcceptsTab="True" 
                             HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" 
                             TextWrapping="Wrap" PreviewMouseWheel="TxtTraditional_PreviewMouseWheel" />
                </GroupBox>
                
                <Label Content="↓ 转换为 ↓" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1" />
                
                <GroupBox Header="横排简体" Grid.Row="2" Margin="5">
                    <TextBox Name="txtSimplified" AcceptsReturn="True" AcceptsTab="True" 
                             HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" 
                             TextWrapping="Wrap" />
                </GroupBox>
            </Grid>
        </GroupBox>
    </Grid>
</Window>

设置窗口代码:

using System.Windows;

namespace VerticalOCRConverter
{
    /// <summary>
    /// SettingsWindow.xaml 的交互逻辑
    /// </summary>
    public partial class SettingsWindow : Window
    {
        public string AppId { get; private set; }
        public string SecretId { get; private set; }
        public string SecretKey { get; private set; }

        public SettingsWindow(string appId, string secretId, string secretKey)
        {
            InitializeComponent();
            AppId = appId;
            SecretId = secretId;
            SecretKey = secretKey;
            
            txtAppId.Text = appId;
            txtSecretId.Text = secretId;
            txtSecretKey.Text = secretKey;
        }

        private void BtnSave_Click(object sender, RoutedEventArgs e)
        {
            AppId = txtAppId.Text.Trim();
            SecretId = txtSecretId.Text.Trim();
            SecretKey = txtSecretKey.Text.Trim();
            
            DialogResult = true;
            Close();
        }

        private void BtnCancel_Click(object sender, RoutedEventArgs e)
        {
            DialogResult = false;
            Close();
        }
    }
}

对应的设置窗口 XAML 代码:

<Window x:Class="VerticalOCRConverter.SettingsWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="设置" Height="250" Width="400" WindowStartupLocation="CenterScreen"
        ResizeMode="NoResize">
    <Grid>
        <Label Content="AppId:" HorizontalAlignment="Left" Margin="20,20,0,0" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="100,20,0,0" Name="txtAppId" VerticalAlignment="Top" Width="260" />
        
        <Label Content="SecretId:" HorizontalAlignment="Left" Margin="20,60,0,0" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="100,60,0,0" Name="txtSecretId" VerticalAlignment="Top" Width="260" />
        
        <Label Content="SecretKey:" HorizontalAlignment="Left" Margin="20,100,0,0" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="100,100,0,0" Name="txtSecretKey" VerticalAlignment="Top" Width="260" />
        
        <Button Content="保存" Height="23" HorizontalAlignment="Left" Margin="160,160,0,0" Name="btnSave" VerticalAlignment="Top" Width="75" Click="BtnSave_Click" />
        <Button Content="取消" Height="23" HorizontalAlignment="Left" Margin="250,160,0,0" Name="btnCancel" VerticalAlignment="Top" Width="75" Click="BtnCancel_Click" />
    </Grid>
</Window>

总结优化

性能优化

对于大图片,可以先进行压缩处理再进行 OCR 识别
使用异步处理避免 UI 线程阻塞
实现识别结果缓存机制,避免重复识别

功能增强

支持批量处理多个图片
添加更多 OCR 选项,如手写文字识别
增加文字编辑功能,方便手动调整识别结果
支持导出为多种格式,如 DOCX、PDF 等

用户体验优化

添加进度条显示识别和转换进度
实现撤销 / 重做功能
添加快捷键支持
提供更多自定义选项,如字体、排版等

技术优化

使用 MVVM 模式重构代码,提高可维护性
添加单元测试确保功能正确性
实现配置文件管理,保存用户设置
添加错误日志记录功能

通过以上实现,你可以开发一个功能完整的竖排繁体图片文字识别转换工具,满足古籍数字化、文档处理等多种应用场景的需求。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容