AI提取图片里包含的文字信息-无法复制的痛点

162 阅读6分钟

1. 前言

平时工作中编写开发技术文档,或者学生在编写论文时,经常会上网搜索一些参考文献、文档。

比如: 上网搜索相似的内容参考一下或者引用别人的一段文字,有时候看到一篇较好的内容想要保存等等。

这个过程中会发现,很多网站的提供的页面都是不能复制粘贴的,或者直接是图片形式提供,为了方便能获取这些文字,当前就利用华为云提供的 通用文字识别接口,识别图片里的文本内容,方便复制文字。这个功能QQ上也集成了,使用很方便,这里利用华为云的接口实现一个与QQ类似的功能,截图之后识别图片里包含的文本内容。

这个文字识别接口里不仅仅有通用文字识别功能,还支持很多其他功能:比如身份证、驾驶证、保险单、手写文本、火车票,行驶证.......等等功能。还支持用户自定义识别模板,指定需要识别的关键字段,实现用户特定格式图片的自动识别和结构化提取。

image-20220214142233066

2. 文本识别接口使用介绍

2.1 开通服务

地址: console.huaweicloud.com/ocr/?region…

image-20220214142605756

这个文字识别服务是按调用次数计费的,每个用户每月有1000次的免费调用次数,开通服务后就可以使用。

2.2 接口地址

官网帮助文档: support.huaweicloud.com/api-ocr/ocr…

 POST https://{endpoint}/v2/{project_id}/ocr/general-text
 ​
 示例:
 https://ocr.cn-north-4.myhuaweicloud.com/v2/0e5957be8a00f53c2fa7c0045e4d8fbf/ocr/general-text
 ​
 请求头:
 {
  "X-Auth-Token": "******",
  "Content-Type": "application/json;charset=UTF-8"
 }
 ​
 请求体:
 {
  "image": ----这是图片的bas64编码
 }
 ​
 响应结果:
 {
  "result": {
   "words_block_count": 13,
   "words_block_list": [
    {
     "words": "撤,还是不撤?",
     "location": [
      [
       43,
       39
      ],
      [
       161,
       39
      ],
      [
       161,
       60
      ],
      [
       43,
       60
      ]
     ]
    },
    {
     "words": "让我更骄傲的是公司在大灾面前的表现。",
     "location": [
      [
       72,
       95
      ],
      [
       332,
       95
      ],
      [
       332,
       113
      ],
      [
       72,
       113
      ]
     ]
    },
    {
     "words": "2011年3月11日14时46分,日本东北部海域发生里氏9.0级",
     "location": [
      [
       71,
       122
      ],
      [
       482,
       122
      ],
      [
       482,
       142
      ],
      [
       71,
       142
      ]
     ]
    },
    {
     "words": "地震并引发海啸。那一刻,我们正在距离东京100公里的热海开会,",
     "location": [
      [
       41,
       149
      ],
      [
       481,
       149
      ],
      [
       481,
       171
      ],
      [
       41,
       171
      ]
     ]
    },
    {
     "words": "感觉“咚”",
     "location": [
      [
       42,
       180
      ],
      [
       114,
       180
      ],
      [
       114,
       199
      ],
      [
       42,
       199
      ]
     ]
    },
    {
     "words": "地被震了一下。面对地震,",
     "location": [
      [
       115,
       178
      ],
      [
       296,
       178
      ],
      [
       296,
       199
      ],
      [
       115,
       199
      ]
     ]
    },
    {
     "words": "大家都很镇定,",
     "location": [
      [
       300,
       179
      ],
      [
       400,
       179
      ],
      [
       400,
       197
      ],
      [
       300,
       197
      ]
     ]
    },
    {
     "words": "直到看到电",
     "location": [
      [
       405,
       179
      ],
      [
       483,
       179
      ],
      [
       483,
       196
      ],
      [
       405,
       196
      ]
     ]
    },
    {
     "words": "视上触目惊心的画面:15时 25 分,海啸到达陆前高田市海岸;15时",
     "location": [
      [
       41,
       206
      ],
      [
       485,
       206
      ],
      [
       485,
       228
      ],
      [
       41,
       228
      ]
     ]
    },
    {
     "words": "26分,海啸到达陆前高田市中心;15时43分,陆前高田市依稀只能",
     "location": [
      [
       40,
       234
      ],
      [
       486,
       234
      ],
      [
       486,
       258
      ],
      [
       40,
       258
      ]
     ]
    },
    {
     "words": "看到四层高的市府大楼的屋顶,一瞬间,城镇就变成了汪洋……对",
     "location": [
      [
       40,
       262
      ],
      [
       487,
       262
      ],
      [
       487,
       287
      ],
      [
       40,
       287
      ]
     ]
    },
    {
     "words": "我来说,地震跟家常便饭一样,可眼前的灾难比以往任何一次都要",
     "location": [
      [
       40,
       292
      ],
      [
       487,
       292
      ],
      [
       487,
       317
      ],
      [
       40,
       317
      ]
     ]
    },
    {
     "words": "惨烈,完全超出了我的预期。",
     "location": [
      [
       41,
       326
      ],
      [
       231,
       326
      ],
      [
       231,
       345
      ],
      [
       41,
       345
      ]
     ]
    }
   ],
   "direction": -1
  }
 }

在请求参数里的X-Auth-Token参数比较重要,调用华为云的任何API接口都需要这个参数,获取方式可以看前面的文章。比如这篇文章: support.huaweicloud.com/api-ocr/ocr…

2.3 在线调试接口

地址: apiexplorer.developer.huaweicloud.com/apiexplorer…

使用调试接口想体验识别效果,图片的数据支持base64编码、http网络图片地址传入,测试非常方便。

关于获取图片base64编码的方式,在文档里也有介绍,直接通过浏览器获取。

image-20220214144146775

image-20220214143945486

3. 实现代码

代码采用QT编写的,请求API接口实现调用。其他语言方法是一样的。

3.1 实现效果

image-20220214144917259

image-20220214144825170

3.2 核心代码

 //解析反馈结果
 void Widget::replyFinished(QNetworkReply *reply)
 {
     QString displayInfo="";
     int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
 ​
     //读取所有数据
     QByteArray replyData = reply->readAll();
 ​
     qDebug()<<"状态码:"<<statusCode;
     qDebug()<<"反馈的数据:"<<QString(replyData);
 ​
     //更新token
     if(function_select==3)
     {
         displayInfo="token 更新失败.";
         //读取HTTP响应头的数据
         QList<QNetworkReply::RawHeaderPair> RawHeader=reply->rawHeaderPairs();
         qDebug()<<"HTTP响应头数量:"<<RawHeader.size();
         for(int i=0;i<RawHeader.size();i++)
         {
             QString first=RawHeader.at(i).first;
             QString second=RawHeader.at(i).second;
             if(first=="X-Subject-Token")
             {
                 Token=second.toUtf8();
                 displayInfo="token 更新成功.";
 ​
                 //保存到文件
                 SaveDataToFile(Token);
                 break;
             }
         }
         QMessageBox::information(this,"提示",displayInfo,QMessageBox::Ok,QMessageBox::Ok);
         return;
     }
 ​
     //判断状态码
     if(200 != statusCode)
     {
         //解析数据
         QJsonParseError json_error;
         QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
         if(json_error.error == QJsonParseError::NoError)
         {
             //判断是否是对象,然后开始解析数据
             if(document.isObject())
             {
                 QString error_str="";
                 QJsonObject obj = document.object();
                 QString error_code;
                 //解析错误代码
                 if(obj.contains("error_code"))
                 {
                     error_code=obj.take("error_code").toString();
                     error_str+="错误代码:";
                     error_str+=error_code;
                     error_str+="\n";
                 }
                 if(obj.contains("error_msg"))
                 {
                     error_str+="错误消息:";
                     error_str+=obj.take("error_msg").toString();
                     error_str+="\n";
                 }
 ​
                 //显示错误代码
                 QMessageBox::information(this,"提示",error_str,QMessageBox::Ok,QMessageBox::Ok);
             }
          }
         return;
     }
 ​
     //结果返回
     if(function_select==1)
     {
         //解析数据
         QJsonParseError json_error;
         QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
         if(json_error.error == QJsonParseError::NoError)
         {
             //判断是否是对象,然后开始解析数据
             if(document.isObject())
             {
                 QJsonObject obj = document.object();
                 QString error_code;
                 //解析
                 if(obj.contains("result"))
                 {
                     QJsonObject obj1=obj.take("result").toObject();
 ​
                     QString bank_name;
                     QString card_number;
                     QString type;
 ​
                     QString text;
                     if(obj1.contains("bank_name"))
                     {
                         bank_name=obj1.take("bank_name").toString();
                     }
                     if(obj1.contains("card_number"))
                     {
                         card_number=obj1.take("card_number").toString();
                     }
                     if(obj1.contains("type"))
                     {
                         type=obj1.take("type").toString();
                     }
 ​
 ​
                     text="发卡行:"+bank_name+"\n";
                     text+="卡号:"+card_number+"\n";
                     text+="卡类型:"+type+"\n";
 ​
                     ui->plainTextEdit->setPlainText(text);
                 }
             }
         }
     }
 ​
     //结果返回
     if(function_select==2)
     {
         //解析数据
         QJsonParseError json_error;
         QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
         if(json_error.error == QJsonParseError::NoError)
         {
             //判断是否是对象,然后开始解析数据
             if(document.isObject())
             {
                 QJsonObject obj = document.object();
                 QString error_code;
                 //解析
                 if(obj.contains("result"))
                 {
                     QJsonObject obj1=obj.take("result").toObject();
 ​
                     int words_block_count;
                     QString text="";
                     if(obj1.contains("words_block_count"))
                     {
                         words_block_count=obj1.take("words_block_count").toInt();
 ​
                        // text=QString("识别到%1行文本.\n").arg(words_block_count);
                     }
 ​
                     if(obj1.contains("words_block_list"))
                     {
                         QJsonArray array=obj1.take("words_block_list").toArray();
                         for(int i=0;i<array.size();i++)
                         {
                             QJsonObject obj2=array.at(i).toObject();
                             if(obj2.contains("words"))
                             {
                                 text+=obj2.take("words").toString();
                                 text+="\n";
                             }
                         }
                     }
 ​
                     ui->plainTextEdit->setPlainText(text);
                 }
             }
         }
     }
 }
 ​
 /*
 功能: 获取token
 */
 void Widget::GetToken()
 {
     //表示获取token
     function_select=3;
 ​
     QString requestUrl;
     QNetworkRequest request;
 ​
     //设置请求地址
     QUrl url;
 ​
     //获取token请求地址
     requestUrl = QString("https://iam.%1.myhuaweicloud.com/v3/auth/tokens")
                  .arg(SERVER_ID);
 ​
     //自己创建的TCP服务器,测试用
     //requestUrl="http://10.0.0.6:8080";
 ​
     //设置数据提交格式
     request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json;charset=UTF-8"));
 ​
     //构造请求
     url.setUrl(requestUrl);
 ​
     request.setUrl(url);
 ​
     QString text =QString("{"auth":{"identity":{"methods":["password"],"password":"
     "{"user":{"domain": {"
     ""name":"%1"},"name": "%2","password": "%3"}}},"
     ""scope":{"project":{"name":"%4"}}}}")
             .arg(MAIN_USER)
             .arg(IAM_USER)
             .arg(IAM_PASSWORD)
             .arg(SERVER_ID);
 ​
     //发送请求
     manager->post(request, text.toUtf8());
 }
 ​
 //粘贴图片
 void Widget::on_pushButton_copy_clicked()
 {
     QClipboard *clipboard = QApplication::clipboard();
     const QMimeData *mimeData = clipboard->mimeData();
     if (mimeData->hasImage())
     {
         //将图片数据转为QImage
         QImage img = qvariant_cast<QImage>(mimeData->imageData());
         if(!img.isNull())
         {
            ui->widget->SetImage(img);
         }
     }
 }
 ​
 //获取图片里的文字信息
 void  Widget::getTextInfo(QImage image)
 {
     function_select=2;
     QString requestUrl;
     QNetworkRequest request;
 ​
     //存放图片BASE64编码
     QString imgData;
 ​
     //设置请求地址
     QUrl url;
 ​
     //人脸搜索请求地址
     requestUrl = QString("https://ocr.%1.myhuaweicloud.com/v2/%2/ocr/general-text")
             .arg(SERVER_ID)
             .arg(PROJECT_ID);
 ​
     //设置数据提交格式
     request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json;charset=UTF-8"));
 ​
     //将图片进行Base64编码
     imgData = QString(toBase64(image)); //编码后的图片大小不超过2M
     //设置token
     request.setRawHeader("X-Auth-Token",Token);
 ​
     //构造请求
     url.setUrl(requestUrl);
     request.setUrl(url);
 ​
     QString post_param=QString
                ("{"
                  ""image": "%1""
                 "}").arg(imgData);
 ​
     //发送请求
     manager->post(request, post_param.toUtf8());
 }