对血常规检验报告的OCR识别、深度学习与分析
项目背景
2016是人工智能爆发的一年,各种层出不穷的新技术、新概念让人眼花缭乱。医疗保健领域中的大数据在改善患者护理以及最终实现合理成本方面具有巨大的潜力。大数据时代的出现给统计机器学习提供了更多的机遇,因为海量的数据更加需要统计、抽样的方法。
本学习孟宁老师的网络程序设计课程内容为基于神经网络的医疗辅助诊断专家系统设计,我和同学们都满怀兴趣和激情加入了课程的学习。同学们在分享讨论中,对机器学习的相关算法、图片和数据的处理方法都进行了学习和了解,这样的学习方式是高效且充满意义的。同时,对课程项目内容也不断地充实、完善,在此进行回顾和总结。
项目目标
能够将手机拍摄的血常规检验报告图片预处理,并且实现图像识别,将检验结果输入到神经网络,进行性别和年龄的预测。对项目进行展望,可以通过更加丰富的检验数据,基于更加完善的深度学习模型,对对象的健康状况给出预测。
课程中的贡献与学习成果
课程贡献:
与同学们分享了机器学习中的决策树算法,并进行了具体应用的演示。
学习成果:
(1)通过参与同学们的分享和讨论,对机器学习的方法有了基础地学习和认识。学习了常见的机器学习算法(SVM、随机森林、决策树、感知机、Adaboost等)的原理和特性。感受到了opencv在图像预处理方面的强大功能,学习了本项目中对图片进行处理的方法原理,掌握了利用ocr方法进行图像文本识别的方法。
(2)在对神经网络的学习中,掌握了BP神经网络的算法原理。通过同学们的分享交流,对深度学习的常用框架caffe、tensorflow、MxNet等有了更进一步的了解。其中,对caffe框架进行了较为深入的研究,受到启发,在工程实践项目中采用caffe实现了图像多目标检测任务。
(3)在认真聆听同学们对各种算法的介绍中,开拓了眼界,能够主动思考比较各类算法和框架的有点与不足,考虑何种算法更适合应用在何种场合,产生了对机器学习的浓厚兴趣。
(4)在学习中,能够与老师和同学们步调保持一致,逐步把项目完善。最终能够成功运行项目Demo,并在同学们的指导和帮助下,能够理解项目代码。
项目部署和Demo演示
项目地址:对血常规检验报告的OCR识别、深度学习与分析
安装numpy
$ sudo apt-get install Python-numpy
安装Opencv
$ sudo apt-get install python-opencv
安装pytesseract
$ sudo apt-get install tesseract-ocr
$ sudo pip install pytesseract
$ sudo apt-get install python-tk
$ sudo pip install pillow
安装Flask框架
$ sudo pip install Flask
安装mongodb
$ sudo apt-get install mongodb # 如果提示no modulename mongodb, 先执行sudo apt-get update
$ sudo service mongodb started
$ sudo pip install pymongo
安装Tensorflow
$ sudo apt-get install python-numpy
$ sudo apt-get install python-imaging
$ pip install --upgradehttps://storage.googleapis.com/tensorflow/Linux/cpu/tensorflow-0.12.0rc0-cp27-none-linux_x86_64.whl
运行
$ git clone https://git.coding.net/mengning/np2016.git
$ cd np2016
$ cd BloodTestReportOCR
$ python view.py # upload图像,在浏览器打开http://localhost:8080
访问 localhost:8080
选择报告单图片上传
点击生成报告
点击predict
设计分析
1.Web模块设计
采用flask框架,json部分用来传输数据,index.html为网页编辑
- <span style="font-size:14px;">app = Flask(__name__, static_url_path="")
-
- app.config.from_object('config')
-
- db = MongoClient(app.config['DB_HOST'], app.config['DB_PORT']).test
-
- def save_file(file_str, f, report_data):
- content = StringIO(file_str)
- try:
- mime = Image.open(content).format.lower()
- print 'content of mime is:', mime
- if mime not in app.config['ALLOWED_EXTENSIONS']:
- raise IOError()
- except IOError:
- abort(400)
- c = dict(report_data=report_data, content=bson.binary.Binary(content.getvalue()), filename=secure_filename(f.name),
- mime=mime)
- db.files.save(c)
- return c['_id'], c['filename']
- @app.route('/', methods=['GET', 'POST'])
- def index():
- return redirect('/index.html')</span>
2.opencv图片预处理,将检验报告图片进行预处理并切割,为ocr识别做准备
- <span style="font-size:14px;">import cv2
- def digitsimg(src):
-
-
- img_gray = cv2.cvtColor(src,cv2.COLOR_BGR2GRAY)
-
- ret,result= cv2.threshold(img_gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
-
- kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,2))
- eroded = cv2.erode(result,kernel)
-
- result = cv2.resize(result,(128,128),interpolation=cv2.INTER_CUBIC)
-
- eroded = cv2.erode(result,kernel)
-
- result = cv2.dilate(eroded,kernel)
-
- cv2.equalizeHist(result)
-
- result = cv2.medianBlur(result,5)
- return result</span>
3.perspect函数用于透视image,他会缓存一个透视后的opencv numpy矩阵,并返回该矩阵
- <span style="font-size:14px;"> 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)
-
- contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
- </span>
4. filter函数返回img经过透视过后的PIL格式的Image对象,如果缓存中有PerspectivImg则直接使用,没有先进行透视过滤失败则返回None
- <span style="font-size:14px;"> def filter(self, param=default):
- if self.PerspectiveImg is None:
- self.PerspectivImg = self.perspect(param)
- if self.PerspectiveImg is None:
- return None
- img = Image.open(self.output_path + 'region.jpg')
- if not(classifier.isReport(img)):
- 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)</span>
5. autocut函数用于剪切ImageFilter中的img成员,剪切之后临时图片保存在out_path,如果剪切失败,返回-1,成功返回0
- <span style="font-size:14px;"> 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</span>
6.ocr函数用于对img进行ocr识别,他会先进行剪切,之后进一步做ocr识别,返回一个json对象如果剪切失败,则返回None
- <span style="font-size:14px;"> 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
-
- 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</span>
切割后的数据:
利用TensorFlow框架进行模型训练和预测:
模型定义(以年龄属性为例)
- <span style="font-size:14px;"> 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):
-
- layer_1 = tf.add(tf.matmul(x_sex, weights_sex['h1']), biases_sex['b1'])
- layer_1 = tf.nn.relu(layer_1)
-
- layer_2 = tf.add(tf.matmul(layer_1, weights_sex['h2']), biases_sex['b2'])
- layer_2 = tf.nn.relu(layer_2)
-
- 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)</span>
模型训练
- <span style="font-size:14px;">
- sess.run(init_op)
-
- threads = tf.train.start_queue_runners(sess=sess)
-
- for i in range(10000):
-
- image, label = sess.run([img_batch, label_batch])
-
- if i % 100 == 0:
- train_accuracy = accuracy.eval(feed_dict={x: image, y_:dense_to_one_hot(label)})
- print("step %d, training accuracy %g" % (i, train_accuracy))
- result = sess.run(merged,feed_dict={x:image,y_:dense_to_one_hot(label)})
- writer.add_summary(result,i)
- train_step.run(feed_dict={x: image, y_: dense_to_one_hot(label)})
-
- test_img, test_label = sess.run([test_img_batch, test_label_batch])
-
- print("test accuracy %g" % accuracy.eval(feed_dict={x: test_img, y_: dense_to_one_hot(test_label)}))
-
- save_path = saver.save(sess, cwd + "/ckpt_sex/sex.ckpt", write_meta_graph=None)
- print("Model saved in file: %s" % save_path)</span>
训练后产生模型文件model.ckpt.data-00000-of-00001和model.ckpt.index
调用训练好的模型进行预测
- <span style="font-size:14px;">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})</span>
收获与感悟
在这门课的学习过程中,虽然对项目所涉及到的工具和方法还没有达到精通的程度,但是收获是巨大的。
首先,在与同学们的分享交流中,让我体会到了分享的快乐。在课程的开始阶段,我与同学们分享了我对机器学习算法中决策树的理解和应用,在分享准备过程中也加深了我对知识的掌握程度。在其他同学分享知识和项目进展的过程中,也让我收获了之前没有涉及到的方法,对于我在项目中遇到的疑问也能够得到很好解答。这样的学习方式比自己闭门造车高效许多。
其次,项目中基于opencv对图片预处理的过程令我感到很神奇,这是之前从未接触到的,不断地去了解新方法、新技术为将来的学习和开发提供了新的思路。
最重要的是,在课程学习中接触到了机器学习这个领域,在学习中收获了很多新的思路和方法,这也指导我在工程实践项目中取得突破和进步。使我认识到,机器学习是未来几年应用比较广泛的方法,也激起了我在这一领域深入研究的兴趣。计划利用寒假期间,从算法原理到代码实现对机器学习进行深入学习。