如何正确使用卷积神经网络给学校教务系统添堵

前言

  在我本科阶段的学习中,一大乐趣就是和学校的网络管理员谭X斗智斗勇。我之前所在的工作室能用各种官方的、非官方的手段拿到学校的一些数据,然后我们工作室的一大目标就是利用这些数据来让各位同学更加方便地使用学校的各项功能(例如选课、成绩查询之类的功能)。

  上面说的官方的办法就是通过指导老师出面,要到一些关键数据库的帐号密码,而非官方的办法也很粗暴,就是利用用户托管在我们平台的帐号去爬取数据。所以网络中心的谭x老师对我们还是非常恼火的,时不时就会上门找茬,例如这次,他就在我们爬取数据的登录接口加了一个验证码。

OCR技术的发展(从cv到dnn)

  OCR技术是光学字符识别的缩写(Optical Character Recognition),最开始的目的就是通过扫描机直接将书本上的文字扫描成电子档。

  OCR最早利用的技术就是传统cv技术了,其步骤一般是:提取文字区域、对目标区域做梯形矫正、分割单行文字、分割单个字符、对单个字符做识别、最后将所有字符拼接起来。这项技术目前已经非常成熟了,但是仍然有其局限性——对手写文字识别率不高、对带噪文字识别率不高等等。

  近两年崛起的深度学习在图像识别、分类领域发展非常迅速,识别率也非常高。所以这次我打算使用 openCV 做前级处理,最后的字符识别使用机器学习技术来实现。

前级处理

  首先我用爬虫爬了一大堆教务系统的验证码下来,验证码大概长下面这个样子:
1.jpg

  在人工分析了一部分验证码之后,我发现验证码中,各个字符基本没有互相重叠,字符的变形也仅仅限于字体的变化,还有一个特征就是每个验证码都是同样的背景色并且有一条同样的干扰横线,剩下的就是椒盐噪声了。上面说的这些特征用简单的 cv 手段即可去除:

  • 将RGB值符合背景色和干扰横线的全部剔除
  • 使用大小filter过滤较小的噪声
  • 使用膨胀腐蚀算法剔除剩余噪声
  • 使用自动阈值算法将整个图像二值化便于处理
  • 使用边缘检测算法分割各个字符并按照排列顺序提取

  经过上列操作,整张验证码就能被切割成四个处理好的二值化字符图像了,到这一步就基本处理完成了:
2.jpg

使用无监督学习自动打标

  虽然字符分割好了,但是还是有一个很大的问题摆在我面前:我差不多爬了2000左右的验证码,如果要人工标记这堆数据的话,那我就得画差不多1-2个小时专门识别验证码,还是很难受的,所以下一步我选择使用 PCA + Kmeans 来自动将这堆数据分成36类(因为一共有36种字符),算法如下:

  • 使用PCA直接处理图像,降维处理
  • 提取每张图片的PCA过程后前40个分量
  • 将这些数据进行K-means聚类(36类)

  分类结果如下(这里展示了两个类别):
3.jpg
4.jpg

  结果还勉强满意,这个分类准确率能达到70%左右,所以还是得手工剔除一些分类错误的数据,解决了这个问题下一步就是训练识别字符的模型了。

字符识别

  相信刚入门机器学习的人对MNIST数据集一定不会陌生。相对于MNIST数据集,这次要处理的字符集不过是多了26个大写字母而已,用同样的算法也是能解决的,所以简单花了几分钟写了个模型(Keras是真的好用):

def init_model():
    model = Sequential()

    model.add ( Reshape((24, 24, 1) , input_shape = (576,)) )

    #model.add( ZeroPadding2D(padding=(0, 2), data_format=None) )
    model.add( Conv2D(  filters=64, 
                        kernel_size=(2, 2),  
                        padding='valid', 
                        activation="relu",
                        kernel_initializer='glorot_uniform') )
    model.add( Dropout(dr) )

    #model.add( ZeroPadding2D(padding=(0, 2), data_format=None) )
    model.add( Conv2D(  filters=32, 
                        kernel_size=(4, 4),  
                        padding='valid', 
                        activation="relu",
                        kernel_initializer='glorot_uniform') )
    model.add( Dropout(dr) )

    #model.add( ZeroPadding2D(padding=(0, 2), data_format=None) )
    model.add( Conv2D(  filters=16, 
                        kernel_size=(4, 4),  
                        padding='valid', 
                        activation="relu",
                        kernel_initializer='glorot_uniform') )
    model.add( Dropout(dr) )

    model.add( Flatten() )
    model.add( Dense(units=256, activation='relu', kernel_initializer='he_normal') )
    model.add( Dropout(dr) )
    model.add( Dense(units=36, kernel_initializer='he_normal' ))
    model.add( Activation('softmax') )
    model.add( Reshape([36]) )

    model.compile(loss='categorical_crossentropy', optimizer='adam')
    model.summary()
    return model

结果

  emmm,看起来效果还不错:
5.jpg

  使用自动验证码识别技术去登录教务系统:
6.jpg

  我觉得OK,打完收工。

评论卡