《网络程序设计》学习总结

前言

《网络程序设计》的课程已经结束了,一如孟宁老师以往的风格,继高软的“kingke”后,老师给出了“np2016—— 血常规检验报告单的OCR识别、深度学习与分析”这样一个有意思的课题。在机器学习这个概念被炒得火热的当下,血常规检验报告单这样一个切入点很妙。血常规检验是我们每个人都会经历的事情,看病嘛,于每个人都是一件重要的事,所以这样一个医学角度的课题自然是有其应用意义的。

“中医药学就是深度学习神经网络训练出来的模型,只是这个训练过程经历了几千年,我们可能在做一件具有开拓意义的工作。”

孟宁老师是这样理解的。仔细一想我也觉得很有道理:中医古老的历史上,数不清的医者各自的行医活动不就是Machine Learning中的训练和预测么,数不清的医者构成了一个跨越时间空间界限的分布式系统,而更为浩繁的病人/病情就是数据集,如此想来,这个全人类意义层面上的ML项目已经跑了几千年。这一对比也佐证了血常规课题的实践意义

机器学习(Machine Learning)、深度学习(Deep Learning)、神经网络(Neural Networks)的关系

在我学习这门课程之前,这三个名词都有所耳闻。当然由于本科不是计算机专业,我的耳闻也来得很晚......大四下学期,也就是16年年初的Google“机器人”AlphaGo对弈韩国围棋棋手李世乭九段,AlphaGo以4:1轻松取得这场五番棋的胜利,我在看人机对弈的直播时,完全被AlphaGo的棋力或者说计算力震撼到了,这才了解到它是机器学习的产物。要知道在此之前,我的课程都是雷达信号,天线电波之类,AI发展到如此程度着实让我长了见识。在这之后,我更加频繁地听到机器学习、深度学习、神经网络这些概念,然而确始终没有理清它们之间的界限。

在经过半个学期《网络程序设计》的学习过后,我想我对这三个概念有了一定的认识。
机器学习是指用某些算法指导计算机利用已知数据得出适当的模型,并利用此模型对新的情境给出判断的过程。而神经网络是机器学习中的一类算法。深度学习则是神经网络算法的进化,其网络架构更加“深”,本质还是神经网络。

关于机器学习,我看到了这样一张interesting的图:

看到图的最后我真是醍醐灌顶茅塞顿开......0.o

课程项目学习历程

我的这篇学习总结的重点会放在这部分,由于确实是白的不能再白的小白,对一些知识的理解难念会有差错,希望读者能不吝指教!
项目传送门戳窝

A1 神经网络实现手写字符识别系统

BP神经网络
手写字符识别是我们拿来学习参考的一个教学课程,在这个部署运行这个项目的过程中,我第一次接触了神经网络。此项目是基于BP神经网络的,所谓BP,即Back Propagation(反向传播)。BP神经网络在很早就被提出了,其拓扑结构为一个输入层、一个输出层和若干个隐藏层:


上图是一个隐藏层的结构图,输入层和隐藏层都有若干个节点和一个偏置节点,每个输入节点与其对应权值参数的乘积之和加上偏置节点就等于下一层的某个节点的输入。
上面的公式便是前向传播的过程,其中函数f()是单极性Sigmoid函数,这相当于预测

  1989年Robert Hecht-Nielsen证明了对于任何闭区间内的一个连续函数都可以用一个隐含层的BP网络来逼近,这就是万能逼近定理。这个定理说明一个三层的BP网络就可以完成任意的m维到n维的映射。

然而我们上面的前向传播中,每个节点的权值参数的取值是不完美的,因此需要通过反向传播来进行修正参数。
反向传播调参的思路:对于我们要用BP网络模拟的函数(本项目中为分类器),有一个训练集,还有我们通过前向传播得到的预测值。通过预测值和训练集可以定义误差函数:


将误差函数对每层的参数(权值参数和偏置参数)求导,计算梯度,通过梯度下降法更新参数的值。由于简书编辑数学公式不便,加上这不是本文的重点,详细的BP算法推导可以参考这篇文章:反向传播神经网络极简入门(强烈推荐!)。

手写字符识别demo
这是一个别人已经写好的手写字符识别小项目,运用反向传播算法训练数据集实现一个0-9数字的分类器。


在画布上写了数字“2”

输出了预测“The neural network predicts you wrote a '2' ” ,这个小demo也侧面验证了BP算法的可行性。

A2 血常规检验报告的图像OCR识别

A2是我们项目的正式开始,这个环节的工作是做OCR(Optical Character Recognition),即光学字符识别。这是项目的第一步也是很重要的一步,通过OCR能从化验报告单的照片得到各项指标的编号以及其对应的数值。

我们最原始的数据就是下面这张血常规检验报告单:
对检验单的一系列处理

1. imageFilter.perspect()

通过一系列预处理以及透视变换对检验单图像做初步的矫正,便于下一步进行识别。这段代码是一位同学写的,看完代码觉得写得真好:

def perspect(self, param=default):
    #载入参数
    gb_param = param[0] #必须是奇数
    canny_param_upper = param[1]
    canny_param_lower = param[2]
    ref_lenth_multiplier = param[3]
    ref_close_multiplier = param[4]
    
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))
   
    # 载入图像,灰度化,开闭运算,描绘边缘
    img_sp = self.img.shape
    ref_lenth = img_sp[0] * img_sp[1] * ref_lenth_multiplier
    img_gray = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY)
    img_gb = cv2.GaussianBlur(img_gray, (gb_param, gb_param), 0)
    closed = cv2.morphologyEx(img_gb, cv2.MORPH_CLOSE, kernel)
    opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel)
    edges = cv2.Canny(opened, canny_param_lower , canny_param_upper)

    # 调用findContours提取轮廓
    contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

    def getbox(i):
        rect = cv2.minAreaRect(contours[i])
        box = cv2.cv.BoxPoints(rect)
        box = np.int0(box)
        return box

    def distance(box):
        delta1 = box[0]-box[2]
        delta2 = box[1]-box[3]
        distance1 = np.dot(delta1,delta1)
        distance2 = np.dot(delta2,delta2)
        distance_avg = (distance1 + distance2) / 2
        return distance_avg

    # 筛选出对角线足够大的几个轮廓
    found = []
    for i in range(len(contours)):
        box = getbox(i)
        distance_arr = distance(box)
        if distance_arr > ref_lenth:
            found.append([i, box])

    def getline(box):
        if np.dot(box[1]-box[2],box[1]-box[2]) < np.dot(box[0]-box[1],box[0]-box[1]):
            point1 = (box[1] + box[2]) / 2
            point2 = (box[3] + box[0]) / 2
            lenth = np.dot(point1-point2, point1-point2)
            return point1, point2, lenth
        else:
            point1 = (box[0] + box[1]) / 2
            point2 = (box[2] + box[3]) / 2
            lenth = np.dot(point1-point2, point1-point2)
            return point1, point2, lenth

    def cmp(p1, p2):
        delta = p1 - p2
        distance = np.dot(delta, delta)
        if distance < img_sp[0] * img_sp[1] * ref_close_multiplier:
            return 1
        else:
            return 0

    def linecmp(l1, l2):
        f_point1 = l1[0]
        f_point2 = l1[1]
        f_lenth = l1[2]
        b_point1 = l2[0]
        b_point2 = l2[1]
        b_lenth = l2[2]
        if cmp(f_point1,b_point1) or cmp(f_point1,b_point2) or cmp(f_point2,b_point1) or cmp(f_point2,b_point2):
            if f_lenth > b_lenth:
                return 1
            else:
                return -1
        else:
            return 0

    def deleteline(line, j):
        lenth = len(line)
        for i in range(lenth):
            if line[i] is j:
                del line[i]
                return

    # 将轮廓变为线
    line = []

    for i in found:
        box = i[1]
        point1, point2, lenth = getline(box)
        line.append([point1, point2, lenth])

    # 把不合适的线删去
    if len(line)>3:
        for i in line:
            for j in line:
                if i is not j:
                    rst = linecmp(i, j)
                    if rst > 0:
                        deleteline(line, j)
                    elif rst < 0:
                        deleteline(line, i)

    #检测出的线数量不对就返回-1跳出
    if len(line) != 3:
        print "it is not a is Report!,len(line) =",len(line)
        return None
    
    def distance_line(i, j):
        dis1 = np.dot(i[0]-j[0], i[0]-j[0])
        dis2 = np.dot(i[0]-j[1], i[0]-j[1])
        dis3 = np.dot(i[1]-j[0], i[1]-j[0])
        dis4 = np.dot(i[1]-j[1], i[1]-j[1])
        return min(dis1, dis2, dis3, dis4)

    def findhead(i, j, k):
        dis = []
        dis.append([distance_line(i, j), i, j])
        dis.append([distance_line(j, k), j, k])
        dis.append([distance_line(k, i), k, i])
        dis.sort()
        if dis[0][1] is dis[2][2]:
            return dis[0][1], dis[2][1]
        if dis[0][2] is dis[2][1]:
            return dis[0][2], dis[2][2]

    def cross(vector1, vector2):
        return vector1[0]*vector2[1]-vector1[1]*vector2[0]

    # 由三条线来确定表头的位置和表尾的位置
    line_upper, line_lower = findhead(line[2],line[1],line[0])

def detectmiss(line, line_lower, ref_angle):
    vector = []
    j = 0
    if linecmp(line[1], line_lower):
        j = 1
    elif linecmp(line[2], line_lower):
        j = 2

    lenth = len(line)
    for i in range(lenth):
        if i != j:
        vector.append([line[j][0]-line[i][0], line[j][1]-line[i][1]])
    vect1 = vector[0][0]
    vect2 = vector[0][1]
    vect3 = vector[1][0]
    vect4 = vector[1][1]
    angle1 = (math.acos(np.dot(vect3, vect1) / ((np.dot(vect1, vect1) ** 0.5) * (np.dot(vect3, vect3)**0.5))))/math.pi*180
    angle2 = (math.acos(np.dot(vect4, vect2) / ((np.dot(vect2, vect2) ** 0.5) * (np.dot(vect4, vect4)**0.5))))/math.pi*180
    if angle1 > ref_angle or angle2 > ref_angle:
        return 1
    return 0

# 通过计算夹角来检测是否有缺失一角的情况
ref_angle = 1
if detectmiss(line, line_lower, ref_angle):
    print "it is not a complete Report!"
    return None

    # 由表头和表尾确定目标区域的位置

    # 利用叉乘的不可交换性确定起始点
    total_width = line_upper[1]-line_upper[0]
    total_hight = line_lower[0]-line_upper[0]
    cross_prod = cross(total_width, total_hight)
    if cross_prod <0:
        temp = line_upper[1]
        line_upper[1] = line_upper[0]
        line_upper[0] = temp
        temp = line_lower[1]
        line_lower[1] = line_lower[0]
        line_lower[0] = temp

    #由于需要表格外的数据,所以变换区域需要再向上和向下延伸
    left_axis = line_lower[0] - line_upper[0]
    right_axis = line_lower[1] - line_upper[1]
    line_upper[0] = line_upper[0] - left_axis * 2 / 15
    line_upper[1] = line_upper[1] - right_axis * 2 / 15
    line_lower[0] = line_lower[0] + left_axis * 2 / 15
    line_lower[1] = line_lower[1] + right_axis * 2 / 15

    #设定透视变换的矩阵
    points = np.array([[line_upper[0][0], line_upper[0][1]], [line_upper[1][0], line_upper[1][1]], 
                    [line_lower[0][0], line_lower[0][1]], [line_lower[1][0], line_lower[1][1]]],np.float32)
    standard = np.array([[0,0], [1000, 0], [0, 760], [1000, 760]],np.float32)

    #使用透视变换将表格区域转换为一个1000*760的图
    PerspectiveMatrix = cv2.getPerspectiveTransform(points,standard)
    self.PerspectiveImg = cv2.warpPerspective(self.img, PerspectiveMatrix, (1000, 760))

    #输出透视变换后的图片
    cv2.imwrite(self.output_path + 'region.jpg', self.PerspectiveImg)
    return self.PerspectiveImg

这段代码使用了opencv2的库函数,简单分析一下上面的代码。
最开始定义了5个参数,由param[]传值。
ref_lenth_multiplierref_lenth的参数因子,ref_lenth = img_sp[0] x img_sp[1] x ref_lenth_multiplier(长x宽x因子),这在后面用来作为筛选轮廓的门限。同样地,ref_close_multiplier也是筛选参数。
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))定义矩形结构元素,腐蚀、膨胀、开闭运算等操作都是以kernel为基础。
gb_param是高斯模糊函数cv2.GaussianBlur()的参数,必须是正奇数。高斯模糊也叫做高斯平滑,其效果如下:


可以直观看出,高斯模糊的效果是让图片钝化。
闭运算closed和开运算opened的作用是获取图像中的主要图像,因为闭运算用来连接被误分为许多小块的对象,而开运算用于移除由图像噪音形成的斑点,所以先闭后开。
经过上述的灰度化、高斯模糊、闭开运算后,我们的原始检验单图像变成了这样:
与上面的原图对比,效果还是很明显的!

canny_param_upper,canny_param_lower是canny算法的俩个阈值参数,Canny函数的用法为cv2.Canny(img,canny_param_lower,canny_param_upper),其中第一个参数img为输入,必须是单通道灰度图。大阈值canny_param_upper用于检测图像中明显的边缘,但一般情况下检测的效果不会那么完美,边缘检测出来是断断续续的。所以这时候用较小的canny_param_lower用于将这些间断的边缘连接起来。调用canny函数描绘边缘得到:


cv2.findContours()函数用于提取图片中的轮廓。其参数cv2.RETR_TREE建立一个等级树结构的轮廓,cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标。返回值contours是轮廓,用numpy中的ndarray表示;hierarchy为contours对应的属性,是关于cv2.RETR_TREE的轮廓层次信息。调用findContours()函数提取轮廓:

getbox()中调用minAreaRect()函数构造轮廓的最小外接矩形,diatance()函数计算轮廓矩形的对角线长度用ref_lenth筛选:

getline()函数返回最小外接矩形的长,以及将该矩形对切的长线段的端点值,即将轮廓矩形变成线:

findhead()、distance_line()通过三条线的距离确定表头和表尾:三条线两两之间的距离的最大距离和最小距离都包含表头。确定了表头和表尾即可利用向量叉乘的有向性来唯一确定表的起始点:
只有在这种情况下时,横向量x纵向量才是大于0的。从而表的起始点得以确定。
确定表的范围后切割表格内容,cv2.warpPerspective()函数将表格内容通过透视变换标准化到水平平面:

以上便是imageFilter.perspect()函数的功能。

2. imageFilter.filter()

过滤掉不合格的或非报告图片:返回img经过透视过后的PIL格式的Image对象,如果缓存中有PerspectivImg则直接使用,没有先进行透视 过滤失败则返回None。

def filter(self, param=default):
    if self.PerspectiveImg is None:
        self.PerspectivImg = self.perspect(param)
        if self.PerspectiveImg is None:
            return None
    if not(classifier.isReport(self.PerspectiveImg)):
        print "it is not a is Report!",classifier.isReport(self.PerspectiveImg)
        return None
    try:
        Image.fromarray(self.PerspectivImg)
    except Exception:
        return None
    return Image.fromarray(self.PerspectivImg)

3. imageFilter.autocut()

** 将图片中性别、年龄、日期和各项目名称数据分别剪切出来**:用于剪切ImageFilter中的img成员,剪切之后临时图片保存在out_path, 如果剪切失败,返回-1,成功返回0。

def autocut(self, num, param=default):
    if self.PerspectiveImg is None:
        self.PerspectivImg = self.filter(param)
    # 仍然是空,说明不是报告
    if self.PerspectiveImg is None:
        return -1
   #输出年龄
    img_age = self.PerspectiveImg[15 : 70, 585 : 690]
    cv2.imwrite(self.output_path + 'age.jpg', img_age)
   #输出性别
    img_gender = self.PerspectiveImg[15 : 58, 365 : 420]
    cv2.imwrite(self.output_path + 'gender.jpg', img_gender)
 #输出时间
    img_time = self.PerspectiveImg[722 : 760, 430 : 630]
    cv2.imwrite(self.output_path + 'time.jpg', img_time)
  #转换后的图分辨率是已知的,所以直接从这个点开始读数据就可以了
    startpoint = [199, 132]
    vertical_lenth = 37
    lateral_lenth = 80
   def getobjname(i, x, y):
        region_roi = self.PerspectiveImg[y : y+vertical_lenth, x : x+170]
        filename = self.output_path + 'p' + str(i) + '.jpg'
        cv2.imwrite(filename, region_roi)
   def getobjdata(i, x, y):
        region_roi = self.PerspectiveImg[y : y+vertical_lenth, x : x+lateral_lenth]
        filename = self.output_path + 'data' + str(i) + '.jpg'
        cv2.imwrite(filename, region_roi)
   #输出图片
    if num <= 13 and num > 0:
        for i in range(num):
            getobjname(int(i), 25, startpoint[1])
            getobjdata(int(i), startpoint[0], startpoint[1])
            startpoint[1] = startpoint[1] + 40
    elif num > 13:
        for i in range(13):
            getobjname(int(i), 25, startpoint[1])
            getobjdata(int(i), startpoint[0], startpoint[1])
            startpoint[1] = startpoint[1] + 40
        startpoint = [700, 135]
        for i in range(num-13):
            getobjname(int(i+13), 535, startpoint[1])
            getobjdata(int(i+13), startpoint[0], startpoint[1])
            startpoint[1] = startpoint[1] + 40
   #正常结束返回0
    return 0

剪切出来的图片在BloodTestReportOCR/temp_pics/ 文件夹下。函数输出为data0.jpg,data1.jpg......等一系列图片,分别是白细胞计数,中性粒细胞记数等的数值的图片。


4. imageFilter.ocr()

模块主函数,返回识别数据:用于对img进行ocr识别,他会先进行剪切,之后进一步做ocr识别,返回一个json对象 如果剪切失败,则返回None
ocr()中还调用了classifier.getItemNum()imgproc.digitsimg():getItemNum()获取检测项目的编号,digitsimg()将被剪切的要被识别的图像进行处理二值化等操作,使其能被集成了pytesseract的image_to_string()函数更好地识别

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

json格式的数据:

经过上述诸多步骤,OCR的工作算是基本完成了。由一张血常规检验单照片解析出上面的数据,接下来就该进行神经网络的工作了。

A3 根据血常规检验的各项数据预测年龄和性别

要搭建神经网络来进行训练和预测可以基于很多库,比如TensorFlow、Spark、Keras、Caffe等。我选择的是基于TensorFlow进行训练预测。
在正式进行预测之前,先通过官方的mnist字符识别demo了解了TensorFlow网络,这是TensorFlow中文官方文档
数据集(性别、年龄、血常规各项指标信息):

tensorflow预测:tf_predict.py

将数据归一化

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

predict():设置神经网络的参数,学习步长、各层节点数等。
tf.placeholder()定义输入输出的占位符
weights_age/sexbiases_age/sex是每层各节点的权值参数和偏置参数
multilayer_perceptron_age()双隐层神经网络计算预测年龄。multilayer_perceptron_sex()同理。隐藏层激活函数使用RELU函数。
feed_dict是反馈字典,其哈希键为前面建立模型时定义的输入占位符。

 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[0][0] * 50 +20
    return sex_result,age_result

运行DEMO进行预测:

python view.py 运行web端:

提交图片生成电子报告单:

预测:

运行环境

安装numpy

sudo apt-get install python-numpy

安装opencv

sudo apt-get install python-opencv

安装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 service mongodb started
sudo pip install pymongo

配置完上述环境,从项目传送门戳窝git clone代码,即可将我们的项目跑起来。

个人贡献

说到我对本次项目做的贡献很尴尬。。我没能做出pr,也还没来得及上台做相关算法的ppt分享。自己的一些徒劳也不知道谈不谈的上苦劳,在OCR阶段尝试用cv做出点什么,但是没能搞出一个完整的可以集成到我们项目里的算法。要用不同DL框架开始搞预测的时候选的tensorflow,能跑mnist的demo,但是自己去重新写一个能用于我们项目的predict时,虽然参考了详细的tensorflow官方文档,还是没有搞出一个可以pr的成熟代码。

所以这次的项目一直处在学习其他同学贡献的代码状态,也让我受益良多!感谢其他同学的贡献!

一些疑问

1.在一次预测后换其他图片无法继续预测,alert()返回的值还是之前的预测值。不知道是index.html里的js部分出了问题还是tf_predict.py出了问题,尝试改了改没能解决问题。最后只在alert()后加了语句自动刷新页面来代替手动F5。。。。。
2.自己拍的报告单照片无法被识别


给报告单拍了十几张图,类似上图,都未能识别。开始觉得图中的2条笔迹对OCR产生了影响,但是笔迹比较短,应该会因其轮廓最小外接矩形的对角线长度小于阈值而被过滤掉。如上图,出现error:len(line)=4的图片下方的报告单边缘与背景颜色对比强烈,可能会影响被当做轮廓。然而在其他效果不同的照片中若控制了背景的色彩对比,还是会出现error:len(line)=1之类的报错。
此问题还有待解决。

学习心得

1.选《网络程序设计》之前对机器学习有着很大的好奇,这门课结束时,对何谓机器学习、何谓深度学习、何谓神经网络有了一定程度的掌握,这是我的收获。

2.尽管没能够直接对项目做出原创性的贡献,在测试代码调试代码的过程中自己也学会了一些方法。

3.孟老师在不到2个月的时间里向我们展示了一个项目从创意到形成一个较为成熟的雏形的过程,指导和引导我们协力去开发项目,这个课堂模式很新颖。

4.不少同学都分享了他们的代码贡献,从中我学到了不少的知识、想法以及姿势水平,收获不小!

5.有问题光拍脑袋不能解决问题,还是应该去查阅书籍、上网搜寻资料或者请教老师和同学。网上的资料很多,反而有点看花了眼,目前我还没能处理好这个问题,容易在浩繁的资源面前不知看哪个好,希望慢慢能改掉这个缺点。

6.之前没接触过python,通过这半学期的学习,逐渐对python有了一些了解,并且对python产生了兴趣,觉得python很轻巧灵活。最近买了python相关的书籍,虽然课程结束了,还得好好学学python。

7.接下来还会继续学习神经网络(机器学习),希望能将《网络程序设计》作为一个开始,后面能有更多的收获。

最后,感谢孟宁老师对我们的帮助,感谢同学们的分享_

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容