初次接触OCR技术,OCR技术在工业检测上有极大的用处,如工件上面得数字标号识别、印刷纸票识别、车牌识别、身份证号码识别等。但中文字体识别较难,如今百度OCR、谷歌tesseract等提供识别接口,可以取得较好的识别效果。
通过贾志刚老师的印刷字体识别课程和一些OpenCV函数的学习,用身份证号码识别检测一下所学知识。
主要步骤
具体实现:
1、透视变换
先检测身份证外轮廓,获取身份证四个角点,然后利用OpenCV中getPerspectiveTransform()函数获取变换矩阵,warpPerspective()函数透视变换。特别需要注意角点坐标的顺序必须按顺序一一对应,按顺序找到对应坐标0123分别是 左上,右上,右下,左下。
#轮廓检测
cnts,hierarchy = cv.findContours(edged, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# cnts = sorted(cnts, key = cv.contourArea, reverse = True)[:5]
for c in cnts:
peri = cv.arcLength(c,True)
approx = cv.approxPolyDP(c,0.02*peri,True) #轮廓多边形逼近,找到角点
if len(approx) == 4:
screenCnt = approx
#print(screenCnt)
break
cv.drawContours(gray, [screenCnt], -1, (0, 255, 0), 2)
pts = screenCnt.reshape(4, 2)
#print(a)
rect = np.zeros((4, 2), dtype = "float32")
# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下
# 计算左上,右下
s = pts.sum(axis = 1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
# 计算右上和左下
diff = np.diff(pts, axis = 1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
(tl, tr, br, bl) = rect
# 计算输入的w和h值
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
maxWidth = max(int(widthA), int(widthB))
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
maxHeight = max(int(heightA), int(heightB))
# 变换后对应坐标位置
dst = np.array([
[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]], dtype = "float32")
# 计算变换矩阵
M = cv.getPerspectiveTransform(rect, dst)
warped = cv.warpPerspective(img, M, (maxWidth, maxHeight))
2、文本粗定位
通过Canny边缘检测,提取文本边缘,但此时边缘之间是断开的,需要通过形态学膨胀操作,将边缘之间连接起来。
canny = cv.Canny(gray,60,255)
# 形态学操作
kernel = cv.getStructuringElement(cv.MORPH_RECT,(11,5))
dilation = cv.dilate(canny,kernel,iterations = 1)
3、身份证号码ROI分割
观察到身份证号码区域的长度是最长的,因此可以通过轮廓检测,用boungdingRect()函数计算各轮廓的长宽值,设定合适阈值,找到ROI区域的轮廓进行分割。
#轮廓检测
cnts,hiri = cv.findContours(dilation, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
for c in cnts:
x, y, w, h = cv.boundingRect(c)
if w < 250:
continue
cv.rectangle(gray, (x,y), (x+w,y+h), (255,0,0), 2)
dst = src[y-2:y + h, x:x + w]
4、字符分割与排序
因为数字字符是连接的,字符之间有间隙,可以直接使用轮廓提取,因此可以以此为据,分割字符。轮廓的检测不能保证是有序的,通过轮廓的X坐标进行排序,字符排序保证以正确顺序依次进行识别。
# 字符分割
contours, hireachy = cv.findContours(canny1, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
#获取字符
rois = []
for c in range(len(contours)):
box = cv.boundingRect(contours[c])
if box[3] < 10:
continue
rois.append(box)
# 字符排序
num = len(rois)
for i in range(num):
for j in range(i+1, num, 1):
x1, y1, w1, h1 = rois[i]
x2, y2, w2, h2 = rois[j]
if x2 < x1:
temp = rois[j]
rois[j] = rois[i]
rois[i] = temp
5、识别模型训练
样本的准确采集与最后的结果准确度息息相关,将上述分割的字符进行样本拓展,因为数字像素特征比较明显,因此将像素特征作为输入,采用OpenCV封装的SVM网络进行训练与识别。
# 获取数据
train_data, train_labels = load_data()
# 网络构建
svm = cv.ml.SVM_create()
svm.setKernel(cv.ml.SVM_LINEAR)
svm.setType(cv.ml.SVM_C_SVC)
svm.setC(2.67)
svm.setGamma(5.383)
svm.train(train_data, cv.ml.ROW_SAMPLE, train_labels)
svm.save("svm_data.yml")
svm = cv.ml.SVM_load("svm_data.yml")
result = svm.predict(train_data)[1]
print(result)
6、身份证号码识别
#数字识别
svm = cv.ml.SVM_load("svm_data.yml")
result = svm.predict(digit_data)[1]
text = ""
for i in range(len(result)):
text += str(np.int32(result[i][0]))
print(text)