初次接触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)
-