ETJava Beta | Java    注册   登录
  • 搜索:
  • 如何自己动手实现一个图片解答小助手

    发表于      阅读(1)     博客类别:Crawler     转自:https://www.cnblogs.com/mingupupu/p/18453894
    如有侵权 请联系我们删除  (页面底部联系我们)  

    有一张图片如下所示:

    Kimi上有一个功能,就是解析图片内容,给出回答:

    image-20241008185201298

    这样可以用于拍照向AI提问的场景,我自己也有这方面的需求,因此动手实践了一下。

    自己动手实现的效果如下所示:

    image-20241008185722470

    那么自己如何实现呢?

    可以通过添加一个OCR的功能来实现。中文图片文字识别也就是OCR效果比较好的是百度开源的PaddleOCR,之前介绍过PaddleOCR的.NET绑定PaddleSharp,见这篇文章:C#使用PaddleOCR进行图片文字识别

    之前使用PaddleOCR的时候,我已经在电脑上安装了一个虚拟环境,因为需求比较简单,就是将图片进行文字识别之后返回文本就行了,因此今天玩个不一样的,不用.NET绑定,直接调用Python脚本就好了。

    那么现在拆解任务就是:

    C#如何调用Python脚本?

    那么就先来试一下,最简单的调用,调用Python脚本输出一个Hello:

    print("Hello")
    

    可以使用 System.Diagnostics.Process 类来启动一个外部进程来运行Python脚本:

     string pythonScriptPath = @"D:\学习路线\人工智能\图片文字识别\test.py"; // 替换为你的Python脚本路径
     string pythonExecutablePath = @"D:\SoftWare\Anaconda\envs\paddle_env\python.exe"; // 替换为你的Python解释器路径                                                                                     
     ProcessStartInfo start = new ProcessStartInfo();
     start.FileName = pythonExecutablePath;
     start.Arguments =$"{pythonScriptPath}";
     start.UseShellExecute = false;
     start.RedirectStandardOutput = true;
     start.RedirectStandardError = true;
     start.CreateNoWindow = true;
    
     using (Process process = Process.Start(start))
     {
         using (System.IO.StreamReader reader = process.StandardOutput)
         {
             string result = reader.ReadToEnd();
             MessageBox.Show(result);
         }
    
         using (System.IO.StreamReader errorReader = process.StandardError)
         {
             string errors = errorReader.ReadToEnd();
             if (!string.IsNullOrEmpty(errors))
             {
                 MessageBox.Show("Errors: " + errors);
             }
         }
     }      
    

    其中ProcessStartInfo各属性的解释如下:

    1. FileName
      • 含义:指定要启动的程序或文档的名称。
      • 示例:在这里,pythonExecutablePath 是 Python 解释器的路径,如 "C:\path\to\python.exe"
    2. Arguments
      • 含义:指定传递给要启动程序的命令行参数。
      • 示例:在这里,pythonScriptPath 是你要执行的 Python 脚本的路径,如 "C:\path\to\hello.py"
    3. UseShellExecute
      • 含义:指定是否使用操作系统 shell 来启动进程。如果设置为 false,则直接启动进程;如果设置为 true,则通过 shell 启动进程。
      • 示例:在这里,设置为 false,表示不使用 shell 启动进程,而是直接启动 Python 解释器。
    4. RedirectStandardOutput
      • 含义:指定是否将子进程的标准输出重定向到 Process.StandardOutput 流。
      • 示例:在这里,设置为 true,表示将 Python 脚本的输出重定向到 Process.StandardOutput,以便你可以读取它。
    5. RedirectStandardError
      • 含义:指定是否将子进程的标准错误输出重定向到 Process.StandardError 流。
      • 示例:在这里,设置为 true,表示将 Python 脚本的错误输出重定向到 Process.StandardError,以便你可以读取它。
    6. CreateNoWindow
      • 含义:指定是否在新窗口中启动进程。如果设置为 true,则不会创建新窗口;如果设置为 false,则会创建新窗口。
      • 示例:在这里,设置为 true,表示不创建新窗口,即在后台运行 Python 脚本。

    现在查看一下运行效果:

    image-20241008191313678

    获取到了Python脚本输出的值。

    那么再拆解一下任务,我们需要在命令行中传入一个参数,该如何实现呢?

    import sys
    
    # 检查是否有参数传递
    if len(sys.argv) > 1:
        n = sys.argv[1]
        print(f"hello {n}")
    else:
        print("请提供一个参数")
    

    只需修改下图中,这两处地方即可:

    image-20241008191639208

    现在再来试下效果:

    image-20241008191805671

    成功在命令行中传入了一个参数。

    那么现在我们的准备工作已经做好了。

    PaddleOCR的使用脚本如下:

    import sys
    import logging
    from paddleocr import PaddleOCR, draw_ocr
    
    # Paddleocr目前支持的多语言语种可以通过修改lang参数进行切换
    # 例如`ch`, `en`, `fr`, `german`, `korean`, `japan`
    
    
    # 检查是否有参数传递
    if len(sys.argv) > 1:
        imagePath = sys.argv[1]
    else:
        print("请提供一个参数")
    
    # 配置日志级别为 WARNING,这样 DEBUG 和 INFO 级别的日志信息将被隐藏
    logging.basicConfig(level=logging.WARNING)
    
    # 创建一个自定义的日志处理器,将日志输出到 NullHandler(不输出)
    class NullHandler(logging.Handler):
        def emit(self, record):
            pass
    
    # 获取 PaddleOCR 的日志记录器
    ppocr_logger = logging.getLogger('ppocr')
    
    # 移除所有默认的日志处理器
    for handler in ppocr_logger.handlers[:]:
        ppocr_logger.removeHandler(handler)
    
    # 添加自定义的 NullHandler
    ppocr_logger.addHandler(NullHandler())
    
    ocr = PaddleOCR(use_angle_cls=True, lang="ch")  # need to run only once to download and load model into memory
    img_path = imagePath
    result = ocr.ocr(img_path, cls=True)
    for idx in range(len(result)):
        res = result[idx]   
        for line in res:
            print(line[1][0])
    

    在vs code中运行效果如下所示:

    image-20241008192131148

    现在在WPF应用中调用结果如下:

    image-20241009104512270

    现在图片文字识别的部分已经搞定了。

    现在就需要与大语言模型结合起来了,就是将识别出来的文字,丢给大语言模型。

    可以这样写:

     public async IAsyncEnumerable<string> GetAIResponse4(string question, string imagePath)
     {
         string pythonScriptPath = @"D:\学习路线\人工智能\图片文字识别\test.py"; // 替换为你的Python脚本路径
         string pythonExecutablePath = @"D:\SoftWare\Anaconda\envs\paddle_env\python.exe"; // 替换为你的Python解释器路径                                                                                         
         string arguments = imagePath; // 替换为你要传递的参数
    
         ProcessStartInfo start = new ProcessStartInfo();
         start.FileName = pythonExecutablePath;
         start.Arguments = $"{pythonScriptPath} {arguments}";
         start.UseShellExecute = false;
         start.RedirectStandardOutput = true;
         start.RedirectStandardError = true;
         start.CreateNoWindow = true;
    
         string result = "";
    
         using (Process process = Process.Start(start))
         {
             using (System.IO.StreamReader reader = process.StandardOutput)
             {
                 result = reader.ReadToEnd();                   
             }
    
             using (System.IO.StreamReader errorReader = process.StandardError)
             {
                 string errors = errorReader.ReadToEnd();
                 if (!string.IsNullOrEmpty(errors))
                 {
                     MessageBox.Show("Errors: " + errors);
                 }
             }
         }
    
         string skPrompt = """
                            获取到的图片内容:{{$PictureContent}}。
                            根据获取到的信息回答问题:{{$Question}}。                       
                         """;
         await foreach (var str in _kernel.InvokePromptStreamingAsync(skPrompt, new() { ["PictureContent"] = result, ["Question"] = question }))
         {
             yield return str.ToString();
         }
     }
    

    就可以实现如下的效果了:

    image-20241009104837084

    全部代码可在https://github.com/Ming-jiayou/SimpleRAG看到。