找回密码
 立即注册
首页 业界区 业界 Github Copilot 实战: 从零开始用AI写一个OCR工具 (3) ...

Github Copilot 实战: 从零开始用AI写一个OCR工具 (3)

岭猿 2025-6-3 00:31:28
源码

https://github.com/densen2014/Blazor100/tree/master/AI/MiOcr
添加一个屏幕截图功能,显示截图起始点,结束点,截图区域,按键ESC取消截图

这里AI就比较中规中矩,很快就能得到我要的功能了.下面只简单贴一下代码
1.png

ScreenCaptureWindow.xaml
  1. <Window x:
  2.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  4.         Window AllowsTransparency="True" Background="#01000000"
  5.         Topmost="True" ShowInTaskbar="False" WindowState="Maximized">
  6.     <Canvas x:Name="CaptureCanvas">
  7.         <TextBlock x:Name="StartCoordText"
  8.                Foreground="Yellow"
  9.                Background="#80000000"
  10.                FontSize="14"
  11.                Visibility="Collapsed"/>
  12.         <TextBlock x:Name="CurrentCoordText"
  13.                Foreground="Yellow"
  14.                Background="#80000000"
  15.                FontSize="14"
  16.                Visibility="Collapsed"/>
  17.         <TextBlock x:Name="SizeText"
  18.                Foreground="Yellow"
  19.                Background="#80000000"
  20.                FontSize="14"
  21.                Visibility="Collapsed"/>
  22.     </Canvas>
  23. </Window>
复制代码
ScreenCaptureWindow.xaml.cs
  1. using System.Windows;
  2. using System.Windows.Controls;
  3. using System.Windows.Input;
  4. using System.Windows.Interop;
  5. using System.Windows.Media;
  6. using System.Windows.Media.Imaging;
  7. using System.Windows.Shapes;
  8. namespace MiOcr;
  9. public partial class ScreenCaptureWindow : Window
  10. {
  11.     public Rect SelectedRect { get; private set; }
  12.     public BitmapSource? CapturedImage { get; private set; }
  13.     private System.Windows.Point? _start;
  14.     private Rectangle? _rectShape;
  15.     public ScreenCaptureWindow()
  16.     {
  17.         InitializeComponent();
  18.         MouseLeftButtonDown += OnMouseDown;
  19.         MouseMove += OnMouseMove;
  20.         MouseLeftButtonUp += OnMouseUp;
  21.         Cursor = Cursors.Cross;
  22.         PreviewKeyDown += ScreenCaptureWindow_PreviewKeyDown;
  23.         Focusable = true;
  24.         Loaded += (s, e) => Keyboard.Focus(this);
  25.     }
  26.     private void ScreenCaptureWindow_PreviewKeyDown(object sender, KeyEventArgs e)
  27.     {
  28.         if (e.Key == Key.Escape)
  29.         {
  30.             CapturedImage = null;
  31.             DialogResult = false;
  32.             Close();
  33.         }
  34.     }
  35.     private void PositionTextBlocks(double x, double y, double w, double h)
  36.     {
  37.         double margin = 8;
  38.         double canvasWidth = CaptureCanvas.ActualWidth;
  39.         double canvasHeight = CaptureCanvas.ActualHeight;
  40.         // 先测量文本大小
  41.         StartCoordText.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  42.         SizeText.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  43.         double startW = StartCoordText.DesiredSize.Width;
  44.         double startH = StartCoordText.DesiredSize.Height;
  45.         double sizeW = SizeText.DesiredSize.Width;
  46.         double sizeH = SizeText.DesiredSize.Height;
  47.         // 1. 左上优先
  48.         double startX = x - startW - margin;
  49.         double startY = y - startH - margin;
  50.         if (startX >= 0 && startY >= 0)
  51.         {
  52.             Canvas.SetLeft(StartCoordText, startX);
  53.             Canvas.SetTop(StartCoordText, startY);
  54.             Canvas.SetLeft(SizeText, startX);
  55.             Canvas.SetTop(SizeText, startY + startH + 4);
  56.             return;
  57.         }
  58.         // 2. 右上
  59.         startX = x + w + margin;
  60.         startY = y - startH - margin;
  61.         if (startX + startW <= canvasWidth && startY >= 0)
  62.         {
  63.             Canvas.SetLeft(StartCoordText, startX);
  64.             Canvas.SetTop(StartCoordText, startY);
  65.             Canvas.SetLeft(SizeText, startX);
  66.             Canvas.SetTop(SizeText, startY + startH + 4);
  67.             return;
  68.         }
  69.         // 3. 左下
  70.         startX = x - startW - margin;
  71.         startY = y + h + margin;
  72.         if (startX >= 0 && startY + startH + sizeH + 4 <= canvasHeight)
  73.         {
  74.             Canvas.SetLeft(StartCoordText, startX);
  75.             Canvas.SetTop(StartCoordText, startY);
  76.             Canvas.SetLeft(SizeText, startX);
  77.             Canvas.SetTop(SizeText, startY + startH + 4);
  78.             return;
  79.         }
  80.         // 4. 右下
  81.         startX = x + w + margin;
  82.         startY = y + h + margin;
  83.         if (startX + startW <= canvasWidth && startY + startH + sizeH + 4 <= canvasHeight)
  84.         {
  85.             Canvas.SetLeft(StartCoordText, startX);
  86.             Canvas.SetTop(StartCoordText, startY);
  87.             Canvas.SetLeft(SizeText, startX);
  88.             Canvas.SetTop(SizeText, startY + startH + 4);
  89.             return;
  90.         }
  91.         // 5. 屏幕内兜底
  92.         Canvas.SetLeft(StartCoordText, Math.Max(margin, Math.Min(canvasWidth - startW - margin, x)));
  93.         Canvas.SetTop(StartCoordText, Math.Max(margin, Math.Min(canvasHeight - startH - margin, y)));
  94.         Canvas.SetLeft(SizeText, Math.Max(margin, Math.Min(canvasWidth - sizeW - margin, x)));
  95.         Canvas.SetTop(SizeText, Math.Max(margin, Math.Min(canvasHeight - sizeH - margin, y + startH + 4)));
  96.     }
  97.     private void OnMouseDown(object sender, MouseButtonEventArgs e)
  98.     {
  99.         _start = e.GetPosition(this);
  100.         _rectShape = new Rectangle
  101.         {
  102.             Stroke = Brushes.Red,
  103.             StrokeThickness = 2,
  104.             Fill = new SolidColorBrush(Color.FromArgb(40, 0, 0, 255))
  105.         };
  106.         CaptureCanvas.Children.Add(_rectShape);
  107.         Canvas.SetLeft(_rectShape, _start.Value.X);
  108.         Canvas.SetTop(_rectShape, _start.Value.Y);
  109.         StartCoordText.Text = $"起点: ({(int)_start.Value.X}, {(int)_start.Value.Y})";
  110.         StartCoordText.Visibility = Visibility.Visible;
  111.         CurrentCoordText.Text = $"当前: ({(int)_start.Value.X}, {(int)_start.Value.Y})";
  112.         CurrentCoordText.Visibility = Visibility.Visible;
  113.         SizeText.Text = $"大小: 0 x 0";
  114.         SizeText.Visibility = Visibility.Visible;
  115.         // 初始位置
  116.         PositionTextBlocks(_start.Value.X, _start.Value.Y, 0, 0);
  117.     }
  118.     private void OnMouseMove(object sender, MouseEventArgs e)
  119.     {
  120.         if (_start.HasValue && _rectShape != null)
  121.         {
  122.             var pos = e.GetPosition(this);
  123.             double x = Math.Min(_start.Value.X, pos.X);
  124.             double y = Math.Min(_start.Value.Y, pos.Y);
  125.             double w = Math.Abs(_start.Value.X - pos.X);
  126.             double h = Math.Abs(_start.Value.Y - pos.Y);
  127.             Canvas.SetLeft(_rectShape, x);
  128.             Canvas.SetTop(_rectShape, y);
  129.             _rectShape.Width = w;
  130.             _rectShape.Height = h;
  131.             // 更新当前点坐标
  132.             CurrentCoordText.Text = $"当前: ({(int)pos.X}, {(int)pos.Y})";
  133.             Canvas.SetLeft(CurrentCoordText, pos.X + 2);
  134.             Canvas.SetTop(CurrentCoordText, pos.Y + 2);
  135.             // 更新区域大小
  136.             SizeText.Text = $"大小: {(int)w} x {(int)h}";
  137.             // 动态调整文本位置
  138.             PositionTextBlocks(x, y, w, h);
  139.         }
  140.     }
  141.     private void OnMouseUp(object sender, MouseButtonEventArgs e)
  142.     {
  143.         if (_start.HasValue && _rectShape != null)
  144.         {
  145.             var end = e.GetPosition(this);
  146.             double x = Math.Min(_start.Value.X, end.X);
  147.             double y = Math.Min(_start.Value.Y, end.Y);
  148.             double w = Math.Abs(_start.Value.X - end.X);
  149.             double h = Math.Abs(_start.Value.Y - end.Y);
  150.             SelectedRect = new Rect(x, y, w, h);
  151.             // 隐藏坐标
  152.             StartCoordText.Visibility = Visibility.Collapsed;
  153.             CurrentCoordText.Visibility = Visibility.Collapsed;
  154.             // 隐藏区域大小
  155.             SizeText.Visibility = Visibility.Collapsed;
  156.             // 截图
  157.             CapturedImage = CaptureScreenArea(SelectedRect);
  158.             DialogResult = true;
  159.             Close();
  160.         }
  161.     }
  162.     private BitmapSource CaptureScreenArea(Rect rect)
  163.     {
  164.         double dpiScale = NativeMethods.GetDpiScale(this);
  165.         int x = (int)(rect.X * dpiScale);
  166.         int y = (int)(rect.Y * dpiScale);
  167.         int w = (int)(rect.Width * dpiScale);
  168.         int h = (int)(rect.Height * dpiScale);
  169.         IntPtr hdcSrc = NativeMethods.GetDC(IntPtr.Zero);
  170.         IntPtr hdcDest = NativeMethods.CreateCompatibleDC(hdcSrc);
  171.         IntPtr hBitmap = NativeMethods.CreateCompatibleBitmap(hdcSrc, w, h);
  172.         IntPtr hOld = NativeMethods.SelectObject(hdcDest, hBitmap);
  173.         NativeMethods.BitBlt(hdcDest, 0, 0, w, h, hdcSrc, x, y, 0x00CC0020); // SRCCOPY
  174.         NativeMethods.SelectObject(hdcDest, hOld);
  175.         NativeMethods.DeleteDC(hdcDest);
  176.         NativeMethods.ReleaseDC(IntPtr.Zero, hdcSrc);
  177.         try
  178.         {
  179.             var source = Imaging.CreateBitmapSourceFromHBitmap(
  180.                 hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
  181.             source.Freeze();
  182.             return source;
  183.         }
  184.         finally
  185.         {
  186.             NativeMethods.DeleteObject(hBitmap);
  187.         }
  188.     }
  189. }
复制代码
步骤二:确保 PaddleOCRService.cs 的 StartOCR 支持 onStatusChanged
如前所述,PaddleOCRService.cs 需如下实现:
  1. using System.Windows;
  2. namespace MiOcr;
  3. public static class NativeMethods
  4. {
  5.     [System.Runtime.InteropServices.DllImport("gdi32.dll")]
  6.     public static extern bool DeleteObject(IntPtr hObject);
  7.     [System.Runtime.InteropServices.DllImport("user32.dll")]
  8.     public static extern IntPtr GetDC(IntPtr hWnd);
  9.     [System.Runtime.InteropServices.DllImport("user32.dll")]
  10.     public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
  11.     [System.Runtime.InteropServices.DllImport("gdi32.dll")]
  12.     public static extern IntPtr CreateCompatibleDC(IntPtr hdc);
  13.     [System.Runtime.InteropServices.DllImport("gdi32.dll")]
  14.     public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
  15.     [System.Runtime.InteropServices.DllImport("gdi32.dll")]
  16.     public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
  17.     [System.Runtime.InteropServices.DllImport("gdi32.dll")]
  18.     public static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight,
  19.         IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop);
  20.     [System.Runtime.InteropServices.DllImport("gdi32.dll")]
  21.     public static extern bool DeleteDC(IntPtr hdc);
  22.     /// <summary>
  23.     /// 获取指定窗口的 DPI 缩放比例。
  24.     /// </summary>
  25.     /// <param name="window"></param>
  26.     /// <returns></returns>
  27.     public static double GetDpiScale(Window window)
  28.     {
  29.         var source = PresentationSource.FromVisual(window);
  30.         if (source?.CompositionTarget != null)
  31.         {
  32.             return source.CompositionTarget.TransformToDevice.M11; // X 方向缩放
  33.         }
  34.         return 1.0;
  35.     }
  36. }
复制代码
这样,用户在模型首次下载或初始化时会看到“正在初始化OCR模型,请稍候...”,其余时间显示“正在识别...”,体验更友好。
回顾

项目目标

本项目旨在利用 Github Copilot 辅助开发,从零实现一个基于 AI 的 OCR(光学字符识别)工具。项目采用 .NET 9 和 WPF 技术栈,集成了 PaddleOCR 作为核心识别引擎,实现了图片文字识别、区域选择、结果高亮与复制等实用功能。
主要技术与依赖

•        开发语言与平台:C#,.NET 9,WPF
•        OCR引擎:Sdcb.OpenVINO.PaddleOCR
•        图像处理:OpenCvSharp
•        界面交互:WPF,支持拖拽、粘贴、截图等多种图片输入方式
•        AI辅助开发:Github Copilot 提供代码建议与自动补全
核心功能


  • 图片输入
    •        支持文件选择、拖拽、粘贴、屏幕截图等多种方式加载图片。
  • OCR识别
    •        调用 PaddleOCR 进行文字识别,支持中文、英文等多语种。
    •        识别结果实时显示,支持区域高亮和文字复制。
  • 用户体验优化
    •        首次模型下载时,异步回调 UI,友好提示“正在初始化OCR模型,请稍候...”,避免用户等待时无响应。
    •        识别过程有进度提示,提升交互体验。
  • 结果交互
    •        支持鼠标框选图片区域,提取并复制选中区域的文字。
    •        右键点击可复制单个识别文本。
关键实现思路

•        AI驱动开发:通过 Copilot 自动生成代码骨架、方法实现和注释,大幅提升开发效率。
•        异步与回调:模型下载和识别过程均为异步,UI 通过回调及时反馈进度和状态。
•        图像与坐标映射:实现了图片与控件坐标的精准映射,保证高亮和选区准确。
总结

本项目充分发挥了 Github Copilot 在 AI 辅助开发中的优势,实现了一个功能完善、交互友好的 OCR 工具。开发过程中 Copilot 提供了大量代码建议,极大提升了开发效率和代码质量。项目结构清晰,易于扩展,适合 AI+开发实战学习与参考。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册