python自动生成黄蓝绿车牌,并自动标注yolo模型labelImg文件
参考作者:
作者:Charlotte77
出处:http://www.cnblogs.com/charlotte77/
本文以学习、研究和分享为主,如需转载,请联系本人,标明作者和出处,非商业用途!
介绍:本文灵感来自于作者:Charlotte77,在其自动生成车牌的基础上稍作修改,可随机生成三色车牌并标注xml文件。生成保存路径和xml保存路径以及单词随机生成个数,可在代码中设置。因为我是做车牌字符的识别,所以生成时规定了省份简称,如需更换可修改代码中相应部分,亦可随机。
修改部分:使用pypinyin模块得到省份简称的拼音,同音字已做区分,藏字得音为cang,需要注意!后续模型分类可另作修改。
注意:注释不一定是原作者真是用意,请加以甄别!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#coding=utf-8 """ genPlate.py:生成随机车牌 """ __author__ = "Huxiaoman" __copyright__ = "Copyright (c) 2017 " import PIL from PIL import ImageFont from PIL import Image from PIL import ImageDraw import cv2 import numpy as np import os from math import * import pypinyin import sys import os import xml.dom.minidom import pypinyin def genXML(plate, labels, xmlPath): # 生成的字符列表和 标签位置 # pinyin = pypinyin.slug(imageName[0]) # if imageName[0] == '贵': # pinyin = 'gui' # elif imageName[0] == '桂': # pinyin = 'guilin' # elif imageName[0] == '甘': # pinyin = 'gan' # elif imageName[0] == '赣': # pinyin = 'jiangxi' # elif imageName[0] == '豫': # pinyin = 'yu' # elif imageName[0] == '渝': # pinyin = 'chongqing' # elif imageName[0] == '晋': # pinyin = 'jin' # elif imageName[0] == '津': # pinyin = 'tianjin' new_txtname = ''.join(plate) # # 创建空的Dom文档对象 doc = xml.dom.minidom.Document() # 创建根结点,根节点名为 annotation annotation = doc.createElement('annotation') # 根节点 # 将根节点添加到Dom文档对象中 doc.appendChild(annotation) # folder节点 folder = doc.createElement('folder') # 创建一个名叫folder的节点 # 内容写入 folder_text = doc.createTextNode('JPEGImages') # folder节点里面要写的内容 folder.appendChild(folder_text) # 添加到folder节点下,如果是内容,节点内容createTextNode类型,就作为内容写入;如果是createElement类型,就作为子节点添加进去 annotation.appendChild(folder) # 之后将添加好内容的folder节点,作为子节点添加到annotation节点中 # filename节点 filename = doc.createElement('filename') filename_text = doc.createTextNode(str(new_txtname) + '.jpg') # 这个地方是随机生成的图片文件名 filename.appendChild(filename_text) # annotation.appendChild(filename) # path节点 path = doc.createElement('path') path_text = doc.createTextNode('E:\\darknet-master\\build\\darknet\\myVLPCharData\\JPEGImages\\%s.jpg' % new_txtname) path.appendChild(path_text) # annotation.appendChild(path) # sourch节点 source = doc.createElement('source') # database = doc.createElement('database') database_text = doc.createTextNode('Unknown') database.appendChild(database_text) # source.appendChild(database) # annotation.appendChild(source) # size节点 size = doc.createElement('size') width = doc.createElement('width') width_text = doc.createTextNode('272') width.appendChild(width_text) size.appendChild(width) height = doc.createElement('height') height_text = doc.createTextNode('72') height.appendChild(height_text) size.appendChild(height) depth = doc.createElement('depth') depth_text = doc.createTextNode('3') depth.appendChild(depth_text) size.appendChild(depth) # annotation.appendChild(size) # segmented节点 segmented = doc.createElement('segmented') segmented_text = doc.createTextNode('0') segmented.appendChild(segmented_text) # annotation.appendChild(segmented) # object节点 for [y1, y2, x1, x2], pChar in zip(labels, plate): object = doc.createElement('object') name = doc.createElement('name') name_text = doc.createTextNode(pChar) # 这个地方是标签的name,也就是分类名称 name.appendChild(name_text) object.appendChild(name) pose = doc.createElement('pose') pose_text = doc.createTextNode("Unspecified") pose.appendChild(pose_text) object.appendChild(pose) truncated = doc.createElement('truncated') truncated_text = doc.createTextNode("0") truncated.appendChild(truncated_text) object.appendChild(truncated) difficult = doc.createElement('difficult') difficult_text = doc.createTextNode("0") difficult.appendChild(difficult_text) object.appendChild(difficult) bndbox = doc.createElement('bndbox') # xmin = doc.createElement('xmin') xmin_text = doc.createTextNode(str(x1)) xmin.appendChild(xmin_text) bndbox.appendChild(xmin) # ymin = doc.createElement('ymin') ymin_text = doc.createTextNode(str(y1)) ymin.appendChild(ymin_text) bndbox.appendChild(ymin) # xmax = doc.createElement('xmax') xmax_text = doc.createTextNode(str(x2)) xmax.appendChild(xmax_text) bndbox.appendChild(xmax) # ymax = doc.createElement('ymax') ymax_text = doc.createTextNode(str(y2)) ymax.appendChild(ymax_text) bndbox.appendChild(ymax) # object.appendChild(bndbox) # annotation.appendChild(object) # 写入xml文本文件中 if not os.path.exists(xmlPath): os.mkdir(xmlPath) fp = open(xmlPath + '/%s.xml' % new_txtname, 'w+') doc.writexml(fp, indent='\n', addindent='\t', newl='', encoding='utf-8') fp.close() index = {"京": 0, "沪": 1, "津": 2, "渝": 3, "冀": 4, "晋": 5, "蒙": 6, "辽": 7, "吉": 8, "黑": 9, "苏": 10, "浙": 11, "皖": 12, "闽": 13, "赣": 14, "鲁": 15, "豫": 16, "鄂": 17, "湘": 18, "粤": 19, "桂": 20, "琼": 21, "川": 22, "贵": 23, "云": 24, "藏": 25, "陕": 26, "甘": 27, "青": 28, "宁": 29, "新": 30, "0": 31, "1": 32, "2": 33, "3": 34, "4": 35, "5": 36, "6": 37, "7": 38, "8": 39, "9": 40, "A": 41, "B": 42, "C": 43, "D": 44, "E": 45, "F": 46, "G": 47, "H": 48, "J": 49, "K": 50, "L": 51, "M": 52, "N": 53, "P": 54, "Q": 55, "R": 56, "S": 57, "T": 58, "U": 59, "V": 60, "W": 61, "X": 62, "Y": 63, "Z": 64} chars = ["京", "沪", "津", "渝", "冀", "晋", "蒙", "辽", "吉", "黑", "苏", "浙", "皖", "闽", "赣", "鲁", "豫", "鄂", "湘", "粤", "桂", "琼", "川", "贵", "云", "藏", "陕", "甘", "青", "宁", "新", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" ] def AddSmudginess(img, Smu): rows = r(Smu.shape[0] - 50) cols = r(Smu.shape[1] - 50) adder = Smu[rows:rows + 50, cols:cols + 50] adder = cv2.resize(adder, (50, 50)) #adder = cv2.bitwise_not(adder) img = cv2.resize(img,(50,50)) img = cv2.bitwise_not(img) img = cv2.bitwise_and(adder, img) img = cv2.bitwise_not(img) return img def rot(img,angel,shape,max_angel): # com, [-30, 30), (70, 226, 3), 30 """ 添加放射畸变 img 输入图像 factor 畸变的参数 size 为图片的目标尺寸 """ size_o = [shape[1], shape[0]] # [226, 70] size = (shape[1] + int(shape[0] * cos((float(max_angel)/180) * 3.14)), shape[0]) # (226+60=286, 70) # 宽度也就是shape[1]的长度加上高度乘以高度的倾斜宽30/180,等于最后放射的宽度 interval = abs(int(sin((float(angel) / 180) * 3.14) * shape[0])) # [0, 34] # 0到34之间整数 pts1 = np.float32([[0, 0], [0, size_o[1]], [size_o[0], 0], [size_o[0], size_o[1]]]) # [[0, 0], [0, 70], [226, 0], [226, 70]] if angel > 0: pts2 = np.float32([[interval, 0], [0, size[1]], [size[0], 0], [size[0]-interval, size_o[1]]]) # [[0到34, 0], [0, 70], [226, 0], [226-0到34, 70]] else: pts2 = np.float32([[0, 0], [interval, size[1]], [size[0]-interval, 0], [size[0], size_o[1]]]) # [[0, 0], [0到34, 70], [226-0到34, 0], [226, 70]] M = cv2.getPerspectiveTransform(pts1, pts2) dst = cv2.warpPerspective(img, M, size) return dst def rotRandrom(img, factor, size): # com, 10, (286, 70) # com.shape = (70, 286, 3) """ 添加透视畸变 """ shape = size pts1 = np.float32([[0, 0], [0, shape[0]], [shape[1], 0], [shape[1], shape[0]]]) pts2 = np.float32([[r(factor), r(factor)], [r(factor), shape[0] - r(factor)], [shape[1] - r(factor), r(factor)], [shape[1] - r(factor), shape[0] - r(factor)]]) M = cv2.getPerspectiveTransform(pts1, pts2) dst = cv2.warpPerspective(img, M, size) return dst def tfactor(img): """ 添加饱和度光照的噪声 """ hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) hsv[:, :, 0] = hsv[:, :, 0]*(0.8 + np.random.random()*0.2) hsv[:, :, 1] = hsv[:, :, 1]*(0.3 + np.random.random()*0.7) hsv[:, :, 2] = hsv[:, :, 2]*(0.2 + np.random.random()*0.8) img = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) return img def random_envirment(img, data_set): """ 添加自然环境的噪声 """ index=r(len(data_set)) env = cv2.imread(data_set[index]) env = cv2.resize(env,(img.shape[1],img.shape[0])) bak = (img==0) bak = bak.astype(np.uint8)*255 inv = cv2.bitwise_and(bak,env) img = cv2.bitwise_or(inv,img) return img def GenCh(f, val): """ 生成中文字符 """ img = Image.new("RGB", (45, 70), (255, 255, 255)) draw = ImageDraw.Draw(img) draw.text((0, 3), val, (0, 0, 0), font=f) img = img.resize((23, 70)) # 文字图片转换成了23列,70行 A = np.array(img) return A def GenCh1(f,val): """ 生成英文字符 """ img=Image.new("RGB", (23, 70), (255, 255, 255)) draw = ImageDraw.Draw(img) # draw.text((0, 2), val.decode('utf-8'), (0, 0, 0), font=f) draw.text((0, 2), val, (0, 0, 0), font=f) A = np.array(img) return A def AddGauss(img, level): # level [1, 5) """ 添加高斯模糊 """ # return cv2.blur(img, (level*2 + 1, level*2 + 1)) return cv2.blur(img, (5, 5)) # 可接受的模糊程度 # return cv2.blur(img, (level, level)) def r(val): # 返回随机数 return int(np.random.random() * val) def AddNoiseSingleChannel(single): """ 添加高斯噪声 """ diff = 255-single.max() noise = np.random.normal(0, 1+r(6), single.shape) noise = (noise - noise.min())/(noise.max()-noise.min()) noise = diff*noise noise = noise.astype(np.uint8) dst = single + noise return dst def addNoise(img, sdev = 0.5, avg=10): img[:, :, 0] = AddNoiseSingleChannel(img[:, :, 0]) img[:, :, 1] = AddNoiseSingleChannel(img[:, :, 1]) img[:, :, 2] = AddNoiseSingleChannel(img[:, :, 2]) return img class GenPlate: def __init__(self, fontCh, fontEng, NoPlates, xmlPath): # "./font/platech.ttf", './font/platechar.ttf', "./NoPlates" self.fontC = ImageFont.truetype(fontCh, 43, 0) self.fontE = ImageFont.truetype(fontEng, 60, 0) self.img = np.array(Image.new("RGB", (226, 70), (255, 255, 255))) # 蓝色车牌背景图 self.bg = cv2.resize(cv2.imread("./images/template.bmp"), (226, 70)) # 黄色车牌背景图 # self.bg = cv2.resize(cv2.imread("./images/y1.bmp"),(226,70)) # # self.smu = cv2.imread("./images/smu2.jpg") # 绿色车牌背景图 # self.bg = cv2.resize(cv2.imread("./images/g1.jpg"),(226,70)) self.noplates_path = [] self.xmlPath = xmlPath # xml文件保存位置 for parent, parent_folder, filenames in os.walk(NoPlates): # print(parent, parent_folder, filenames) # ./NoPlates # [] # ['A01_N84E28_1.jpg', 'A01_N84E28_2.jpg', 'A01_NMV802_1.jpg', 'A01_NMV802_2.jpg', 'A02_NBD719_1.jpg', 'A02_NBD719_2.jpg', 'A03_A05F26_1.jpg', 'A03_A137U8_1.jpg', 'A03_A137U8_2.jpg', 'A03_A137U8_3.jpg', 'A03_A19Z80_0.jpg', 'A03_A19Z80_1.jpg', 'A03_A19Z80_2.jpg', 'A03_A19Z80_3.jpg', 'A03_A19Z80_4.jpg', 'A03_A19Z80_5.jpg', 'A03_A19Z80_7.jpg', 'A03_A19Z80_8.jpg', 'A03_A1L828_0.jpg', 'A03_A1L828_1.jpg', 'A03_A1L828_2.jpg', 'A03_A1L828_4.jpg', 'A03_A1Z726_1.jpg', 'A03_A203J1_1.jpg', 'A03_A203J1_2.jpg', 'A03_A2H337_0.jpg', 'A03_A2H337_2.jpg', 'A03_A2M801_1.jpg', 'A03_A2M801_2.jpg', 'A03_A4F288_1.jpg', 'A03_A5X098_1.jpg', 'A03_A60L48_0.jpg', 'A03_A60L48_1.jpg', 'A03_A60L48_2.jpg', 'A03_A60L48_3.jpg', 'A03_A60L48_4.jpg', 'A03_A63X72_1.jpg', 'A03_A6U922_1.jpg', 'A03_A6U922_2.jpg', 'A03_A722S6_0.jpg', 'A03_A79A95_1.jpg', 'A03_A7N292_1.jpg', 'A03_A82E65_1.jpg', 'A03_A82E65_2.jpg', 'A03_A85V02_1.jpg', 'A03_A8B389_1.jpg', 'A03_A8B389_2.jpg', 'A03_A8B389_3.jpg', 'A03_A8B389_4.jpg', 'A03_A8E322_1.jpg', 'A03_A9F208_1.jpg', 'A03_A9F208_2.jpg', 'A03_AAC595_0.jpg', 'A03_AAC595_2.jpg', 'A03_AAC595_3.jpg', 'A03_AAC595_4.jpg', 'A03_AAC595_5.jpg', 'A03_AAQ839_0.jpg', 'A03_AAQ839_1.jpg', 'A03_AAQ839_2.jpg', 'A03_AAQ839_4.jpg', 'A03_AAQ839_5.jpg', 'A03_ABF318_1.jpg', 'A03_ABF318_2.jpg'] for filename in filenames: path = parent + "/" + filename # path = os.path.join(parent, filename) # print(path) self.noplates_path.append(path) def draw(self,val): # val = # 鄂ARQ2CR offset = 2 self.img[0:70, offset+8:offset+8+23] = GenCh(self.fontC, val[0]) # 第一个中文的位置 x, y, w, h 在0- 70行的 第10- 第10+23=33列 # print(offset+8, ':', offset+8+23) self.img[0:70, offset+8+23+6:offset+8+23+6+23] = GenCh1(self.fontE, val[1]) # 第二个城市简称的位置在 第10+23 + 6 = 39- 第39+23= 62列 # print(offset+8+23+6, ':', offset+8+23+6+23) for i in range(5): base = offset + 8 + 23 + 6 + 23 + 17 + i*23 + i*6 # 后五位编号的位置在第二个城市代号末尾列,也就是第10+23+6+23= 62列的后面加上17= 79列,17是城市代号到后五位编号中间的防伪标记点的占距 self.img[0:70, base: base+23] = GenCh1(self.fontE, val[i+2]) # print(base, ':', base+23) return self.img # 总结一下:(226, 70) 10-63行 # 省份简称 [:, 10:33], # 城市代号[:, 39:62], # 第一位编号[:, 79:102], # 第二位编号[:, 108:131], # 第三位编号[:, 137:160], # 第四位编号[:, 166:189], # 第五位编号[:, 195:218] # 放大后 # [11: 61, 12: 39] # [11:61, 46: 74] # [11:61, 95: 122] # [11:61, 129: 157] # [11:61, 164: 192] # [11:61, 199: 227] # [11:61, 234: 262] def generate(self, text): # 鄂ARQ2CR # if len(text) == 9: if len(text) == 7: # fg = self.draw(text.decode(encoding="utf-8")) # text.encode(encoding="utf-8").decode(encoding="utf-8") # fg = self.draw(text.encode(encoding="utf-8").decode(encoding="utf-8")) fg = self.draw(text) # 鄂ARQ2CR # 白色字体 # 使用时,注释掉黑色字体部分 fg = cv2.bitwise_not(fg) com = cv2.bitwise_or(fg, self.bg) # com = rot(com, r(60)-30, com.shape, 30) # 放射畸变 # 整体效果为 车牌行数不变,列数拉伸成∠60°的平行四边形状 # print(com.shape) # (70, 286, 3) # # com = rotRandrom(com, 10, (com.shape[1], com.shape[0])) # 透视畸变 # 整体效果为 梯形拉伸, com = tfactor(com) # 饱和光照的噪声 com = random_envirment(com, self.noplates_path) # 自然环境噪声 com = AddGauss(com, 1+r(4)) # 添加高斯模糊 com = addNoise(com) print(com.shape) # # 黑色字体 黄车牌适用 # 使用时,注释掉白色字体部分 # com = cv2.bitwise_and(fg,self.bg) # # com = addNoise(com) # 先添加噪音,后进行其他操作对字体颜色的影响很小 # # com = rot(com, r(60) - 30, com.shape, 30) # # com = rotRandrom(com, 10, (com.shape[1], com.shape[0])) # com = tfactor(com) # com = random_envirment(com, self.noplates_path) # 绿色车牌背景图时,要注释掉自然环境噪声,减小对字体颜色影响 # com = AddGauss(com, 3 + r(2)) return com else: return None def genPlateString(self, pos, val): # -1, -1 ''' 生成车牌String,存为图片 生成车牌list,存为label ''' plateStr = "" plateList = [] box = [0, 0, 0, 0, 0, 0, 0] if pos != -1: box[pos] = 1 for unit, cpos in zip(box, range(len(box))): if unit == 1: plateStr += val #print plateStr plateList.append(val) else: if cpos == 0: # plateStr += chars[r(31)] # 控制省份随机范围,可指定某一省份 plateStr += chars[11] # 控制省份随机范围,可指定某一省份 京 0 渝 3 30封顶 plateList.append(plateStr) elif cpos == 1: plateStr += chars[41+r(24)] plateList.append(plateStr) else: plateStr += chars[31 + r(34)] plateList.append(plateStr) plate = [plateList[0]] b = [plateList[i][-1] for i in range(len(plateList))] plate.extend(b[1:7]) # extend() 在一个list后面,添加另一个list的多个值 return plateStr, plate # 将生成的车牌图片写入文件夹,对应的label写入label.txt def genBatch(self, batchSize, pos, charRange, outputPath, size): if not os.path.exists(outputPath): os.mkdir(outputPath) outfile = open('label.txt', 'w') for i in range(batchSize): plateStr, plate = G.genPlateString(-1, -1) pinyin = pypinyin.slug(plateStr[0]) if plateStr[0] == '贵': pinyin = 'gui' elif plateStr[0] == '桂': pinyin = 'guilin' elif plateStr[0] == '甘': pinyin = 'gan' elif plateStr[0] == '赣': pinyin = 'jiangxi' elif plateStr[0] == '豫': pinyin = 'yu' elif plateStr[0] == '渝': pinyin = 'chongqing' elif plateStr[0] == '晋': pinyin = 'jin' elif plateStr[0] == '津': pinyin = 'tianjin' elif plateStr[0] == '冀': pinyin = 'hebei' elif plateStr[0] == '吉': pinyin = 'ji' labels = [ [11, 61, 12, 39], [11, 61, 46, 74], [11, 61, 95, 122], [11, 61, 129, 157], [11, 61, 164, 192], [11, 61, 199, 227], [11, 61, 234, 262] ] # 修改后的位置 labels = [ [12, 61, 11, 40], [12, 61, 45, 75], [12, 61, 94, 123], [12, 61, 128, 158], [12, 61, 163, 193], [12, 61, 198, 228], [12, 61, 233, 263] ] imageName = pinyin + plateStr[1:] # 省份简称替换成拼音 plate[0] = pinyin # 标签换成转换后的拼音 genXML(plate, labels, self.xmlPath) # 生成对应的标签 print(imageName) print(plateStr, plate) # 鄂ARQ2CR ['鄂', 'A', 'R', 'Q', '2', 'C', 'R'] img = G.generate(plateStr) # print(img.shape) img = cv2.resize(img, size) # (272, 72) # 注释掉这一步是因为要get到没放大的字符位置 # print(img.shape) # cv2.imwrite(outputPath + "/" + str(i).zfill(2) + ".jpg", img) if not os.path.exists(outputPath): os.mkdir(outputPath) cv2.imwrite(outputPath + "/" + imageName + ".jpg", img) outfile.write(str(plate)+"\n") outfile.close() # G = GenPlate("./font/platech.ttf", './font/platechar.ttf', "./NoPlates", 'E:\\darknet-master\\build\\darknet\\myVLPCharData\\Annotations') G = GenPlate("./font/platech.ttf", './font/platechar.ttf', "./NoPlates", './xml') # 测试修改时使用 #G.genBatch(100, 2, range(31, 65), "./plate_100", (272, 72)) if __name__ == '__main__': # G.genBatch(int(sys.argv[1]), 2, range(31, 65), sys.argv[2], (272, 72)) # # # python genPlate.py 100 ./plate_100 # G.genBatch(int(300), 2, range(31, 65), "E:\\darknet-master\\build\\darknet\\myVLPCharData\\JPEGImages", (272, 72)) G.genBatch(int(13), 2, range(31, 65), "./plate_100", (272, 72)) # 测试修改时使用 classDict = {'jing': "京", 'hu': "沪", 'tianjin': "津", 'chongqing': "渝", 'hebei': "冀", 'jin': "晋", 'meng': "蒙", 'liao': "辽", 'ji': "吉", 'hei': "黑", 'su': "苏", 'zhe': "浙", 'wan': "皖", 'min': "闽", 'jiangxi': "赣", 'lu': "鲁", 'yu': "豫", 'e': "鄂", 'xiang': "湘", 'yue': "粤", 'guangxi': "桂", 'qiong': "琼", 'chuan': "川", 'gui': "贵", 'yun': "云", 'cang': "藏", 'shan': "陕", 'gan': "甘", 'qing': "青", 'ning': "宁", 'xin': "新"}
网盘文件链接(基础代码皆来自-作者:Charlotte77):
链接:https://pan.baidu.com/s/1qPnEvZh8fzmqmOuuZfe8Ag
提取码:m1sw