前段时间,答题 APP 如火如荼的发展,各大互联网公司都加入了撒币大战,包括像冲顶大会,百万英雄,芝士英雄等等。随之而来的也是各个答题应用辅助的兴起。

网上已经有不少答题应用的辅助,一般来说包括两个步骤,即获取题目选项以及搜索答案。对于题目以及选项的获取包括利用 adb 抓取手机屏幕截图,然后使用 ocr(optical character recognization) 的方式去识别题目和选项。大多数使用的 ocr 工具有谷歌开源的 tesseract-ocr以及百度的 ocr API。谷歌的 tesseract-ocr 可以在本地进行安装,软件下载地址是 https://digi.bib.uni-mannheim.de/tesseract/tesseract-ocr-setup-3.05.01.exe , 安装的时候注意选择增加中文简体语言包,否则无法识别中文。另外一种方法就是利用百度的 ocr API,可以免费申请,使用起来比较方便,识别率相对来说也更加准确。百度 API 还有一个优点是图片无需处理就可以进行识别,而 tesseract-ocr 一般还需要对图片进行简单的处理。获取题目以及选项的另外一种方式就是使用抓包工具去抓取 APP 请求从而获取题目以及选项信息。

另一方面,对于题目答案的搜索。常见的几种做法是直接用题目作为搜索关键字打开浏览器,或者是问题加选项搜索,获取搜索引擎搜索的结果数量。通过结果数量来判断问题和选项的相关性从而判断问题的答案,一般来说这种方式获取的答案都是不太准确的,一是因为现在题目的出题方式越来越诡异,二是相关性越大并不一定就意味着是正确答案。本来对于题目和选项的判断就是很难的一件事情,除非你能做出很完美的语意理解,否则很难判断出正确的选项。还有一种比较直白的方式就是建立题库。在本文中,我们讨论一种建立题库的方式,这里只是做一个简单的探索,未必在实际中就能够使用,因为题库必须足够全才能够发挥威力。

使用 elasticsearch 建立题库

本文主要讲解关于题库的建立方面的很小的一方面进行探索,对于答题辅助的使用可以阅读原文查看完整介绍,代码主要是基于TopSup 做了一些调整。Elasticsearch 将被用于题库的建立,对于 es 的安装可以查看第一篇文章。有人可能会觉得用 es 来做题库,简直就是高射炮打蚊子——小题大做。但我觉得 es 安装和使用都很方便,得益于其强大的 RESTFUL接口,几乎可以用任何工具操控 es。Talk is cheap, show me the code.

from elasticsearch import Elasticsearch

def write_quetion():
  question = {
    'question': '谁是世界上最帅的人',
    'answer': 'Neal'
  }
  es = Elasticsearch({'localhost'})
  es.index(index='question-index', doc_type='question', id=1, body=question)

上面是一个简单的像索引中写入一条记录的代码片段,其实 es 可以算是一种非关系型数据库,在 DB-Engines 的最新排名中,es 已经蹿到了第 9 名。Elasticsearch 中的某些概念可以和关系型数据库进行类比:

关系型数据库 Elasticsearch
database index
table type
row document
column field

那么在 es 中搜索问题时应该这样:

def search_question(key_words):
  es = Elasticsearch({'localhost'})
  res = es.search(index='question-index', body={
    "query": {
      "match": {
        "question": key_words,
        "minimum_should_match": "75%"
        }
      }
    }
  })
  if res['hits'['total'] > 0:
    for hit in res['hits']['hits']:
      print(hit['_source']['question'] + ':' + hit['_source']['answer'])
   else:
     print('未搜索到类似结果')

从图片中获取问题和答案

题库的建立可以使用文本的方式或者直接使用答题应用的手机截图,毫无疑问后者是更有价值的。假设我们现在有一张这样的截图:

这张图片中已经包含了正确的选项,但我们如何识别这个图片并且知道这个正确答案呢?使用选项后面的数字么,不可行,正确答案并不一定是选择的最多的选项。感谢图像处理这门课程,里面有一个非常基础的概念帮我解决了这个问题。一般来说将彩色图片转化为灰度图片就是通过一个确定的函数将彩色空间映射到灰度空间。以 matlab 中将 RGB 图(可以理解为一张彩色图)转化为灰度图的 rgb2gray 函数为例,假设一个彩色像素的 RGB 值是 (R, G, B),那么它的灰度值 G 的计算方法应该是:

G = 0.2989 * R + 0.5870 * G + 0.1140 * B

业界的通用做法就是将按照一定的权重来计算彩色像素的灰度值。通过取色笔可以获取上图正确答案背景颜色的 RGB 值是(80, 215, 216),而错误答案背景颜色的 RGB 值是(194, 194, 194)。

936LqI.md.png

今天教大家的是乘法分配律,秀了一波小学数学。言归正传,可以看出,彩色图像映射的灰度值更低。这对于我们区分正确选项和错误选项就有了重大的帮助。首先我们对选项区域进行裁剪,避免右边的数字影响识别结果。通过二值化算法,我们可以把问题选项图使用不同的阈值将图片转换成两张不同的图片,小于阈值的像素点变成黑色像素点,大于阈值的像素点编程白色像素点。二值化转换的算法非常简单:

def binarizing(img, threshold):
    pixdata = img.load()
    w, h = img.size
    for y in range(h):
        for x in range(w):
            if pixdata[x, y] < threshold:
                pixdata[x, y] = 0
            else:
                pixdata[x, y] = 255
    return img

通过阈值 120 和阈值 180(175到194之间的任意值都是可以的) 来获取二值化图片,结果分别为:

93c8dx.png

93clLR.png

这下答案就呼之欲出了吧。我们将这两张图通过 ocr 的方式去识别,第一张图可以获取所有的选项,而第二张图只能获取错误的选项,那么二者的差异之处不正就是正确选项了嘛!是不是骨骼精奇,是不是没想到!

结语

本文就到此为止,本文主要是从一个很小的角度讲述一种建立题库的方式,使用一种图像处理的简单技术来获取正确的选项。是不是觉得学的课程还是有价值的。当然本文只是作为一种技术的探讨,并不一定保证实际中的可操作性,详细代码可以阅读原文查看。

以上。