參考作者:
作者:Charlotte77
出處:http://www.cnblogs.com/charlotte77/
本文以學習、研究和分享為主,如需轉載,請聯系本人,標明作者和出處,非商業用途!
介紹:本文靈感來自於作者:Charlotte77,在其自動生成車牌的基礎上稍作修改,可隨機生成三色車牌並標注xml文件。生成保存路徑和xml保存路徑以及單詞隨機生成個數,可在代碼中設置。因為我是做車牌字符的識別,所以生成時規定了省份簡稱,如需更換可修改代碼中相應部分,亦可隨機。
修改部分:使用pypinyin模塊得到省份簡稱的拼音,同音字已做區分,藏字得音為cang,需要注意!后續模型分類可另作修改。
注意:注釋不一定是原作者真是用意,請加以甄別!

#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