正式处理:使用 Google API 从一个图片中读取特定号码段(二)

665 阅读5分钟

写在前面

拖拖拉拉现在才着手写第二部分是因为训练出来的效果并不能让老板满意,于是就拖了很久。在这里声明一下,用户上传图片之后的逻辑为:

  • 上传图片给 Google Cloud Vision 进行文字检测
  • 文字检测的结果给自己训练的数据集进行 machine learning 获取关键数字串
  • 通过正则表达式从关键数字串中获得用户所需要的数据

文字检测和贴标签

文字检测其实也不是很顺利,Google Cloud Vision 比较作妖,有每分钟的上传上限。所以我给文档分了组在上传,获得了图片中存在的文字。

我们所有要处理的电表图片主要分为四种:

  • A 123 456
  • 89 BA 123 456
  • 01 13 22 123456 78
  • 01 13 22 123456

对于前两种,我对 A 123 456 贴上 type1 的标签;对于后两种,我对 01 13 22 123456 贴上 type2 的标签。而我最终要寻找的数字串被称作 numero de matricule ,为所有字符串的 456 三位,准备 machine learning 之后用正则表达式取出。由于大多数情况都是第三种,所以我先写了个正则读了一下 01 13 22 123456 (含括号),这样可以稍微减轻一点自己的工作量。

exports.getNumMatricule = function (filename) {
	var fs = require('fs');
	var fReadName = filename;
	var result;
	var reg1 = /(\d){2}(\s){1}(\d){2}(\s){1}(\d){2}(\s){1}(\d){6}/g;
	var contentText = String(fs.readFileSync(fReadName));
	result = contentText.match(reg1);
	if (result == null) {
		result = "";
	}
	return result;
}

这里用了一下闭包,把所有的结果放到了一个.txt中。

var fs = require("fs");
var getN = require("./getN.js");
var Ut = require("./commen.js");
for (var i = 1; i < 148; i++) {
    (async function (n) {
        await Ut.sleep(n * 100);
        var array = getN.getNumMatricule("text" + n + ".txt") + "\n";
        fs.appendFile('results.txt', array, function (err) {
            if (err) throw err;
        });
    })(i)
}

之后,手动修改了一下没有读到数据的个别情况。(总的来说,Google vision API 返回来的结果其实并不是特别理想的,否则就没必要在这里折腾机器学习了。我们经常发现莫名其妙多出来或者被删掉的空格,以及数字读成字母或者符号的情况。)处理完成了之后批量转成 .jsonl 文件,根据字符串长度贴上不同 type 的标签。

const fs = require('fs');
const readline = require('readline');

readFileToArr("./results.txt", function (data) {
  for (var i = 0; i < 148; i++) {
    var index = fs.readFileSync("./text" + (i + 1) + ".txt", 'utf-8');
    var object = {
      annotations: [],
      text_snippet:
        { content: index }
    };
    if (data[i] != "") {
      if (data[i].length >= 12 && (/[0-9]+/).test(data[i])) {
        var annotation = {
          text_extraction: {
            text_segment: {
              end_offset: index.search(data[i]) + data[i].length, start_offset: index.search(data[i])
            }
          },
          display_name: "Type_2"
        }
        object.annotations.push(annotation);
      } else {
        var annotation = {
          text_extraction: {
            text_segment: {
              end_offset: index.search(data[i]) + data[i].length, start_offset: index.search(data[i])
            }
          },
          display_name: "Type_1"
        }
        object.annotations.push(annotation);
      }
    }
    var resultJson = JSON.stringify(object);
    fs.writeFile("./Jsonl0/text" + (i + 1) + ".jsonl", resultJson, 'utf8', function (err) {
      if (err) {
        console.log("An error occured while writing JSON Object to File.");
        return console.log(err);
      }
      //console.log("JSON file" + (i + 1) + " has been saved.");
    });
  }
}
)

function readFileToArr(fReadName, callback) {
  var fRead = fs.createReadStream(fReadName);
  var objReadline = readline.createInterface({
    input: fRead
  });
  var arr = new Array();
  objReadline.on('line', function (line) {
    arr.push(line);
    //console.log('line:'+ line);
  });
  objReadline.on('close', function () {
    // console.log(arr);
    callback(arr);
  });
}
//readFileToArr函数来源:https://blog.csdn.net/yajie_china/article/details/79407851

上传至训练集的过程还是相对容易的,做一个 .csv 文件分配好(train,test,validate)比例就可以了。这里要注意的就是,每个不同的标签至少要有一百个数据才可以进行训练,同时 test 和 validate 的数量不能少于10 个。

训练结果和使用

训练的结果数据上看来可以说是惨不忍睹,低得惊人的准确率让人下了一大跳。但是我所关心的只是最重要的 456 三位数。于是我写了一个python正则把那三位数提取出来比较了一下。之所以用python是因为在Google文档里只写了python和RestAPI的教程方法。

import sys
import re
from google.cloud import automl_v1beta1
from google.cloud.automl_v1beta1.proto import service_pb2

def get_prediction(content, model_name):
  prediction_client = automl_v1beta1.PredictionServiceClient()

  payload = {'text_snippet': {'content': content, 'mime_type': 'text/plain'} }
  params = {}
  request = prediction_client.predict(model_name, payload, params)
  return request  # waits till request is returned

def replacement2(matched):
  texts = matched.groups()
  return texts[0]+texts[1]+texts[2]+" "

def replacement1(matched):
  texts = matched.groups()
  return texts[0]+" "

f = open('/Users/hang/Documents/LeiTAN/MLGoogle/AutoML/texts/textmltest/3/compare.txt','a')

for i in range(1,150):
  if __name__ == '__main__':
    file_path = "/Users/hang/Documents/LeiTAN/MLGoogle/AutoML/texts/textmltest/3/text" + str(i) + ".txt"
    model_name = "projects/945831563156/locations/us-central1/models/TEN1487129058985639936"

    with open(file_path, 'rb') as ff:
      content = ff.read().decode('utf-8')
    pattern = re.compile(r'\d\d ?\d\d ?\d\d ?\d\d\d\d\d\d')
    result = pattern.findall(content)

    if len(result) == 1:
      mat = re.sub(r'[^a-zA-Z0-9]', "",result[0])
      f.write(mat[-3:-1]+mat[-1]+"\n")
    else:
      pre = get_prediction(content, model_name)
      if len(pre.payload)==0:
        contentnew=re.sub(r'(N|n)',replacement1,content)
        pre = get_prediction(contentnew, model_name)
      if len(pre.payload)==0:
        contentnew=re.sub(r'(N |n )(.)',replacement2,content)
        pre = get_prediction(contentnew, model_name)
      if len(pre.payload)>0:
        mat = re.sub(r'[^a-zA-Z0-9]', "", pre.payload[0].text_extraction.text_segment.content)
        f.write(mat[-3:-1]+mat[-1]+"\n")
      else:
        f.write("\n") 
    
  print (str(i)+"  Finish")
f.close()

可以很明显的看到,在代码中我首先用正则判断存不存在最完美的表达式的情况,如果不存在的就机器学习直接拿到结果,没有结果的话就,根据经验(迷)稍作修改,再次机器学习活的结果。不幸中的万幸就是,456 三位和正确数据对比下来结果还是比较好看的,错误率在2-3%。

最后和老板稍作了讨论。本来老板也就是让我试一下,看一看 google 做这个有没有前途。总结下来还是有的,但是他的不便之处也十分明显,速度慢并且并不是特别精准,这一定程度上也是因为我们数据量不足,同时 google vision 返回的效果不好。

总之,现在肯定是不可能马上使用的,最近老板又把重心放在了另外一边,我就赶着忙着去学一些前端的东西。估计在实习结束之前,还会有一篇关于 Google ML API 的文章,那时候估计就是真的要把它用在网站里了。