网络程序设计考察报告
{修改后前言}
这篇总结最早发表是在2016年12月底,时至今年8月课程才敢修改。因为:
第一,这是上课要求的作业,不得不实名。
第二,文中极尽谄媚之词也是为了分数为之,我自己也恶心无极。思量再三,觉得还是留下不删为好,一来提供证据,二来警示自己。
最后,请各位后来者看到引以为戒,不要选孟某人任何课程。
{课程内容}
基于深度学习神经网络等机器学习技术实现一个医学辅助诊断的专家系统原型,具体切入点为课程项目:对血常规检验报告的OCR识别、深度学习与分析。
网络程序设计课程主页
网络程序设计项目主页
{知识准备}
深度学习入门——神经网络实现手写字符识别系统
三步走感受深度学习
机器学习的过程很像一个寻找转换函数的过程。
我们可以将这个过程抽象为以下三个步骤:
第一步:寻找多个方法
第二步:评估方法性能
第三步:确定最优方法
深度学习进阶——血常规检验报告的图像OCR识别和年龄性别预测
Web 端上传图片到服务器,存入mongodb并获取oid; 前端采用了vue.js, mvvm模式
读取配置文件
app.config.from_object('config')
连接数据库,并获取数据库对象
db = MongoClient(app.config['DB_HOST'], app.config['DB_PORT']).test
将矫正后图片与图片识别结果(JSON)存入数据库
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']
对图像透视裁剪和OCR进行了简单的封装,以便于模块间的交互,规定适当的接口
ImageFilter初始化,可以传入一个opencv格式打开的图片,也可以传入一个图片的路径,二选一
def __init__(self, image, imagepath='origin_pics/bloodtestreport2.jpg'):
self.img = image
if image is None:
print 'img init from',imagepath
self.img = cv2.imread(imagepath)
self.PerspectiveImg = None
#设置输出路径,创建目录
self.output_path = 'temp_pics/'
if not(os.path.exists(self.output_path)):
os.makedirs(self.output_path)
perspect函数用于透视image,他会缓存一个透视后的opencv numpy矩阵,并返回该矩阵
透视失败,则会返回None,并打印不是报告
@param 透视参数
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])
filter函数返回img经过透视过后的PIL格式的Image对象,如果缓存中有PerspectivImg则直接使用,没有先进行透视
过滤失败则返回None
@param filter参数
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)
autocut函数用于剪切ImageFilter中的img成员,剪切之后临时图片保存在out_path,
如果剪切失败,返回-1,成功返回0
@num 剪切项目数
@param 剪切参数
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
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
用于判定裁剪矫正后的报告和裁剪出检测项目的编号
# 判断是否血常规检验报告,输入经过矫正后的报告图像
def isReport(img):
# add your code here
image = Image.open(os.getcwd() + ‘/origin_pics/region.jpg’)
rate=pHash.classify_DCT(image,img)/64.0
if(rate>0.6):
return True
else:
return False
# 根据剪裁好的项目名称图片获得该项目的分类号,注意不是检验报告上的编号,是我们存储的编号
num = 0
def getItemNum(img):
# replace your code
global num
if num >= 22:
num = 0
ret = num
num = num + 1
return ret
{修习成果}
WEB系统——血常规检验报告的图像OCR识别与年龄性别预测
版本库
安装方法
安装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
运行方法
cd BloodTestReportOCR
python view.py # upload图像,在浏览器打开http://0.0.0.0:8080
Demo截图
- 访问主页 http://0.0.0.0:8080
- 提交血常规化验单
- 识别并生成报告
- 性别与年龄预测
{心得体会}
“河北人马,如此雄壮!”
“以吾观之,如土鸡瓦犬耳!”
这一段对话出自《三国演义》,是曹操和关公各自对袁绍军仪发表的看法。如今再读,却有些与现实暗暗相合的感觉来。似我这般初次接触机器学习的,自然要对神经网络晦涩的概念和冗杂的数学公式嗟悼一番,叹息出“如此雄壮”这种无可奈何的无力感来,同时又对那些能发出“土鸡瓦犬”这等豪言壮语的英雄们倾慕无极。
但打仗终究不是一个人的事情。英雄们自可以在百万人中取上将首级,如我之辈亦不能妄自菲薄,消灭一两个雄壮的人马,也是为取得胜利做出了贡献。而随着时间和经验的积累,不求能将麾盖之下绣袍金甲的颜良看做插标卖首者,得以把如此雄壮的河北人马视为土鸡瓦犬也不失为一种进步了。
网络程序设计这一课程的学习也是如此。这里向为本课程做出贡献的各路豪杰致以崇高敬意,没有他们的例子妄图跟上课程进度,所花费的时间只怕三国都能统一三次了(玩笑话)。但并不能因为能跑通现成的项目就心安理得或者沾沾自喜,以为掌握了这门技术;而应该仔细研读,以求温故知新,创出自己的版本来,这样才能达到这门课该有的效果。想必这也是孟宁老师用心良苦所希望的。
第一次选修这样独具匠心的课尚属首次,这种别开生面的授课方式也给我带来了以往所收获不到的惊喜。摒除了其他课程一言堂式的教学方式,每个同学可以自由而全面的发展,并能够感受不同思想的碰撞,颇有一种百家争鸣的盛世场面。大家各自繁荣,还能各取所长,尤其在神经网络的框架中,不仅有tensorflow的尝试,还有caffe、keras等不同形式共同发展的局面。但各自发展又不是任意的,这个时候就体现出老师引导的重要性:什么时候该做尝试,什么时候该做取舍,对整个项目的进展有着举足轻重的作用。毫无疑问,孟宁老师始终发挥着把控全局的作用:不至于分崩离析,又能够遍地开花;既能保证项目的前进方向,也能兼顾不同版本的依次更新。将艺高人胆大诠释的淋漓尽致。
然而分别终究是要来到了。但是课程的结束并不意味着工程的止步,深度学习的魅力和一个多月的成果,将推动我们朝着更高的要求前进。希望在以后的工作中,我们能够怀揣着网络程序设计带来的理念和精神,在深度学习的领域中乘万里风,破万里浪!