网络程序设计学习总结

前言

转眼间网络程序设计这门课就要结课了,虽然没有做出什么贡献,但是看着项目从无到有以及同学们的踊跃分享还是感到十分欣慰。记得在第一节课老师就说过二八定律,现在有了深刻体会,作为后面80%的我在面对各位大佬的疯狂pr时感到了各种恐惧。

无论如何,作为完全没有软件工程基础的我在经历了一个完整的项目之后对开发过程有了进一步的认识。


项目简介

项目原型是以深度学习和神经网络为工具,以大数据为基础,来辅助医生判断的病例生成系统。

具体过程是将血常规检验报告的图片识别出年龄、性别及血常规检验的各项数据,在识别后与该病人病例相结合进行深度学习,训练出模型后再根据用户拍照或者上传的图片进行识别预测其年龄和性别。
注:目前只支持独墅湖科教区创新医院化验单识别。 
 

运行环境
#安装numpy
sudo apt-get install python-numpy #  http://www.numpy.org/
#安装opencv
sudo apt-get install python-opencv # http://opencv.org/

#安装OCR和预处理相关依赖
sudo apt-get install tesseract-ocr
sudo pip install pytesseract
sudo apt-get install python-tk
sudo pip install pillow

#安装Flask框架、mongo
sudo pip install Flask

sudo apt-get install mongodb # 如果找不到可以先sudo apt-get update
sudo service mongodb started
sudo pip install pymongo

#安装numpy
sudo apt-get install python-numpy
#安装PIL
sudo apt-get install python-imaging
#安装Tensorflow
pip install --upgrade https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.12.0rc0-cp27-none-linux_x86_64.whl

#运行demo
cd  BloodTestReportOCR
python view.py # upload图像,在浏览器打开http://localhost:8080

文件介绍
view.py

Web 端上传图片到服务器,存入mongodb并获取oid,稍作修整,希望能往REST架构设计,目前还不完善。前端采用了vue.js, mvvm模式。

imageFilter.py

对图像透视裁剪和OCR进行了简单的封装,以便于模块间的交互,规定适当的接口

imageFilter = ImageFilter() #  opencv
num = 22
print imageFilter.ocr(num)


ocr函数 - 模块主函数返回识别数据

用于对img进行ocr识别,他会先进行剪切,之后进一步做ocr识别,返回一个json对象

如果剪切失败,则返回None

@num 规定剪切项目数

perspect函数做 - 初步的矫正图片

用于透视image,他会缓存一个透视后的opencv numpy矩阵,并返回该矩阵

透视失败,则会返回None,并打印不是报告

@param 透视参数

关于param

参数的形式为[p1, p2, p3 ,p4 ,p5]。

p1,p2,p3,p4,p5都是整型,其中p1必须是奇数。

p1是高斯模糊的参数,p2和p3是canny边缘检测的高低阈值,p4和p5是和筛选有关的乘数。

如果化验报告单放在桌子上时,有的边缘会稍微翘起,产生比较明显的阴影,这种阴影有可能被识别出来,导致定位失败。

解决的方法是调整p2和p3,来将阴影线筛选掉。但是如果将p2和p3调的比较高,就会导致其他图里的黑线也被筛选掉了。

参数的选择是一个问题。

我在getinfo.default中设置的是一个较低的阈值,p2=70,p3=30,这个阈值不会屏蔽阴影线。

如果改为p2=70,p3=50则可以屏蔽,但是会导致其他图片识别困难。

就现在来看,得到较好结果的前提主要有三个

- 化验单尽量平整

- 图片中应该包含全部的三条黑线

- 图片尽量不要包含化验单的边缘,如果有的话,请尽量避开有阴影的边缘。

filter函数 - 过滤掉不合格的或非报告图片

返回img经过透视过后的PIL格式的Image对象,如果缓存中有PerspectivImg则直接使用,没有先进行透视,过滤失败则返回None

@param filter参数

autocut函数 - 将图片中性别、年龄、日期和各项目名称数据分别剪切出来

用于剪切ImageFilter中的img成员,剪切之后临时图片保存在out_path,如果剪切失败,返回-1,成功返回0

@num 剪切项目数

@param 剪切参数

剪切出来的图片在BloodTestReportOCR/temp_pics/ 文件夹下

函数输出为data0.jpg,data1.jpg......等一系列图片,分别是白细胞计数,中性粒细胞记数等的数值的图片。

classifier.py

用于判定裁剪矫正后的报告和裁剪出检测项目的编号

imgproc.py 

将识别的图像进行处理二值化等操作,提高识别率,包括对中文和数字的处理

digits

将该文件替换Tesseract-OCR\tessdata\configs中的digits

Code Review
根据demo的数据流主要分为以下三个部分:
透视变换
首先是上传图片后寻找数据表格内容并透视成1000*760的矩阵(web端为了填充将图片拉大了):  
网络程序设计学习总结_第1张图片

透视主要流程:

  • 图像预处理去噪
网络程序设计学习总结_第2张图片
  • 采用Canny算子提取边缘
 网络程序设计学习总结_第3张图片
  • 调用CV2模块的findContours提取矩形轮廓,筛选对角线大于阈值的轮廓
网络程序设计学习总结_第4张图片
  • 将轮廓变成线:比较最小外接矩形相邻两条边的长短,以两条短边的中点作为线的两端
网络程序设计学习总结_第5张图片
  • 若线数量大于三则根据线长短继续筛选长线。
  • 根据三条线间的距离确定表格头部和内容的位置

  • 利用向量叉乘不可变性确定起始点(若方向相反,结果为负),将表格内容往上包含头部的一些年龄性别信息
网络程序设计学习总结_第6张图片
  •  将表格切下并进行透视投影变换
具体的代码实现如下所示:


数据提取
  网络程序设计学习总结_第7张图片

主要步骤:

  • 将表格数据内容切成22份数据
网络程序设计学习总结_第8张图片
  •   对每份数据调用pytesserac的OCR库进行识别
识别代码如下:
'''
    ocr img ocr ocr json
     None
    @num 
'''
def ocr(self, num):
    digtitsresult = []
    chiresult = []
    # 
    if self.autocut(num) == -1:
        return None
    # 
    def image_to_string(image, flag=True):
        if flag:
            text = pytesseract.image_to_string(Image.fromarray(image), config='-psm 7 digits')
        else:
            text = pytesseract.image_to_string(Image.fromarray(image), lang='chi_sim', config=' -psm 7 Bloodtest')
        return text
    # 
    def read(url):
        image = cv2.imread(url)
        return image
    # load json example
    with open('bloodtestdata.json') as json_file:
        data = json.load(json_file)
    # 
    for i in range(num):
        item = read('temp_pics/p' + str(i) + '.jpg')
        item_num = classifier.getItemNum(item)
        image = read('temp_pics/data' + str(i) + '.jpg')
        image = imgproc.digitsimg(image)
        digtitstr = image_to_string(image)
        digtitstr = digtitstr.replace(" ", '')
        digtitstr = digtitstr.replace("-", '')
        digtitstr = digtitstr.strip(".")
        data['bloodtest'][item_num]['value'] = digtitstr
    json_data = json.dumps(data,ensure_ascii=False,indent=4)
    return json_data
  • web用ajax异步传输json数据,数据格式如下: 
网络程序设计学习总结_第9张图片

进行预测
 本项目采用TensorFlow进行预测,通过view.py调用predict函数,预测结果如下:
网络程序设计学习总结_第10张图片
  预测流程:
  •  数据预处理(一般是归一化)

代码如下:

def normalized(a,b):
    for i in range(22):
        tmp = np.mean(a[:, i])
        a[:, i] = a[:, i] - tmp
        b[:, i] = b[:, i] - tmp
        if np.min(a[:, i]) != np.max(a[:, i]):
            b[:, i] = 2 * (b[:, i] - np.min(a[:, i])) / (np.max(a[:, i]) - np.min(a[:, i])) - 1
        else:
            b[:, i] = 0
    return b
tensorflow 
def predict(data_predict):
    tf.reset_default_graph()
    data_nor = np.loadtxt(open("./data.csv", "rb"), delimiter=",", skiprows=0)
    data_predict = normalized(data_nor[:, 2:], data_predict)
    '''
    
    '''
    learning_rate = 0.005
    display_step = 100
    n_input = 22
    n_hidden_1_age = 32
    n_hidden_2_age = 16
    n_classes_age = 1
    n_hidden_1_sex = 16
    n_hidden_2_sex = 8
    n_classes_sex = 2
    data = np.loadtxt(open("./data.csv", "rb"), delimiter=",", skiprows=0)
    '''
    
    '''
    x_age = tf.placeholder("float", [None, n_input])
    y_age = tf.placeholder("float", [None, n_classes_age])
    def multilayer_perceptron_age(x_age, weights_age, biases_age):
        # Hidden layer with RELU activation
        layer_1 = tf.add(tf.matmul(x_age, weights_age['h1']), biases_age['b1'])
        layer_1 = tf.nn.relu(layer_1)
        # Hidden layer with RELU activation
        layer_2 = tf.add(tf.matmul(layer_1, weights_age['h2']), biases_age['b2'])
        layer_2 = tf.nn.relu(layer_2)
        # Output layer with linear activation
        out_layer = tf.matmul(layer_2, weights_age['out']) + biases_age['out']
        return out_layer
    weights_age = {
        'h1': tf.Variable(tf.random_normal([n_input, n_hidden_1_age])),
        'h2': tf.Variable(tf.random_normal([n_hidden_1_age, n_hidden_2_age])),
        'out': tf.Variable(tf.random_normal([n_hidden_2_age, n_classes_age]))
    }
    biases_age = {
        'b1': tf.Variable(tf.random_normal([n_hidden_1_age])),
        'b2': tf.Variable(tf.random_normal([n_hidden_2_age])),
        'out': tf.Variable(tf.random_normal([n_classes_age]))
    }
    pred_age = multilayer_perceptron_age(x_age, weights_age, biases_age)
    '''
    
    '''
    x_sex = tf.placeholder("float", [None, n_input])
    y_sex = tf.placeholder("float", [None, n_classes_sex])
    def multilayer_perceptron_sex(x_sex, weights_sex, biases_sex):
        # Hidden layer with RELU activation
        layer_1 = tf.add(tf.matmul(x_sex, weights_sex['h1']), biases_sex['b1'])
        layer_1 = tf.nn.relu(layer_1)
        # Hidden layer with RELU activation
        layer_2 = tf.add(tf.matmul(layer_1, weights_sex['h2']), biases_sex['b2'])
        layer_2 = tf.nn.relu(layer_2)
        # Output layer with linear activation
        out_layer = tf.matmul(layer_2, weights_sex['out']) + biases_sex['out']
        return out_layer
    weights_sex = {
        'h1': tf.Variable(tf.random_normal([n_input, n_hidden_1_sex])),
        'h2': tf.Variable(tf.random_normal([n_hidden_1_sex, n_hidden_2_sex])),
        'out': tf.Variable(tf.random_normal([n_hidden_2_sex, n_classes_sex]))
    }
    biases_sex = {
        'b1': tf.Variable(tf.random_normal([n_hidden_1_sex])),
        'b2': tf.Variable(tf.random_normal([n_hidden_2_sex])),
        'out': tf.Variable(tf.random_normal([n_classes_sex]))
    }‘’
    pred_sex = multilayer_perceptron_sex(x_sex, weights_sex, biases_sex)
    '''
    
    '''
    saver = tf.train.Saver()
    init = tf.global_variables_initializer()
    with tf.Session() as sess:
        saver.restore(sess, "./model.ckpt")
        print ("load model success!")
        p_sex = sess.run(pred_sex, feed_dict={x_sex: data_predict})
        p_age = sess.run(pred_age, feed_dict={x_age: data_predict})
    if p_sex[0][0] > p_sex[0][1]:
        sex_result = 1
    else:
        sex_result = 0
    age_result = p_age * 50 +50
    return sex_result,age_result

年龄预测数据流分析

训练阶段:

网络程序设计学习总结_第11张图片
  • csv格式的predicttrain转换为one-hot格式,提取年龄字段作为期望值
  • 输入train的一维向量乘以权值矩阵加偏置到隐层
  • 通过softmax回归处理得到输出层
  • 将最大概率节点与期望值求误差,要求|X - Y| <= 5
  • 如果误差大于5则求出交叉熵对wb求梯度并更新权值矩阵和偏置向量

预测阶段:

网络程序设计学习总结_第12张图片
  • 将csv格式的predict和train转换为one-hot格式,提取年龄字段作为期望值
  • 输入predict的一维向量乘以权值矩阵加偏置到隐层
  • 通过softmax回归处理得到输出层将最大概率节点与期望值求误差,要求|X - Y|<= 5
  • 如果误差大于5则判定失败

心得体会

虽然课程已经结束,但是对于机器学习和神经网络的学习还在继续。我所理解的机器学习就是将强大的计算能力转化为对象的分类预测,从而具有了一定的智能。相信随着该学科的完善,机器学习会更快的普及到我们的生活中,为人类社会的发展提供更强大的动力。

你可能感兴趣的:(网络程序设计学习总结)