|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | + |
| 4 | +# @Time : 2017/10/14 0014 上午 10:18 |
| 5 | +# @Author : 李雪洋 |
| 6 | +# @File : 04.py |
| 7 | +# @Software: PyCharm |
| 8 | + |
| 9 | +import cv2 |
| 10 | +import numpy as np |
| 11 | + |
| 12 | + |
| 13 | +# 获取检测到的最小矩形坐标 |
| 14 | +def getRectangularCoordinate(box): |
| 15 | + ys = [box[0, 1], box[1, 1], box[2, 1], box[3, 1]] |
| 16 | + xs = [box[0, 0], box[1, 0], box[2, 0], box[3, 0]] |
| 17 | + ys_sorted_index = np.argsort(ys) |
| 18 | + xs_sorted_index = np.argsort(xs) |
| 19 | + |
| 20 | + x1 = box[xs_sorted_index[0], 0] |
| 21 | + x2 = box[xs_sorted_index[3], 0] |
| 22 | + |
| 23 | + y1 = box[ys_sorted_index[0], 1] |
| 24 | + y2 = box[ys_sorted_index[3], 1] |
| 25 | + |
| 26 | + return [x1, x2, y1, y2] |
| 27 | + |
| 28 | + |
| 29 | +def findPlateNumberRegion2(img): |
| 30 | + region = [] |
| 31 | + # 查找轮廓 |
| 32 | + (_, contours, _) = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) |
| 33 | + |
| 34 | + # 筛选面积小的 |
| 35 | + for i in contours: |
| 36 | + # 计算该轮廓的面积 |
| 37 | + area = cv2.contourArea(i) |
| 38 | + |
| 39 | + # 面积小的都筛选掉 |
| 40 | + if area < 70: |
| 41 | + continue |
| 42 | + |
| 43 | + # 找到最小的矩形,该矩形可能有方向 |
| 44 | + rect = cv2.minAreaRect(i) |
| 45 | + print("rect is: " + str(rect)) |
| 46 | + |
| 47 | + # box是四个点的坐标 |
| 48 | + box = cv2.boxPoints(rect) |
| 49 | + box = np.int0(box) |
| 50 | + |
| 51 | + print("box:" + str(box)) |
| 52 | + |
| 53 | + # 计算高和宽 |
| 54 | + height = abs(box[0][1] - box[2][1]) |
| 55 | + width = abs(box[0][0] - box[2][0]) |
| 56 | + # 身份证号码正常情况下长高比在6.8-7.8之间 |
| 57 | + ratio = float(width) / float(height) |
| 58 | + print("ratio:" + str(ratio)) |
| 59 | + if 0 < ratio < 1: |
| 60 | + region.append(box) |
| 61 | + # 该列表为无序列表,要想得到正确的身份证顺序,需要对其进行排序,排序的规则就是以轮廓的左上角x坐标为准 |
| 62 | + return sorted(region, key=lambda a_list: a_list[1, 0]) |
| 63 | + |
| 64 | + |
| 65 | +def findPlateNumberRegion(img, large, small): |
| 66 | + region = [] |
| 67 | + # 查找轮廓 |
| 68 | + (_, contours, _) = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) |
| 69 | + |
| 70 | + # 筛选面积小的 |
| 71 | + for i in range(len(contours)): |
| 72 | + cnt = contours[i] |
| 73 | + # 计算该轮廓的面积 |
| 74 | + area = cv2.contourArea(cnt) |
| 75 | + # |
| 76 | + # 面积小的都筛选掉 |
| 77 | + # if area < 2000: |
| 78 | + # continue |
| 79 | + |
| 80 | + # 找到最小的矩形,该矩形可能有方向 |
| 81 | + rect = cv2.minAreaRect(cnt) |
| 82 | + print("rect is: " + str(rect)) |
| 83 | + |
| 84 | + # box是四个点的坐标 |
| 85 | + box = cv2.boxPoints(rect) |
| 86 | + box = np.int0(box) |
| 87 | + |
| 88 | + print("box:" + str(box)) |
| 89 | + |
| 90 | + # 计算高和宽 |
| 91 | + height = abs(box[0][1] - box[2][1]) |
| 92 | + width = abs(box[0][0] - box[2][0]) |
| 93 | + # 身份证号码正常情况下长高比在6.8-7.8之间 |
| 94 | + ratio = float(width) / float(height) |
| 95 | + print("ratio:" + str(ratio)) |
| 96 | + if small < ratio < large: |
| 97 | + return box, rect[2] |
| 98 | + |
| 99 | + |
| 100 | +def getEffectiveArea(img, srcImg): |
| 101 | + [ret3, th3] = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) |
| 102 | + cv2.imshow('th3', th3) |
| 103 | + |
| 104 | + kernel = np.ones((10, 10), np.uint8) |
| 105 | + erosion = cv2.erode(th3, kernel, iterations=1) |
| 106 | + # cv2.imshow('th3-erosion', erosion) |
| 107 | + |
| 108 | + kernel = np.ones((10, 10), np.uint8) |
| 109 | + dilation = cv2.dilate(th3, kernel, iterations=1) |
| 110 | + # cv2.imshow('th3-dilation', dilation) |
| 111 | + |
| 112 | + box, angel = findPlateNumberRegion(dilation, 2, 1) |
| 113 | + |
| 114 | + rows, cols, _ = srcImg.shape |
| 115 | + # 这里的第一个参数为旋转中心,第二个为旋转角度,第三个为旋转后的缩放因子 |
| 116 | + # 可以通过设置旋转中心,缩放因子,以及窗口大小来防止旋转后超出边界的问题 |
| 117 | + M = cv2.getRotationMatrix2D((cols / 2, rows / 2), angel, 1) |
| 118 | + # 第三个参数是输出图像的尺寸中心 |
| 119 | + srcImg = cv2.warpAffine(srcImg, M, (cols, rows)) |
| 120 | + dilation = cv2.warpAffine(dilation, M, (cols, rows)) |
| 121 | + img = cv2.warpAffine(img, M, (cols, rows)) |
| 122 | + |
| 123 | + box, angel = findPlateNumberRegion(dilation, 2, 1) |
| 124 | + |
| 125 | + cv2.drawContours(srcImg, [box], 0, (0, 255, 0), 2) |
| 126 | + |
| 127 | + coordinate = getRectangularCoordinate(box) |
| 128 | + |
| 129 | + img_org2 = srcImg.copy() |
| 130 | + |
| 131 | + img_plate = img_org2[coordinate[2]:coordinate[3], coordinate[0]:coordinate[1]] |
| 132 | + img = img[coordinate[2]:coordinate[3], coordinate[0]:coordinate[1]] |
| 133 | + cv2.imshow('number plate', img_plate) |
| 134 | + cv2.waitKey(0) |
| 135 | + |
| 136 | + # (530, 340)为正常可辨识范围取的基本值,可浮动 |
| 137 | + img = cv2.resize(img, (530, 340), interpolation=cv2.INTER_CUBIC) |
| 138 | + img_plate = cv2.resize(img_plate, (530, 340), interpolation=cv2.INTER_CUBIC) |
| 139 | + |
| 140 | + cv2.imshow('img_plate', img_plate) |
| 141 | + cv2.imshow('img-00', img) |
| 142 | + cv2.waitKey(0) |
| 143 | + |
| 144 | + return img, img_plate |
| 145 | + |
| 146 | + |
| 147 | +img = cv2.imread('./img/01.bmp') |
| 148 | +cv2.imshow('src-img', img) |
| 149 | + |
| 150 | +gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) |
| 151 | + |
| 152 | +blur = cv2.GaussianBlur(gray, (5, 5), 0) |
| 153 | + |
| 154 | +########################取有效区域######################## |
| 155 | +effectiveArea, srcImg = getEffectiveArea(blur, img) |
| 156 | +########################取有效区域######################## |
| 157 | + |
| 158 | +# 阈值一定要设为 0! |
| 159 | +ret3, th3 = cv2.threshold(effectiveArea, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) |
| 160 | +cv2.imshow('th3', th3) |
| 161 | + |
| 162 | +# 腐蚀操作 |
| 163 | +# 去掉部分噪点 |
| 164 | +kernel = np.ones((3, 3), np.uint8) |
| 165 | +erosion = cv2.erode(th3, kernel, iterations=1) |
| 166 | +cv2.imshow('th3-erosion', erosion) |
| 167 | + |
| 168 | +# 膨胀的核函数(9, 7) |
| 169 | +# element2 = cv2.getStructuringElement(cv2.MORPH_RECT, (12, 15)) |
| 170 | +# 膨胀一次,让轮廓突出 |
| 171 | +kernel = np.ones((15, 15), np.uint8) |
| 172 | +dilation = cv2.dilate(erosion, kernel, iterations=1) |
| 173 | +cv2.imshow('th3-dilation', dilation) |
| 174 | +cv2.waitKey(0) |
| 175 | + |
| 176 | +box, angel = findPlateNumberRegion(dilation, 10, 6) |
| 177 | + |
| 178 | +# cv2.drawContours(srcImg, [box], 0, (0, 255, 0), 2) |
| 179 | + |
| 180 | +coordinate = getRectangularCoordinate(box) |
| 181 | + |
| 182 | +img_org2 = srcImg.copy() |
| 183 | + |
| 184 | +img_plate = img_org2[coordinate[2]:coordinate[3], coordinate[0]:coordinate[1]] |
| 185 | +cv2.imshow('number plate', img_plate) |
| 186 | + |
| 187 | +rows, cols, _ = img_plate.shape |
| 188 | +# 这里的第一个参数为旋转中心,第二个为旋转角度,第三个为旋转后的缩放因子 |
| 189 | +# 可以通过设置旋转中心,缩放因子,以及窗口大小来防止旋转后超出边界的问题 |
| 190 | +M = cv2.getRotationMatrix2D((cols / 2, rows / 2), angel, 1) |
| 191 | +# 第三个参数是输出图像的尺寸中心 |
| 192 | +dst = cv2.warpAffine(img_plate, M, (cols, rows)) |
| 193 | + |
| 194 | +cv2.imshow('dst', dst) |
| 195 | +cv2.waitKey(0) |
| 196 | + |
| 197 | +gray = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY) |
| 198 | + |
| 199 | +blur = cv2.GaussianBlur(gray, (5, 5), 0) |
| 200 | + |
| 201 | +ret3, th3 = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) |
| 202 | + |
| 203 | +cv2.imshow('th3-eff', th3) |
| 204 | +cv2.waitKey(0) |
| 205 | + |
| 206 | +kernel = np.ones((4, 3), np.uint8) |
| 207 | +dilation = cv2.dilate(th3, kernel, iterations=1) |
| 208 | + |
| 209 | +cv2.imshow('end-dilation', dilation) |
| 210 | +cv2.waitKey(0) |
| 211 | + |
| 212 | +# 分割字符 |
| 213 | +boxList = findPlateNumberRegion2(dilation) |
| 214 | + |
| 215 | +imgList = [] |
| 216 | +for box in boxList: |
| 217 | + cv2.drawContours(dst, [box], 0, (0, 255, 0), 1) |
| 218 | + coordinate = getRectangularCoordinate(box) |
| 219 | + img_org2 = dst.copy() |
| 220 | + img_plate = img_org2[coordinate[2]:coordinate[3], coordinate[0]:coordinate[1]] |
| 221 | + imgList.append(img_plate) |
| 222 | + |
| 223 | +cv2.imshow('endImg', dst) |
| 224 | +cv2.waitKey(0) |
| 225 | + |
| 226 | +i = 0 |
| 227 | +for img in imgList: |
| 228 | + h, w, _ = img.shape |
| 229 | + img = cv2.resize(img, (w * 10, h * 10), interpolation=cv2.INTER_CUBIC) |
| 230 | + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) |
| 231 | + cv2.imshow(str(i), gray) |
| 232 | + cv2.waitKey(0) |
| 233 | + i += 1 |
| 234 | +cv2.waitKey(0) |
| 235 | + |
| 236 | +cv2.destroyAllWindows() |