使用Python统计抗疫未填报同学排名

辅导员老师每天都会发当天没有填“抗疫小助手”同学名单的截图。有一天突发奇想,能不能统计一下没填的同学的名单,然后排序看一看哪位能够勇夺第一名呢?

本文章仅为娱乐及学习目的。

说干就干。统计的核心过程就是把老师发的截图集中保存下来,然后将其中的名字识别出来即可。保存截图就是一个体力活,只需要每天老师发截图的时候动动小手保存下来就可以。保存下来的截图如下图所示。

https://img.yuanze.wang/posts/python-name-counter/screenshots.jpg
保存截图的文件夹

老师的截图的示例如下图所示。

https://img.yuanze.wang/posts/python-name-counter/orig.jpg
老师截图的例子

其中,打码的第一列是名字,第二列是学号。我们需要的只是名字,并且由于老师用的手机是固定的,每张图中的名字的位置都相同,颜色也都相同。通过计算CSS,得知姓名一栏占用了网页最左侧的10%宽度。这样,我们只需要将图像最左侧的一竖条提取出来,并通过颜色范围将字提取出来,即可得到只含有名字的图片。

https://img.yuanze.wang/posts/python-name-counter/names.jpg
裁剪并颜色范围筛选过的图片(部分)

得到这个图片之后,如何把它们转换成可以供程序处理的文字呢?答案很简单,OCR。在此我们可以使用本地的OCR,也可以使用云端的OCR。为了简单起见,这里使用了百度云AI开放平台的通用文字识别服务。为了精准起见,这里使用的是高精度版。对于免费用户,每天有500次的免费额度且有2 Qps的限制,但这已经足够我们使用的了。为了使用Python语言调用百度云的API,可以使用百度云官方的Python API。使用pip安装好后,向百度申请API Key即可正常使用。

识别过程中,首先读入图片,然后经过opencv的处理生成一个元组,之后同样使用opencv将其编码成jpg格式,通过API发送到百度进行识别即可获得图片中所有人的名字。

https://img.yuanze.wang/posts/python-name-counter/output.jpg
Python输出

最后,我们只需要使用matplotlib来绘制图表即可。再次不再赘述。(matplotlib用的很少,画出来的图能正常显示就行了,别要求太多……)

https://img.yuanze.wang/posts/python-name-counter/result.jpg
识别结果

第一名居然超出第二名18次,果然是优秀的同学(虽然我并不认识)。

如下是所有代码。

from aip import AipOcr
import cv2, numpy as np, matplotlib
import os, time
from matplotlib import pyplot as plt
font = {'family': 'MicroSoft YaHei'}
matplotlib.rc('font', **font)  # 使pyplot支持中文
plot_num = 30
APP_ID = 'xxxxxxxxx'
API_KEY = 'xxxxxxxxx'
SECRET_KEY = 'xxxxxxxxx'
font_color = np.array([35, 46, 204])
font_horizontal_ratio = 0.1 # 测量CSS之后得出姓名栏10%的比例
folder = r'screenshots'
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
name_count = dict()
filelist = os.listdir(folder)
for file in filelist:
   image = cv2.imread(folder+'\\'+file) # 读入图片
   size = image.shape
   fnt = cv2.inRange(image, font_color*0.9, font_color*1.1) # 提取文字
   cropped = fnt[:, 0:int(size[0]*font_horizontal_ratio)] # 裁剪图片
   _, img_encoded = cv2.imencode('.jpg',cropped) # 编码jpg
   res = client.basicAccurate(img_encoded)
   print('[', file, ']', end=' ')
   try:
      for name_dict in res['words_result']:
         name = name_dict['words']
         if name in name_count:
            name_count[name] = name_count[name] + 1 # 累计出现次数
         else:
            name_count[name] = 1 # 新增
         print(name, end=' ')
   except ValueError:
      print('识别错误', end='')
      pass
   print('')
   time.sleep(0.5)
print('总人数: {}'.format(name_count.__len__()))
print(name_count)
name_sorted = np.array(sorted(name_count.items(), key=lambda d: d[1], reverse=True))
col_name = np.flip(name_sorted[0:plot_num,0])
col_times = np.flip(name_sorted[0:plot_num,1])
plt.figure(dpi=144)  # 设置图形大小
plt.barh(range(plot_num), col_times, height=0.3, color='orange')  # 绘制横着的条形图,横着的用height控制线条宽度
plt.yticks(range(plot_num), col_name)
plt.grid(alpha=0.3)  # 添加网格
plt.show()