def img_show(name, img):
cv2.imshow(name, img) # 图像显示,第一个变量为窗口名称
cv2.waitKey(0) # 等待时间,0表示任意键终止
cv2.destroyAllWindows() # 关闭所有窗口
class IDNumber(object):
def __init__(self, tem_file_name):
self.template = self.get_tem_nums(tem_file_name)
ID = self.get_img_num('E:/RL/ID_identify/3.jpg')
ID_number = self.identify(ID)
print(ID_number)
# 规定w宽度,保证原比例不变放缩图像
def change_size(self, img, aim_w=512):
h, w = img.shape[:2]
ratio = h/w
aim_h = int(aim_w * h / w)
new_img = cv2.resize(img, (aim_w, aim_h))
return new_img
# 计算轮廓最小外界矩形面积
def contourArea(self, cnt):
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int64(box)
return cv2.contourArea(box)
# 获得身份证ID图像
def get_img_num(self, img_file):
kernel_3 = np.ones((3, 3))
kernel_5 = np.ones((5, 5))
img = cv2.imread(img_file)
img = self.change_size(img, aim_w=800)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 提取身份证位置
sobel = self.gradient(gray_img) # 图像梯度计算
sobel = cv2.threshold(sobel, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
sobel = cv2.morphologyEx(sobel, cv2.MORPH_CLOSE, kernel_3, iterations=2)
sobel = cv2.morphologyEx(sobel, cv2.MORPH_CLOSE, kernel_5, iterations=2)
contours, hierarchy = cv2.findContours(sobel, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # 轮廓检测
contour = sorted(contours, key=self.contourArea, reverse=True)[0] # 寻找最大面积的轮廓即身份证
# 轮廓近似为四边形
epsilon = 0.02 * cv2.arcLength(contour, True) # 通常间隔阈值是通过周长的百分比进行比较
approx = cv2.approxPolyDP(contour, epsilon, True) # 轮廓近似
aim_img = self.transform(img, approx) # 透视变换,将身份证图片放正
aim_img = self.change_size(aim_img, aim_w=500)
aim_gray_img = cv2.cvtColor(aim_img, cv2.COLOR_BGR2GRAY)
img_show('a', aim_gray_img)
aim_gray_img = cv2.medianBlur(aim_gray_img, 3)
# 找到身份证号码轮廓
aim_gray_img = cv2.morphologyEx(aim_gray_img, cv2.MORPH_BLACKHAT, kernel_5, iterations=2)
aim_gray_img = cv2.threshold(aim_gray_img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
aim_gray_img = cv2.morphologyEx(aim_gray_img, cv2.MORPH_CLOSE, kernel_5, iterations=2)
aim_gray_img = cv2.dilate(aim_gray_img, kernel_3, iterations=3)
contours, hierarchy = cv2.findContours(aim_gray_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 根据身份证号码长宽比确定位置轮廓
area = []
loc = []
loc_contours = []
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
if 6 <= (w / h) <= 13:
loc_contours.append(cnt)
area.append(self.contourArea(cnt))
loc.append([x, y, w, h])
[x, y, w, h] = loc[np.argmax(area)]
contour = loc_contours[np.argmax(area)]
# 利用最小外界矩形,加透视变换,将身份证号码放正
rect = cv2.minAreaRect(contour)
box = cv2.boxPoints(rect)
box = np.int0(box)
box = [[box_loc] for box_loc in box]
ID_img = self.transform(aim_img, box)
# 提取身份证号码位置图像
ID_img = self.change_size(ID_img, aim_w=400)
ID_gray_img = cv2.cvtColor(ID_img, cv2.COLOR_BGR2GRAY)
ID_gray_img = cv2.threshold(ID_gray_img, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
img_show('a', ID_gray_img)
contours, hierarchy = cv2.findContours(ID_gray_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# res = cv2.drawContours(ID_img.copy(), contours, -1, (0, 0, 255), 2)
# img_show('a', res)
ID = []
location_x = []
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
if w * h > 25:
location_x.append(x)
loc = ID_img[y:y + h, x:x + w]
loc = cv2.resize(loc, (100, 150), interpolation=cv2.INTER_LINEAR)
loc = cv2.cvtColor(loc, cv2.COLOR_BGR2GRAY)
loc = cv2.threshold(loc, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
ID.append(loc)
ID = np.array(ID)
ID = ID[np.argsort(location_x)] # 根据数字x坐标排序身份证图像
return ID
# 图像梯度计算
def gradient(self, gray_img, weight_x=0.5, weight_y=0.5):
if (weight_x+weight_y) != 1:
weight_x = weight_x / (weight_x + weight_y)
weight_y = weight_y / (weight_x + weight_y)
sobelx = cv2.Scharr(gray_img, cv2.CV_64F, 1, 0) # X方向上的梯度,此时数值存在正负
sobelx = cv2.convertScaleAbs(sobelx) # 对其进行绝对值操作,否则只能显示出白减黑的梯度
(minval, maxval) = (np.min(sobelx), np.max(sobelx))
sobelx = (255 * ((sobelx - minval) / (maxval - minval)))
sobelx = sobelx.astype('uint8')
sobely = cv2.Scharr(gray_img, cv2.CV_64F, 0, 1) # y方向上的梯度,此时数值存在正负
sobely = cv2.convertScaleAbs(sobely) # 对其进行绝对值操作,否则只能显示出白减黑的梯度
(minval, maxval) = (np.min(sobely), np.max(sobely))
sobely = (255 * ((sobely - minval) / (maxval - minval)))
sobely = sobely.astype('uint8')
sobel = cv2.addWeighted(sobelx, weight_x, sobely, weight_y, 0)
return sobel
# 获得0-X模板图像
def get_tem_nums(self, file_name):
template_img = cv2.imread(file_name)
template_img_gray = cv2.cvtColor(template_img, cv2.COLOR_BGR2GRAY)
template_img_gray = cv2.threshold(template_img_gray, 128, 255, cv2.THRESH_BINARY_INV)[1] # 获得二值化标签图像
contours, hierarchy = cv2.findContours(template_img_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 获得轮廓信息
contours = sorted(contours, key=self.contourArea, reverse=True)[:11] # 获取面积前11的轮廓,即0-X
template_nums = np.array([cv2.boundingRect(cnt) for cnt in contours]) # 0-X的(x,y,w,h)
template_nums_x = [template_num[0] for template_num in template_nums] # 各个数字的X坐标值
template_nums = template_nums[np.argsort(template_nums_x)] # 将0-X按顺序排序
template_num_img = [] # 将0-X标签图像存入
for [x, y, w, h] in template_nums:
loc = template_img_gray[y:y+h, x:x+w]
loc = cv2.resize(loc, (100, 150))
template_num_img.append(loc)
return template_num_img
# 将数字图像与模板进行匹配
def identify(self, ID_imgs):
ID = str() # 身份证数字
for id_img in ID_imgs:
scores = []
# 匹配各个模板,取得分最高的一项
for tem in self.template:
result = cv2.matchTemplate(id_img, tem, cv2.TM_CCOEFF)
_, score, _, _ = cv2.minMaxLoc(result)
scores.append(score)
num = np.argmax(scores)
if num == 10:
num = 'X'
else:
num = str(num)
ID += num
return ID
# 透视变换,将图片根据四坐标点摆正
def transform(self, img, points):
new_points = []
new_points_y = []
new_points_x = []
for point in points:
new_points.append(point[0].tolist())
new_points_x.append(point[0][0].tolist())
new_points_y.append(point[0][1].tolist())
top_points = [new_points[i] for i in np.argsort(new_points_y)[:2]]
left_points = [new_points[i] for i in np.argsort(new_points_x)[:2]]
for top_point in top_points:
if top_point in left_points:
tl = top_point
new_points.remove(tl)
top_points.remove(tl)
tr = top_points[0]
new_points.remove(tr)
left_points.remove(tl)
bl = left_points[0]
new_points.remove(bl)
br = new_points[0]
widthA = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
widthB = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
maxWidth = max(int(widthA), int(widthB))
heightA = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
heightB = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
maxHeight = max(int(heightA), int(heightB))
points = np.array([tl, tr, br, bl], dtype='float32')
changed_points = np.array([[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]], dtype='float32')
M = cv2.getPerspectiveTransform(points, changed_points) # 计算变换矩阵
warped = cv2.warpPerspective(img, M, (maxWidth, maxHeight)) # 将图片摆正
return warped
a = IDNumber('E:/RL/ID_identify/number_ref.jpg')
参考视频:唐宇迪opencv P36-P34