├── README.md └── project ├── 检测条形码位置 ├── detecting-barcodes-in-images │ ├── detect_barcode.py │ └── images │ │ ├── barcode_01.jpg │ │ ├── barcode_02.jpg │ │ ├── barcode_03.jpg │ │ ├── barcode_04.jpg │ │ ├── barcode_05.jpg │ │ └── barcode_06.jpg └── 用 Python 和 OpenCV 检测图片上的条形码 - 文章 - 伯乐在线.pdf ├── 汉字识别 └── tinymind_chinese_font_recognition │ ├── README.md │ ├── pipline_model.py │ ├── split_dataset.py │ ├── tinymind_predict.py │ ├── tinymind_split_train_test.py │ └── tinymind_train_model.py └── 用 Python 和 OpenCV 检测和跟踪运动对象 ├── motion_detector.py └── 用 Python 和 OpenCV 检测和跟踪运动对象 - Python - 伯乐在线.pdf /README.md: -------------------------------------------------------------------------------- 1 | # project 2 | 3 | python-opencv检测条形码位置 4 | 5 | python-opencv检测和跟踪运动目标 6 | 7 | tinymind 汉字识别 8 | -------------------------------------------------------------------------------- /project/检测条形码位置/detecting-barcodes-in-images/detect_barcode.py: -------------------------------------------------------------------------------- 1 | # USAGE 2 | # python detect_barcode.py --image images/barcode_01.jpg 3 | 4 | # import the necessary packages 5 | import numpy as np 6 | import argparse 7 | import cv2 8 | 9 | # construct the argument parse and parse the arguments 10 | ap = argparse.ArgumentParser() 11 | ap.add_argument("-i", "--image", required = True, help = "path to the image file") 12 | args = vars(ap.parse_args()) 13 | 14 | # load the image and convert it to grayscale 15 | image = cv2.imread(args["image"]) 16 | gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 17 | 18 | # compute the Scharr gradient magnitude representation of the images 19 | # in both the x and y direction 20 | gradX = cv2.Sobel(gray, ddepth = cv2.cv.CV_32F, dx = 1, dy = 0, ksize = -1) 21 | gradY = cv2.Sobel(gray, ddepth = cv2.cv.CV_32F, dx = 0, dy = 1, ksize = -1) 22 | 23 | # subtract the y-gradient from the x-gradient 24 | gradient = cv2.subtract(gradX, gradY) 25 | gradient = cv2.convertScaleAbs(gradient) 26 | 27 | # blur and threshold the image 28 | blurred = cv2.blur(gradient, (9, 9)) 29 | (_, thresh) = cv2.threshold(blurred, 225, 255, cv2.THRESH_BINARY) 30 | 31 | # construct a closing kernel and apply it to the thresholded image 32 | kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (21, 7)) 33 | closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel) 34 | 35 | # perform a series of erosions and dilations 36 | closed = cv2.erode(closed, None, iterations = 4) 37 | closed = cv2.dilate(closed, None, iterations = 4) 38 | 39 | # find the contours in the thresholded image, then sort the contours 40 | # by their area, keeping only the largest one 41 | (cnts, _) = cv2.findContours(closed.copy(), cv2.RETR_EXTERNAL, 42 | cv2.CHAIN_APPROX_SIMPLE) 43 | c = sorted(cnts, key = cv2.contourArea, reverse = True)[0] 44 | 45 | # compute the rotated bounding box of the largest contour 46 | rect = cv2.minAreaRect(c) 47 | box = np.int0(cv2.cv.BoxPoints(rect)) 48 | 49 | # draw a bounding box arounded the detected barcode and display the 50 | # image 51 | cv2.drawContours(image, [box], -1, (0, 255, 0), 3) 52 | cv2.imshow("Image", image) 53 | cv2.waitKey(0) -------------------------------------------------------------------------------- /project/检测条形码位置/detecting-barcodes-in-images/images/barcode_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhzgithub/project_tinymind/6e26cbca81bc0ad50d8c25f26a453044cddce252/project/检测条形码位置/detecting-barcodes-in-images/images/barcode_01.jpg -------------------------------------------------------------------------------- /project/检测条形码位置/detecting-barcodes-in-images/images/barcode_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhzgithub/project_tinymind/6e26cbca81bc0ad50d8c25f26a453044cddce252/project/检测条形码位置/detecting-barcodes-in-images/images/barcode_02.jpg -------------------------------------------------------------------------------- /project/检测条形码位置/detecting-barcodes-in-images/images/barcode_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhzgithub/project_tinymind/6e26cbca81bc0ad50d8c25f26a453044cddce252/project/检测条形码位置/detecting-barcodes-in-images/images/barcode_03.jpg -------------------------------------------------------------------------------- /project/检测条形码位置/detecting-barcodes-in-images/images/barcode_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhzgithub/project_tinymind/6e26cbca81bc0ad50d8c25f26a453044cddce252/project/检测条形码位置/detecting-barcodes-in-images/images/barcode_04.jpg -------------------------------------------------------------------------------- /project/检测条形码位置/detecting-barcodes-in-images/images/barcode_05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhzgithub/project_tinymind/6e26cbca81bc0ad50d8c25f26a453044cddce252/project/检测条形码位置/detecting-barcodes-in-images/images/barcode_05.jpg -------------------------------------------------------------------------------- /project/检测条形码位置/detecting-barcodes-in-images/images/barcode_06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhzgithub/project_tinymind/6e26cbca81bc0ad50d8c25f26a453044cddce252/project/检测条形码位置/detecting-barcodes-in-images/images/barcode_06.jpg -------------------------------------------------------------------------------- /project/检测条形码位置/用 Python 和 OpenCV 检测图片上的条形码 - 文章 - 伯乐在线.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhzgithub/project_tinymind/6e26cbca81bc0ad50d8c25f26a453044cddce252/project/检测条形码位置/用 Python 和 OpenCV 检测图片上的条形码 - 文章 - 伯乐在线.pdf -------------------------------------------------------------------------------- /project/汉字识别/tinymind_chinese_font_recognition/README.md: -------------------------------------------------------------------------------- 1 | # tinymind_chinese_font_recognition 2 | 汉字书法识别         3 | 4 | split_dataset.py 分割数据集,分割为训练集和验证集           5 | 6 | pipline_model.py 使用卷积的pipline模型来进行训练和预测 7 | 8 | 训练集在百度网盘 9 | 10 | # 自然场景汉字识别 11 | tinymind_split_train_test.py 是和split_dataset.py一样的代码 12 | 13 | tinymind_train_model.py 是针对自然场景识别的汉字数据集进行训练分类,代码摘自pipline_model.py 14 | 15 | tinymind_predict.py 是对汉字进行预测类别,代码摘自pipline_model.py 16 | 17 | 训练集在腾讯微云(不是腾讯云) 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /project/汉字识别/tinymind_chinese_font_recognition/pipline_model.py: -------------------------------------------------------------------------------- 1 | from keras.preprocessing.image import ImageDataGenerator 2 | from keras.applications.resnet50 import ResNet50 3 | from keras.layers import Conv2D, MaxPool2D, AveragePooling2D, Activation, Embedding 4 | from keras.layers import Flatten, Dense, BatchNormalization, Dropout, PReLU, Lambda 5 | from keras.models import Model, Input 6 | from keras.callbacks import LearningRateScheduler, ModelCheckpoint, TensorBoard 7 | from keras.optimizers import SGD 8 | import keras.backend as K 9 | from PIL import Image 10 | import matplotlib.pyplot as plt 11 | import pandas as pd 12 | import numpy as np 13 | import os 14 | 15 | 16 | # bn + prelu 17 | def bn_prelu(x): 18 | x = BatchNormalization()(x) 19 | x = PReLU()(x) 20 | return x 21 | 22 | 23 | # ==================================================================================================================================================================- 24 | # learning rate of epoch # 这里的epoch没用到啊,后面的程序直接调用的lr = LearningRateScheduler(lrschedule),根本没有给参数epoch赋值! 25 | def lrschedule(epoch): 26 | if epoch <= 40: 27 | return 0.1 28 | elif epoch <= 80: 29 | return 0.01 30 | else: 31 | return 0.001 32 | 33 | 34 | # label for directory in disk 每类汉字的标签 35 | # def label_of_directory(directory): # 这里的directory最后被赋值为train_path = code_path + 'new_train_val_data/font_tra/' 36 | # """ 37 | # sorted for label indices 38 | # return a dict for {'classes', 'range(len(classes))'} 39 | # """ 40 | # classes = [] 41 | # for subdir in sorted(os.listdir(directory)): # 对汉字文件夹路径排序 42 | # if os.path.isdir(os.path.join(directory, subdir)): # 如果汉字文件夹是路径,就把汉字文件夹路径添加进classes列表中 os.path.join() 将多个路径组合后返回 43 | # classes.append(subdir) 44 | 45 | # num_classes = len(classes) # 统计训练集文件夹下的汉字文件夹有多少个,即汉字类别有多少。明显是100个 46 | # class_indices = dict(zip(classes, range(len(classes)))) # 生成 汉字文件夹路径作为关键字,汉字类别数目作为键值 的字典,即{类别:索引} 47 | # return class_indices # 函数返回值为{类别:索引}的字典形式 48 | 49 | 50 | # # 读取数据 51 | # # 直接读取数据到内存?还是先占据个位置训练的时候再分批次读取进内存? 52 | # # 训练的时候分批次读取进内存,该函数需要在训练或预测模块函数内使用,不能直接在训练之外使用,不然会一直占据大量内存 53 | # def read_data(path): 54 | # image_list=[] 55 | # label_list=[] 56 | # for img_dir in sorted(os.listdir(path)): # 遍历图片文件夹,而非图片 57 | # label_list.append(img_dir) 58 | # for image in os.listdir(path+img_dir): # 遍历每个图片文件夹的图片 59 | # # print(image) 60 | # img=load_image(path+img_dir+'/'+image) 61 | # # print(img.shape) 62 | # image_list.append(img) 63 | # image_arr=np.array(image_list) 64 | # label_list=sorted(label_list*len(os.listdir(path+img_dir))) 65 | # # label_arr=np.array(label_list).reshape(len(label_list),1) # 这里只是每个文件夹的标签,并非文件夹下的图片的标签 66 | 67 | # # print(image_arr.shape) #shape=(?,128,128,1) 68 | # return image_arr,label_list # 函数返回值为所有图片组成的数组,但是并没有标签 69 | 70 | # # 标签变为onehot形式的标签 71 | # def trans2onehot_label(labels,columns): # 这里的label是由所有样本的标签组成的标签向量,标签向量为[1,3,8,12,..]的形式 72 | 73 | # new_label=np.zeros((len(labels),100),dtype=np.uint8) # 创建一个样本数行,100列的零矩阵 74 | 75 | # # 例如:某标签为12的话,那么第12个位置就设置为1 76 | # count=0 77 | # for value in labels: 78 | # for j in range(100): 79 | # if value==j: 80 | # new_label[count][value]=1 81 | # break 82 | # count+=1 83 | # return new_label 84 | 85 | 86 | # 搭建模型 87 | def build_model(out_dims, input_shape=(128, 128, 1)): 88 | inputs_dim = Input(input_shape) # inputs_dim的type为tf.Tensor,shape为(None,128,128,1),dtype为float32 89 | # model=Sequential() 90 | # model.add(Conv2D(32,(3,3),strides=(2, 2), padding='valid')(inputs_dim)) 91 | 92 | x = Conv2D(32, (3, 3), strides=(2, 2), padding='valid')( 93 | inputs_dim) # “valid”代表只进行有效的卷积,即对边界数据不处理。“VALID”发现余下的窗口不够卷积核大小了所以就把后面的列直接去掉了 94 | # conv2D(卷积核个数,卷积核尺寸,步长,填充方式)(输入尺寸),其中卷积核个数即输出的深度,本次卷积后shape为(None,63,63,32) 95 | 96 | x = bn_prelu(x) 97 | x = Conv2D(32, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,61,61,32) 98 | x = bn_prelu(x) 99 | x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,30,30,32) 100 | 101 | x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,28,28,64) 102 | x = bn_prelu(x) 103 | x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,26,26,64) 104 | x = bn_prelu(x) 105 | x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,13,13,64) 106 | 107 | x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,11,11,128) 108 | x = bn_prelu(x) 109 | x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,5,5,128) 110 | 111 | x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,3,3,128) 112 | x = bn_prelu(x) 113 | x = AveragePooling2D(pool_size=(2, 2))(x) # 本次平均池化后,shape==(None,1,1,128) 114 | 115 | x_flat = Flatten()(x) # shape==(None,1*1*128)即(None,128) 116 | 117 | fc1 = Dense(512)(x_flat) # Dense(全连接层),512是该层的输出维度,x_flat是该层的输入维度,则输出shape==(None,512) 118 | fc1 = bn_prelu(fc1) 119 | dp_1 = Dropout(0.3)(fc1) 120 | 121 | fc2 = Dense(out_dims)(dp_1) # 后面会赋值out_dims==100,即100个汉字类别,输出shape==(None,100),None表示样本数 122 | fc2 = Activation('softmax')(fc2) 123 | 124 | model = Model(inputs=inputs_dim, outputs=fc2) 125 | return model 126 | 127 | 128 | # 训练模型 129 | # def train_model(istrain): 130 | # if not istrain: 131 | # if os.path.exists(weights_path): 132 | # model.load_weights(weights_path) 133 | # else: 134 | # print('weights文件不存在,不能加载权重文件,请重新修改train_model函数的istrain参数') 135 | # else: 136 | # print('开始训练') 137 | # model=construct_model(100,input_shape=(128,128,1)) 138 | # sgd = SGD(lr=0.1, momentum=0.9, decay=5e-4, nesterov=True) 139 | # model.compile(optimizer='sgd', 140 | # loss='categorical_crossentropy', 141 | # metrics=['accuracy']) 142 | # # model.fit(data, labels) # starts training 这种训练方式是把所有的训练数据一起输入进去,没有分batch 143 | # for epochs in range(100): 144 | # train_loss=model.train_on_batch(x_train,y_train) 145 | # # model.predict(x_val,y_val) 146 | # val_loss,val_acc=model.evaluate(x_val,y_val) 147 | # print(('epoch=%d-----train_loss=%f---val_loss=%f---val_acc=%f',(epochs,train_loss,val_loss,val_acc))) 148 | # model.save(weights_path) 149 | # return model 150 | 151 | def model_train(model): 152 | lr = LearningRateScheduler(lrschedule) 153 | mdcheck = ModelCheckpoint(WEIGHTS_PATH, monitor='val_acc', save_best_only=True) # 在每个epoch后保存模型到WEIGHTS_PATH 154 | td = TensorBoard(log_dir=code_path + 'new_train_val_data/tensorboard_log/') 155 | 156 | sgd = SGD(lr=0.1, momentum=0.9, decay=5e-4, nesterov=True) 157 | print("model compile!!") 158 | model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy']) 159 | ''' 160 | model.compile(optimizer, loss, metrics=None, sample_weight_mode=None) 161 | 编译用来配置模型的学习过程,其参数有 162 | optimizer:字符串(预定义优化器名)或优化器对象,参考优化器 163 | loss:字符串(预定义损失函数名)或目标函数,参考损失函数 164 | metrics:列表,包含评估模型在训练和测试时的网络性能的指标,典型用法是metrics=['accuracy'] 165 | sample_weight_mode:如果你需要按时间步为样本赋权(2D权矩阵),将该值设为“temporal”。默认为“None”,代表按样本赋权(1D权)。在下面fit函数的解释中有相关的参考内容。 166 | kwargs:使用TensorFlow作为后端请忽略该参数,若使用Theano作为后端,kwargs的值将会传递给 K.function 167 | 168 | 如实例: 169 | model.compile( loss='categorical_crossentropy', optimizer='adam',metrics=['accuracy'] ) 170 | ''' 171 | print("model training!!") 172 | history = model.fit_generator(train_generator, 173 | steps_per_epoch=32000 // BATCH_SIZE, 174 | # steps_per_epoch=128 // BATCH_SIZE, 175 | epochs=max_Epochs, 176 | validation_data=val_generator, 177 | # validation_steps=128 // BATCH_SIZE, 178 | validation_steps=8000 // BATCH_SIZE, 179 | callbacks=[lr, mdcheck, td]) 180 | return history 181 | ''' http://keras-cn.readthedocs.io/en/latest/models/sequential/#fit_generator 182 | fit_generator 183 | fit_generator(self, generator, steps_per_epoch, epochs=1, verbose=1, callbacks=None, validation_data=None, 184 | validation_steps=None, class_weight=None, max_q_size=10, workers=1, pickle_safe=False, initial_epoch=0) 185 | 利用Python的生成器,逐个生成数据的batch并进行训练。生成器与模型将并行执行以提高效率。例如,该函数允许我们在CPU上进行实时的数据提升,同时在GPU上进行模型训练 186 | 函数的参数是: 187 | 188 | generator:生成器函数,生成器的输出应该为: 189 | 一个形如(inputs,targets)的tuple 190 | 一个形如(inputs, targets,sample_weight)的tuple。所有的返回值都应该包含相同数目的样本。生成器将无限在数据集上循环。每个epoch以经过模型的样本数达到samples_per_epoch时,记一个epoch结束 191 | steps_per_epoch:整数,当生成器返回steps_per_epoch次数据时计一个epoch结束,执行下一个epoch 192 | epochs:整数,数据迭代的轮数 193 | verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,2为每个epoch输出一行记录 194 | validation_data:具有以下三种形式之一 195 | 196 | 生成验证集的生成器 197 | 一个形如(inputs,targets)的tuple 198 | 一个形如(inputs,targets,sample_weights)的tuple 199 | validation_steps: 当validation_data为生成器时,本参数指定验证集的生成器返回次数 200 | class_weight:规定类别权重的字典,将类别映射为权重,常用于处理样本不均衡问题。 201 | sample_weight:权值的numpy array,用于在训练时调整损失函数(仅用于训练)。可以传递一个1D的与样本等长的向量用于对样本进行1对1的加权,或者在面对时序数据时,传递一个的形式为(samples,sequence_length)的矩阵来为每个时间步上的样本赋不同的权。这种情况下请确定在编译模型时添加了sample_weight_mode='temporal'。 202 | workers:最大进程数 203 | max_q_size:生成器队列的最大容量 204 | pickle_safe: 若为真,则使用基于进程的线程。由于该实现依赖多进程,不能传递non picklable(无法被pickle序列化)的参数到生成器中,因为无法轻易将它们传入子进程中。 205 | initial_epoch: 从该参数指定的epoch开始训练,在继续之前的训练时有用。 206 | 函数返回一个History对象 207 | ''' 208 | 209 | # 以下定义的这么多函数都是为了处理最后要预测的数据集,对于训练时并不需要使用到这些函数。本例即是"test1"文件夹下的数据 210 | # 以下是进行预测时候的步骤 211 | ''' 进行预测时候的步骤: 212 | def test_image_predict_top1(): # 从训练好的模型中加载权重文件进行预测,这里只预测一个最优类别,函数返回值为"test1"图片文件夹下所有图片的汉字类别组成的列表 213 | def test_image_predict_top_k(): # 从训练好的模型中加载权重文件进行预测,这里预测top_5个最优类别,返回汉字形式的所有图片的预测标签组成的列表 214 | 215 | 1. def generator_list_of_imagepath() # 把path路径下的所有图片的路径加入到image_list列表中,该列表的元素是图片的路径,如./a/1.jpg 216 | 217 | 2. def label_of_directory() # 把汉字标签映射为数字标签{类别:索引},如{ 白:0, 高:1, ...}的形式 218 | 219 | 3. def load_image() # 加载图片,并对图片进行resize以及归一化处理 220 | 221 | 4. def get_label_predict_top1() # 获取预测图片的标签:输入的参数image是图片,返回值是该图片的标签,是一个数字 222 | def get_label_predict_top_k() # 获取预测图片的前top_k个最大可能的标签,返回值为top_k个数字组成的列表。 223 | 224 | 5. def get_key_from_value() # 把索引值等于字典的键值的对应汉字标签关键字找出来 225 | 226 | 227 | # test_image_predict_top_k()预测出的标签是列表形式,即每个标签中间隔开了,因此需要把列表形式的标签转换为汉字标签不间隔的字符串形式,然后存在csv文件中 228 | def save_csv(): 229 | def tran_list2str() # 把list转换为str形式,由于本来一张图片预测出来了5个标签,这5个标签构成每张图片的对应标签列表里面的的5个元素, 230 | # 这五个元素是分隔开的,最后提交的文件格式是无间隔的几个汉字。因此需要把这五个元素以字符串形式连接起来 231 | ''' 232 | 233 | # ==================================================================================================================================================================- 234 | # label for directory in disk 该函数功能是返回 把汉字文件夹名称作为关键字,汉字类别索引作为键值 的字典,即把汉字标签映射为数字标签{类别:索引},如{ 白:0, 高:1, ...}的形式 235 | def label_of_directory(directory): # 这里的directory最后被赋值为train_path = code_path + 'new_train_val_data/font_tra/' 236 | """ 237 | sorted for label indices 238 | return a dict for {'classes', 'range(len(classes))'} 239 | """ 240 | classes = [] 241 | for subdir in sorted(os.listdir(directory)): # 对汉字文件夹目录排序。这里的目录不是绝对路径或相对路径,而是列出汉字文件夹的名称,如"白"、"高",而不是./train/"白" 242 | if os.path.isdir( 243 | os.path.join(directory, subdir)): # 如果汉字文件夹是目录 os.path.join() 将多个路径组合后返回 244 | classes.append(subdir) # 就把汉字文件夹目录添加进classes列表中,这里的汉字文件夹目录是如 "白"的形式。 245 | 246 | num_classes = len(classes) # 统计训练集文件夹下的汉字文件夹有多少个,即汉字类别有多少。明显是100个 247 | class_indices = dict(zip(classes, range(len(classes)))) # 生成 汉字文件夹名称作为关键字,汉字类别索引作为键值 的字典,即{类别:索引} 248 | return class_indices # 函数返回值为{类别:索引}的字典形式,如{ 白:0, 高:1, ...}的形式 249 | 250 | 251 | # zip()的用法:zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。 252 | # >>>a = [1,2,3] 253 | # >>> b = [4,5,6] 254 | # >>> c = [4,5,6,7,8] 255 | # >>> zipped = zip(a,b) # 打包为元组的列表 256 | # [(1, 4), (2, 5), (3, 6)] 257 | 258 | # >>> dict(a='a', b='b', t='t') # 传入关键字 259 | # {'a': 'a', 'b': 'b', 't': 't'} 260 | # >>> dict(zip(['one', 'two', 'three'], [1, 2, 3])) # 映射函数方式来构造字典 261 | # {'three': 3, 'two': 2, 'one': 1} 262 | # >>> dict([('one', 1), ('two', 2), ('three', 3)]) # 可迭代对象方式来构造字典 263 | # {'three': 3, 'two': 2, 'one': 1} 264 | 265 | 266 | # ==================================================================================================================================================================- 267 | # get key from value in dict 该函数功能是把索引值等于字典的键值的对应汉字标签关键字找出来,因为后面做预测时候需要的是预测出汉字类别,而不是他对应的数字标签 268 | def get_key_from_value(dict, index): 269 | for keys, values in dict.items(): 270 | if values == index: 271 | return keys 272 | 273 | 274 | # ==================================================================================================================================================================- 275 | # 该函数功能是把path路径下的所有图片的路径加入到image_list列表中,该列表的元素是图片的路径,如./a/1.jpg 276 | # 这是把"test1"文件夹下的所有图片路径添加进列表 277 | def generator_list_of_imagepath(path): 278 | image_list = [] 279 | for image in os.listdir(path): 280 | if not image == '.DS_Store': 281 | image_list.append(path + image) 282 | return image_list 283 | 284 | 285 | # ==================================================================================================================================================================- 286 | # read image and resize to gray 加载图片,并对图片进行resize以及归一化处理。该函数只在预测没有标签的数据集时才用到,即本例的'test1'文件夹, 287 | # 而在读取训练集验证集的时候并没有用到该函数。训练的时候读取训练集以及测试集是使用ImageDataGenerator.flow_from_directory()来读取的。 288 | def load_image(image): 289 | img = Image.open(image) 290 | img = img.resize((128, 128)) 291 | img = np.array(img) # 难道这里之前的时候img不是array?答:是数组 292 | img = img / 255 293 | img = img.reshape((1,) + img.shape + (1,)) # reshape img to size(1, 128, 128, 1) 把二维图片矩阵变为四维的 294 | return img 295 | 296 | 297 | # ==================================================================================================================================================================- 298 | # 获取预测图片的标签:输入的参数image是图片,返回值是该图片的标签,是一个数字,因为中间把onehot形式的标签变为了一个数字,所以返回值是一个数字,将来会把它从字典映射回汉字标签 299 | def get_label_predict_top1(image, model): 300 | """ 301 | image = load_image(image), input image is a ndarray 302 | retturn best of label 303 | """ 304 | predict_proprely = model.predict(image) # 这里会预测出该张图片的数字标签,是onehot形式,而不是汉字标签 305 | predict_label = np.argmax(predict_proprely, axis=1) # 找出每行中的最大值位置的索引值,即是汉字标签映射的键值。即把onehot形式的输出值转化为单个数字的输出值 306 | return predict_label # 这里的标签是一个数字 307 | 308 | 309 | # ==================================================================================================================================================================- 310 | # 获取预测图片的前top_k个最大可能的标签,返回值为top_k个数字组成的列表。 311 | # 输入的参数image是图片,返回值是该图片的标签,是数字形式的标签列表,因为中间把onehot形式的标签变为了一个数字,所以返回值是五个预测数字标签的列表,将来会把它从字典映射回汉字标签 312 | def get_label_predict_top_k(image, model, top_k): 313 | """ 314 | image = load_image(image), input image is a ndarray 315 | return top-5 of label 316 | """ 317 | # array 2 list 把数组转化为列表 318 | predict_proprely = model.predict(image) # 这里会预测出该张图片的数字标签,是onehot形式,而不是汉字标签 319 | predict_list = list(predict_proprely[0]) # 因为输出维度为(None,100)的二维矩阵,所以a[0]取得是一行,而a[0][2]是第0行第2列的一个元素 320 | # 对于一张图片进行预测,它的输出应该是一个(1,100)矩阵,而不是一个(100,)序列向量。但是不添加[0]也行啊,那干嘛还添加个[0]呢? 321 | # 如果是(1,100)的矩阵,那么list(predict_proprely[0])就是[array([0,0,...,1,...,0])]的形式了,咋办?说到底还是一个序列向量。 322 | min_label = min(predict_list) # 找出标签中的最小值,在下面的程序中会用到 323 | label_k = [] 324 | for i in range(top_k): # 循环top_k次,每次找出predict_list中的最大值的索引值,然后把该索引值对应的值(即最大值)从列表中remove 325 | label = np.argmax(predict_list) # 把predict_list中的最大值的索引值赋给label 326 | predict_list.remove(predict_list[label]) # 把最大值的索引值对应的值(即最大值)从predict_list列表中remove 327 | predict_list.insert(label, min_label) # 在移除最大值的位置处,把列表中的最小值插入到该位置 328 | label_k.append(label) # 把最大值的索引值添加到label_k列表中 329 | return label_k # 经过for循环,最终label_k列表中会有top_k个值,即softmax输出的值从大到小排列的top_k个值的索引值。这些索引值将来就会映射为汉字标签 330 | 331 | 332 | # ==================================================================================================================================================================- 333 | # 从训练好的模型中加载权重文件进行预测,这里只预测一个最优类别,函数返回值为"test1"图片文件夹下所有图片的汉字类别组成的列表 334 | def test_image_predict_top1(model, test_image_path, directory): 335 | model.load_weights(WEIGHTS_PATH) # 加载训练好的权重文件到模型 336 | image_list = generator_list_of_imagepath(test_image_path) # 把测试集(这里指的是"./test1/"文件夹)要预测的图片的路径加入到image_list列表中 337 | 338 | predict_label = [] 339 | class_indecs = label_of_directory(directory) # {类别:索引}的字典class_indecs,即把汉字类别映射为数字索引 340 | for image in image_list: # 遍历image_list列表中的每个元素,即每张图片的路径,如./a/1.jpg 341 | img = load_image(image) # 加载一张图片,并做resize和归一化处理 342 | label_index = get_label_predict_top1(img, model) # 获取预测图片的标签,这里的标签是一个数字,将来会把它从字典映射回汉字标签 343 | label = get_key_from_value(class_indecs, 344 | label_index) # 当label_index和class_indecs字典的value值相等时,返回class_indecs的keys。即找出预测出的数字标签对应的汉字 345 | predict_label.append(label) # 将一张图片预测出的汉字形式的类别(标签)加入到predict_label列表中 346 | return predict_label # 函数返回值为整个图片文件夹下的图片的汉字类别 347 | 348 | 349 | # ==================================================================================================================================================================- 350 | # 从训练好的模型中加载权重文件进行预测,这里预测top_5个最优类别,返回汉字形式的所有图片的预测标签组成的列表 351 | def test_image_predict_top_k(modle, test_image_path, directory, top_k): 352 | model.load_weights(WEIGHTS_PATH) # 由于训练完成后会生成权重文件,因此这里进行预测的时候就得加载训练后的权重文件 353 | image_list = generator_list_of_imagepath(test_image_path) # 把测试集(这里指的是"./test1/"文件夹)要预测的图片的路径加入到image_list列表中 354 | 355 | predict_label = [] 356 | class_indecs = label_of_directory(directory) # {类别:索引}的字典class_indecs,即把汉字类别映射为数字索引 357 | # 这个外循环是对文件夹下的所有图片组成的列表进行遍历 358 | for image in image_list: # 遍历image_list列表中的每个元素,即每张图片的路径,如./a/1.jpg 359 | img = load_image(image) # 加载一张图片,并做resize和归一化处理 360 | # return a list of label max->min 361 | label_index = get_label_predict_top_k(img, model, 362 | 5) # 获取预测图片的前top_k个最大概率的标签,这里的label_index是top_k个数字组成的列表,将来会把它从字典映射回汉字标签 363 | label_key_list = [] # 364 | # 下面这个内循环是对一张图片的五个预测标签进行遍历,把索引值标签转化为汉字标签 365 | for label in label_index: # 因为这里标签有5个候选值,因此需要把这5个候选值对应的汉字找出来,所以需要遍历每个标签索引值 366 | label_key = get_key_from_value(class_indecs, 367 | label) # 当label和class_indecs字典的value值相等时,返回class_indecs的keys。即找出预测出的数字标签对应的汉字 368 | label_key_list.append(str(label_key)) # 把索引值对应的关键字即汉字标签添加进label_key_list列表,该列表最终会有top_k个元素。 369 | # 注意这里需要把汉字转换为str。但是top1的时候他却没有转换为str? 后面有专门的tran_list2str()会把列表转换为str 370 | 371 | predict_label.append(label_key_list) # 把内循环得到的一张图片的预测标签(top_k个元素组成的列表)添加进predict_label列表 372 | 373 | return predict_label # 函数返回值为所有图片的预测标签组成的predict_label列表,该列表的每个元素也是列表,即一张图片的top_k个预测标签(汉字形式) 374 | 375 | 376 | # ==================================================================================================================================================================- 377 | # translate list to str in label 把list转换为str形式 378 | def tran_list2str(predict_list_label): 379 | new_label = [] 380 | for row in range(len(predict_list_label)): # 遍历所有图片的预测出的标签组成的列表 381 | str = "" # 这里不是表示成对双引号,而是指无空格,即双引号之间没有空格,即汉字之间没有分隔符 382 | for label in predict_list_label[row]: # 遍历一张图片的预测标签列表的top_k个预测标签 383 | str += label # 遍历完一次内循环后,str为 你是谁啊你 384 | new_label.append(str) # 然后把 你是谁啊你 添加进new_label列表 385 | return new_label # 返回值为[你是谁啊你,你是谁啊你,你是谁啊你,...]的形式 386 | 387 | 388 | # ==================================================================================================================================================================- 389 | # save filename , label as csv # 将图片名称以及其预测类别存为csv文件 390 | def save_csv(test_image_path, predict_label): 391 | image_list = generator_list_of_imagepath(test_image_path) # image_list是每张图片的路径组成的列表 392 | save_arr = np.empty((10000, 2), dtype=np.str) 393 | save_arr = pd.DataFrame(save_arr, columns=['filename', 'label']) 394 | predict_label = tran_list2str(predict_label) # 把图片的预测的汉字标签转换为str形式 395 | for i in range(len(image_list)): # 遍历测试集文件夹里面的图片个数次,即测试集有多少张图片,就遍历多少次 396 | filename = image_list[i].split('/')[-1] # 对第i张图片的路径字符串进行分割,取路径字符串最后一个'/'分隔符后面的字符串(即图片名称,如1.jpg),赋给filename 397 | save_arr.values[i, 0] = filename # 把第张图片文件名图片文件名赋给save_arr的第[i,0]个元素,即图片文件名称置于第0列 398 | save_arr.values[i, 1] = predict_label[i] # 把第张图片的预测标签赋给save_arr的第[i,1]个元素,即图片预测的标签置于第1列 399 | save_arr.to_csv('submit_test.csv', decimal=',', encoding='utf-8', index=False, index_label=False) 400 | print('submit_test.csv have been write, locate is :', os.getcwd()) # os.getcwd() 方法用于返回当前工作目录 401 | 402 | 403 | # # 预测一个正确的:top1 404 | # def predict_top1(test_image_path): 405 | # model=train_model(path,istrain) 406 | # model.load_weights(weights_path) 407 | # predict_image=[] 408 | # for image in test_image_path: # 遍历测试集里面的所有图片 409 | # predict=model.predict(image) 410 | # predict_image.append(predict) 411 | # return [image,predict_image] 412 | 413 | 414 | # # 预测5个正确的:top5 415 | # def predict_top5(path): 416 | # model=train_model(path,istrain) 417 | # model.load_weights(weights_path) 418 | # model.predict(x_test) 419 | 420 | 421 | # # 输出结果文件 422 | # def save_csv(path): 423 | # data_arr=np.array(predict_top1(test_image_path),dtype=np.str) 424 | # data_arr = np.empty((10000, 2), dtype=np.str) 425 | # data_arr=pd.DataFrame(data_arr,columns=['filename','label']) 426 | # data_arr.to_csv('submit_test.csv', decimal=',', encoding='utf-8', index=False, index_label=False) 427 | # print('submit_test.csv have been write, locate is :', os.getcwd()) # os.getcwd() 方法用于返回当前工作目录 428 | 429 | 430 | if __name__ == '__main__': 431 | code_path = 'E:/BaiduNetdiskDownload/TMD/' 432 | train_path = code_path + 'new_train_val_data/font_tra/' 433 | val_path = code_path + 'new_train_val_data/font_val/' 434 | test_image_path = 'test1/' 435 | 436 | num_classes = 100 437 | BATCH_SIZE = 128 438 | WEIGHTS_PATH = 'best_weights_hanzi.hdf5' 439 | max_Epochs = 100 440 | 441 | # ImageDataGenerator就是图片数据生成器,下面函数是构建训练集生成器 442 | train_datagen = ImageDataGenerator( # https://blog.csdn.net/weiwei9363/article/details/78635674 443 | rescale=1. / 255, # rescale值在执行其他处理前乘到整个图像上,这个值定为0~1之间的数 444 | # horizontal_flip=True #进行随机水平翻转 445 | width_shift_range=0.15, # 宽度偏移,随机沿着水平方向,以图像的宽小部分百分比为变化范围进行平移; 446 | height_shift_range=0.15 # 高度偏移,随机沿着垂直方向,以图像的高小部分百分比为变化范围进行平移; 447 | ) 448 | 449 | val_datagen = ImageDataGenerator( # 对验证集只需要对像素值缩放1/255就行 450 | rescale=1. / 255 451 | ) 452 | 453 | # 利用keras中image.ImageDataGenerator.flow_from_directory()实现从文件夹中提取图片和进行简单归一化处理,以及数据扩充。但是标签没有读进去啊? 454 | # 答:自动读取了标签,每一个子文件夹都会被认为是一个新的类。(类别的顺序将按照字母表顺序映射到标签值)。通过属性class_indices可获得文件夹名与类的序号的对应字典。 455 | # 所以使用flow_from_directory()给定训练集或者验证集路径之后,他就自动读取数据及其标签,不用管它怎么分的类。 456 | # 对训练集图像进行数据扩充 利用上面构建的训练集生成器来对图片进行归一化以及数据扩充。 457 | # 但是到底有没有扩充呢?图片数量并没有增加啊?难道是只增加在内存中,并没有存到本地文件夹里面? 458 | # 答:他没有实现扩充后的存储操作,应该是只做了数据扩充,却没有执行扩充让他读取进数据集。 459 | train_generator = train_datagen.flow_from_directory( # https://blog.csdn.net/u012193416/article/details/79368855 460 | 461 | train_path, # 训练集的路径 462 | target_size=(128, 128), # 在这里即对训练集的图片进行缩放,缩放为(128,128) 463 | batch_size=BATCH_SIZE, # 在这里对训练设置batch_size 464 | color_mode='grayscale', 465 | class_mode='categorical', # class_mode参数决定了返回的标签数组的形式, "categorical"会返回2D的one-hot编码标签 466 | # save_to_dir=code_path+ 'gen/' , 467 | # save_prefix='gen' 468 | ) 469 | ''' http://keras-cn.readthedocs.io/en/latest/preprocessing/image/#imagedatagenerator 470 | 1.flow_from_directory() 471 | flow_from_directory(directory): 以文件夹路径为参数,生成经过数据提升/归一化后的数据,在一个无限循环中无限产生batch数据 472 | 473 | directory: 目标文件夹路径,对于每一个类,该文件夹都要包含一个子文件夹.子文件夹中任何JPG、PNG、BNP、PPM的图片都会被生成器使用.详情请查看此脚本 474 | target_size: 整数tuple,默认为(256, 256). 图像将被resize成该尺寸 475 | color_mode: 颜色模式,为"grayscale","rgb"之一,默认为"rgb".代表这些图片是否会被转换为单通道或三通道的图片. 476 | classes: 可选参数,为子文件夹的列表,如['dogs','cats']默认为None. 若未提供,则该类别列表将从directory下的子文件夹名称/结构自动推断。每一个子文件夹都会被认为是一个新的类。(类别的顺序将按照字母表顺序映射到标签值)。通过属性class_indices可获得文件夹名与类的序号的对应字典。 477 | class_mode: "categorical", "binary", "sparse"或None之一. 默认为"categorical. 该参数决定了返回的标签数组的形式, "categorical"会返回2D的one-hot编码标签,"binary"返回1D的二值标签."sparse"返回1D的整数标签,如果为None则不返回任何标签, 生成器将仅仅生成batch数据, 这种情况在使用model.predict_generator()和model.evaluate_generator()等函数时会用到. 478 | batch_size: batch数据的大小,默认32 479 | shuffle: 是否打乱数据,默认为True 480 | seed: 可选参数,打乱数据和进行变换时的随机数种子 481 | save_to_dir: None或字符串,该参数能让你将提升后的图片保存起来,用以可视化 482 | save_prefix:字符串,保存提升后图片时使用的前缀, 仅当设置了save_to_dir时生效 483 | save_format:"png"或"jpeg"之一,指定保存图片的数据格式,默认"jpeg" 484 | flollow_links: 是否访问子文件夹中的软链接 485 | 486 | 2.flow() 487 | flow(self, X, y, batch_size=32, shuffle=True, seed=None, save_to_dir=None, save_prefix='', save_format='png'):接收numpy数组和标签为参数,生成经过数据提升或标准化后的batch数据,并在一个无限循环中不断的返回batch数据 488 | 489 | x:样本数据,秩应为4.在黑白图像的情况下channel轴的值为1,在彩色图像情况下值为3 490 | y:标签 491 | batch_size:整数,默认32 492 | shuffle:布尔值,是否随机打乱数据,默认为True 493 | save_to_dir:None或字符串,该参数能让你将提升后的图片保存起来,用以可视化 494 | save_prefix:字符串,保存提升后图片时使用的前缀, 仅当设置了save_to_dir时生效 495 | save_format:"png"或"jpeg"之一,指定保存图片的数据格式,默认"jpeg" 496 | yields:形如(x,y)的tuple,x是代表图像数据的numpy数组.y是代表标签的numpy数组.该迭代器无限循环. 497 | seed: 整数,随机数种子 498 | ''' 499 | 500 | # 以上用的是ImageDataGenerator.flow_from_directory()函数,该函数对图片进行简单归一化处理。本程序并没有使用数据扩充,因为总样本还是40000个。数据生成后并没有保存下来读取。 501 | # 还有一种是使用ImageDataGenerator.flow()函数,该函数将会返回一个生成器,这个生成器用来扩充数据,每次都会产生batch_size个样本。 https://blog.csdn.net/weiwei9363/article/details/78635674 502 | 503 | # 对验证集进行数据扩充 但是标签没有读进去啊? 504 | #答:自动读取了标签,因为读取每个文件夹下的图片就已经知道了他们分别是哪一个类别。所以使用flow_from_directory()给定训练集或者验证集路径之后,他就自动读取数据及其标签,不用管它怎么分的类。 505 | val_generator = val_datagen.flow_from_directory( 506 | val_path, 507 | target_size=(128, 128), 508 | batch_size=BATCH_SIZE, 509 | color_mode='grayscale', 510 | class_mode='categorical' # class_mode参数决定了返回的标签数组的形式, "categorical"会返回2D的one-hot编码标签 511 | ) 512 | 513 | simple_model = build_model(num_classes) 514 | print(simple_model.summary()) 515 | 516 | print("=====start train image of epoch=====") 517 | 518 | model_history = model_train(simple_model) 519 | 520 | print("=====show acc and loss of train and val====") 521 | # draw_loss_acc(model_history) 522 | 523 | print("=====test label=====") 524 | simple_model.load_weights(WEIGHTS_PATH) 525 | model = simple_model 526 | predict_label = test_image_predict_top_k(model, code_path + test_image_path, train_path, 5) 527 | 528 | print("=====csv save=====") 529 | save_csv(code_path + test_image_path, predict_label) 530 | 531 | print("====done!=====") 532 | -------------------------------------------------------------------------------- /project/汉字识别/tinymind_chinese_font_recognition/split_dataset.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 从数据集中划分train和validation两个文件 4 | train_test_split_ratio=0.1 or 0.2 5 | Tree目录: 6 | data: 7 | train: 8 | folder1 9 | ...... 10 | folder529 11 | validation: 12 | folder1 13 | ...... 14 | folder529 15 | """ 16 | import os 17 | import random 18 | import PIL.Image as Image # PIL是Python Imaging Library 图片库 19 | 20 | 21 | # 检查路径下面是否都是文件 22 | def isfile(path): 23 | for folder in os.listdir(path): # os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。这个列表以字母顺序 24 | if not os.path.isdir(path+folder): 25 | os.remove(path+folder) 26 | 27 | 28 | # 建立文件夹 29 | def mkdir(path): 30 | """ 31 | if folder is exists, or make new dir 32 | """ 33 | isexists = os.path.exists(path) 34 | if not isexists: 35 | os.makedirs(path) 36 | print(path) 37 | print('success') 38 | return True 39 | else: 40 | print(path) 41 | print('folder is exist') 42 | return False 43 | 44 | 45 | # 返回文件列表 ----- 46 | def eachFile(filepath): 47 | pathDir = os.listdir(filepath) # /train路径下的文件和文件夹,本例就是100个类别汉字文件夹 48 | child_file_name = [] 49 | full_child_file_list = [] 50 | for allDir in pathDir: #遍历100个类别的汉字文件夹,并不遍历汉字文件夹里面的汉字图片。allDir就是分别取值100个汉字文件夹的名称 51 | if not allDir == '.DS_Store': # 若allDir不为'.DS_Store',则执行下面的合并路径命令。我感觉把'.DS_Store'随便改成别的字符串也能行的样子。 52 | child = os.path.join(filepath, allDir) # os.path.join()将多个路径组合后返回。本句话是创建child路径为每个汉字类别的路径 53 | 54 | full_child_file_list.append(child) # 将每个汉字类别路径添加到full_child_file_list列表 55 | child_file_name.append(allDir) # 将每个汉字文件夹的名称添加到child_file_name 56 | return full_child_file_list, child_file_name # 返回每个汉字类别路径所组成的full_child_file_list列表,和每个汉字文件夹的名称组成的child_file_name列表 57 | 58 | 59 | # 转移ratio文件 60 | def move_ratio(data_list, original_str, replace_str): # 具体作用就是更换路径吗?我感觉这函数名称取得真烂,这函数名称和功能有任何关系吗? 61 | # 涉及到复制移动图片 62 | for x in data_list: 63 | fromImage = Image.open(x) 64 | x = x.replace(original_str, replace_str) # x是图片,x.replace(original_str, replace_str)意义何在?---把原来文件夹的路径替换为现在的路径,然后再保存图片 65 | fromImage.save(x) 66 | 67 | 68 | if __name__ == '__main__': 69 | 70 | # data_path = 'C:/Users/Jack/Documents/jupyter/font_recognition/chinese_font_recognition/train/' # 原始数据存放地址 71 | # data_tra_path = 'C:/Users/Jack/Documents/jupyter/font_recognition/chinese_font_recognition/new_train_val_data/font_tra/' # new_train_data新取的名 72 | # data_val_path = 'C:/Users/Jack/Documents/jupyter/font_recognition/chinese_font_recognition/new_train_val_data/font_val/' 73 | 74 | data_path = 'E:/BaiduNetdiskDownload/TMD/train/' # 原始数据存放地址 75 | data_tra_path = 'E:/BaiduNetdiskDownload/TMD/new_train_val_data/font_tra/' # new_train_data新取的名 76 | data_val_path = 'E:/BaiduNetdiskDownload/TMD/new_train_val_data/font_val/' 77 | 78 | full_child_file, child_file = eachFile(data_path) # 返回每个汉字类别路径所组成的full_child_file列表,和每个汉字文件夹的名称组成的child_file列表 79 | 80 | # 建立相应的文件夹 -------建立了这么多文件夹,后面也没用到啊,那建立了做啥?----用到了,后面用到的路径在前面已经以字符串形式赋值了。 81 | for i in child_file: # 遍历每个汉字文件夹的名称组成的child_file列表 82 | tra_path = data_tra_path + '/' + str(i) # str(i)是汉字文件夹的名称,感觉中间的这个'/'可以去掉 83 | mkdir(tra_path) # 建立训练集文件夹 84 | val_path = data_val_path + '/' + str(i) 85 | mkdir(val_path) # 建立验证集文件夹 86 | 87 | # 划分train和val 88 | test_train_split_ratio = 0.9 89 | 90 | 91 | # 注意一点:不只/train/是路径,/train/a.jpg 也是路径,读取这个路径,也就是读取这个图片文件了 92 | for i in full_child_file: # i遍历每个汉字类别路径所组成的full_child_file列表 93 | pic_dir, pic_name = eachFile(i) # 返回每个汉字类别路径下的所有汉字图片所组成的pic_dir列表,和每个汉字类别路径下的所有汉字图片的名称组成的pic_name列表 94 | #由于eachFile()中有一句是child = os.path.join(filepath, allDir),这一句是合并路径,所以pic_dir应该是汉字图片的路径,如./白/hdjahdksaj.jpg,而并不是读取了图片 95 | random.shuffle(pic_dir) # shuffle :洗牌,搅乱 96 | train_list = pic_dir[0:int(test_train_split_ratio * len(pic_dir))] # pic_dir[0 : int(0.9*400)],即pic_dir[0:360],即把每个汉字类别文件夹下的前360个汉字路径为作为训练集 97 | val_list = pic_dir[int(test_train_split_ratio * len(pic_dir)):] #pic_dir[int(0.9*400) : ],即pic_dir[360:],即把每个汉字类别文件夹下的第360到400的汉字路径为作为验证集 98 | # 以上还仅仅只是对于一个汉字文件夹做的处理,i遍历full_child_file才是对所有的汉字类别做处理。 99 | # 问题:最后的训练集train_list不是使用的append,对于每次i,train_list,val_list都是重新赋值? 100 | # train_move, val_move 101 | print('proprecessing %s' % i) 102 | # print('train_list:',train_list) 103 | # # 由于下面代码在for循环中,这意思是对每个类别汉字文件夹的每张图片都要做repalce操作,是更换每张图片名称的操作吗? 104 | move_ratio(train_list, 'train', 'new_train_val_data/font_tra') # train_list是某个汉字文件夹的前360张图片路径列表。 105 | # 把原来train文件夹下的某类别汉字的前360张图片复制到new_train_val_data/font_tra路径下 106 | move_ratio(val_list, 'train', 'new_train_val_data/font_val') # val_list 是某个汉字文件夹的后 40张图片路径列表 107 | 108 | -------------------------------------------------------------------------------- /project/汉字识别/tinymind_chinese_font_recognition/tinymind_predict.py: -------------------------------------------------------------------------------- 1 | from keras.preprocessing.image import ImageDataGenerator 2 | from keras.applications.resnet50 import ResNet50 3 | from keras.layers import Conv2D, MaxPool2D, AveragePooling2D, Activation, Embedding 4 | from keras.layers import Flatten, Dense, BatchNormalization, Dropout, PReLU, Lambda 5 | from keras.models import Model, Input 6 | from keras.callbacks import LearningRateScheduler, ModelCheckpoint, TensorBoard 7 | from keras.optimizers import SGD 8 | import keras.backend as K 9 | from PIL import Image 10 | import matplotlib.pyplot as plt 11 | import pandas as pd 12 | import numpy as np 13 | import os 14 | 15 | width = 32 16 | height = 32 17 | 18 | # bn + prelu 19 | def bn_prelu(x): 20 | x = BatchNormalization()(x) 21 | x = PReLU()(x) 22 | return x 23 | 24 | # def build_model(out_dims, input_shape=(height, width, 1)): 25 | # inputs_dim = Input(input_shape) # inputs_dim的type为tf.Tensor,shape为(None,128,128,1),dtype为float32 26 | # # model=Sequential() 27 | # # model.add(Conv2D(32,(3,3),strides=(2, 2), padding='valid')(inputs_dim)) 28 | 29 | # x = Conv2D(32, (3, 3), strides=(2, 2), padding='valid')( 30 | # inputs_dim) # “valid”代表只进行有效的卷积,即对边界数据不处理。“VALID”发现余下的窗口不够卷积核大小了所以就把后面的列直接去掉了 31 | # # conv2D(卷积核个数,卷积核尺寸,步长,填充方式)(输入尺寸),其中卷积核个数即输出的深度,本次卷积后shape为(None,63,63,32) (32) 32 | 33 | # x = bn_prelu(x) 34 | # x = Conv2D(32, (3, 3), strides=(1, 1), padding='valid')( 35 | # x) # “valid”代表只进行有效的卷积,即对边界数据不处理。“VALID”发现余下的窗口不够卷积核大小了所以就把后面的列直接去掉了 36 | # # conv2D(卷积核个数,卷积核尺寸,步长,填充方式)(输入尺寸),其中卷积核个数即输出的深度,本次卷积后shape为(None,63,63,32) (30) 37 | # x = bn_prelu(x) 38 | # x = Conv2D(32, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,61,61,32) (28) 39 | # x = bn_prelu(x) 40 | # x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,30,30,32) (14) 41 | 42 | # x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,28,28,64) (12) 43 | # x = bn_prelu(x) 44 | # x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,26,26,64) (None,10,10,64) 45 | # x = bn_prelu(x) 46 | # x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,13,13,64) (None,5,5,64) 47 | 48 | # x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,11,11,128) (None,3,3,128) 49 | # x = bn_prelu(x) 50 | # # x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,5,5,128) (None,1,1,128) 51 | 52 | # # x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,3,3,128) 53 | # # x = bn_prelu(x) 54 | # x = AveragePooling2D(pool_size=(2, 2))(x) # 本次平均池化后,shape==(None,1,1,128) 55 | 56 | # x_flat = Flatten()(x) # shape==(None,1*1*128)即(None,128) 57 | 58 | # fc1 = Dense(512)(x_flat) # Dense(全连接层),512是该层的输出维度,x_flat是该层的输入维度,则输出shape==(None,512) 59 | # fc1 = bn_prelu(fc1) 60 | # dp_1 = Dropout(0.3)(fc1) 61 | 62 | # fc2 = Dense(out_dims)(dp_1) # 后面会赋值out_dims==100,即100个汉字类别,输出shape==(None,100),None表示样本数 63 | # fc2 = Activation('softmax')(fc2) 64 | 65 | # model = Model(inputs=inputs_dim, outputs=fc2) 66 | # return model 67 | 68 | 69 | #####搭建模型 70 | def build_model(out_dims, input_shape=(32, 32, 1)): 71 | inputs_dim = Input(input_shape) # inputs_dim的type为tf.Tensor,shape为(None,128,128,1),dtype为float32 72 | # model=Sequential() 73 | # model.add(Conv2D(32,(3,3),strides=(2, 2), padding='valid')(inputs_dim)) 74 | 75 | x = Conv2D(32, (3, 3), strides=(1, 1), padding='valid')( 76 | inputs_dim) # “valid”代表只进行有效的卷积,即对边界数据不处理。“VALID”发现余下的窗口不够卷积核大小了所以就把后面的列直接去掉了 77 | # conv2D(卷积核个数,卷积核尺寸,步长,填充方式)(输入尺寸),其中卷积核个数即输出的深度,本次卷积后shape为(None,63,63,32) (30) 78 | 79 | x = bn_prelu(x) 80 | x = Conv2D(32, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,61,61,32) (28) 81 | x = bn_prelu(x) 82 | x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,30,30,32) (14) 83 | 84 | x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,28,28,64) (12) 85 | x = bn_prelu(x) 86 | x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,26,26,64) (None,10,10,64) 87 | x = bn_prelu(x) 88 | x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,13,13,64) (None,5,5,64) 89 | 90 | x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,11,11,128) (None,3,3,128) 91 | x = bn_prelu(x) 92 | # x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,5,5,128) (None,1,1,128) 93 | 94 | # x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,3,3,128) 95 | # x = bn_prelu(x) 96 | x = AveragePooling2D(pool_size=(2, 2))(x) # 本次平均池化后,shape==(None,1,1,128) 97 | 98 | x_flat = Flatten()(x) # shape==(None,1*1*128)即(None,128) 99 | 100 | fc1 = Dense(512)(x_flat) # Dense(全连接层),512是该层的输出维度,x_flat是该层的输入维度,则输出shape==(None,512) 101 | fc1 = bn_prelu(fc1) 102 | dp_1 = Dropout(0.3)(fc1) 103 | 104 | fc2 = Dense(out_dims)(dp_1) # 后面会赋值out_dims==100,即100个汉字类别,输出shape==(None,100),None表示样本数 105 | fc2 = Activation('softmax')(fc2) 106 | 107 | model = Model(inputs=inputs_dim, outputs=fc2) 108 | return model 109 | 110 | # ==================================================================================================================================================================- 111 | # label for directory in disk 该函数功能是返回 把汉字文件夹名称作为关键字,汉字类别索引作为键值 的字典,即把汉字标签映射为数字标签{类别:索引},如{ 白:0, 高:1, ...}的形式 112 | def label_of_directory(directory): # 这里的directory最后被赋值为train_path = code_path + 'new_train_val_data/font_tra/' 113 | """ 114 | sorted for label indices 115 | return a dict for {'classes', 'range(len(classes))'} 116 | """ 117 | classes = [] 118 | for subdir in sorted(os.listdir(directory)): # 对汉字文件夹目录排序。这里的目录不是绝对路径或相对路径,而是列出汉字文件夹的名称,如"白"、"高",而不是./train/"白" 119 | if os.path.isdir( 120 | os.path.join(directory, subdir)): # 如果汉字文件夹是目录 os.path.join() 将多个路径组合后返回 121 | classes.append(subdir) # 就把汉字文件夹目录添加进classes列表中,这里的汉字文件夹目录是如 "白"的形式。 122 | 123 | num_classes = len(classes) # 统计训练集文件夹下的汉字文件夹有多少个,即汉字类别有多少。明显是100个 124 | class_indices = dict(zip(classes, range(len(classes)))) # 生成 汉字文件夹名称作为关键字,汉字类别索引作为键值 的字典,即{类别:索引} 125 | return class_indices # 函数返回值为{类别:索引}的字典形式,如{ 白:0, 高:1, ...}的形式 126 | 127 | # ==================================================================================================================================================================- 128 | # get key from value in dict 该函数功能是把索引值等于字典的键值的对应汉字标签关键字找出来,因为后面做预测时候需要的是预测出汉字类别,而不是他对应的数字标签 129 | def get_key_from_value(dict, index): 130 | for keys, values in dict.items(): 131 | if values == index: 132 | return keys 133 | 134 | 135 | # ==================================================================================================================================================================- 136 | # 该函数功能是把path路径下的所有图片的路径加入到image_list列表中,该列表的元素是图片的路径,如./a/1.jpg 137 | # 这是把"test1"文件夹下的所有图片路径添加进列表 138 | def generator_list_of_imagepath(path): 139 | image_list = [] 140 | for image in sorted(os.listdir(path)): 141 | if not image == '.DS_Store': 142 | image_list.append(path + image) 143 | return image_list 144 | 145 | 146 | # ==================================================================================================================================================================- 147 | # read image and resize to gray 加载图片,并对图片进行resize以及归一化处理。该函数只在预测没有标签的数据集时才用到,即本例的'test1'文件夹, 148 | # 而在读取训练集验证集的时候并没有用到该函数。训练的时候读取训练集以及测试集是使用ImageDataGenerator.flow_from_directory()来读取的。 149 | def load_image(image): 150 | img = Image.open(image) 151 | img = img.resize((height, width)) 152 | img = np.array(img) # 难道这里之前的时候img不是array?答:是数组 153 | img = img / 255 154 | img = img.reshape((1,) + img.shape + (1,)) # reshape img to size(1, 128, 128, 1) 把二维图片矩阵变为四维的 155 | return img 156 | 157 | # ==================================================================================================================================================================- 158 | # translate list to str in label 把list转换为str形式 159 | def tran_list2str(predict_list_label): 160 | new_label = [] 161 | for row in range(len(predict_list_label)): # 遍历所有图片的预测出的标签组成的列表 162 | str = "" # 这里不是表示成对双引号,而是指无空格,即双引号之间没有空格,即汉字之间没有分隔符 163 | for label in predict_list_label[row]: # 遍历一张图片的预测标签列表的top_k个预测标签 164 | str += label # 遍历完一次内循环后,str为 你是谁啊你 165 | new_label.append(str) # 然后把 你是谁啊你 添加进new_label列表 166 | return new_label # 返回值为[你是谁啊你,你是谁啊你,你是谁啊你,...]的形式 167 | 168 | # ==================================================================================================================================================================- 169 | # 从训练好的模型中加载权重文件进行预测,这里只预测一个最优类别,函数返回值为"test1"图片文件夹下所有图片的汉字类别组成的列表 170 | def test_image_predict_top1(model, test_image_path, directory): 171 | model.load_weights(WEIGHTS_PATH) # 加载训练好的权重文件到模型 172 | image_list = generator_list_of_imagepath(test_image_path) # 把测试集(这里指的是"./test1/"文件夹)要预测的图片的路径加入到image_list列表中 173 | 174 | predict_label = [] 175 | class_indecs = label_of_directory(directory) # {类别:索引}的字典class_indecs,即把汉字类别映射为数字索引 176 | for image in image_list: # 遍历image_list列表中的每个元素,即每张图片的路径,如./a/1.jpg 177 | img = load_image(image) # 加载一张图片,并做resize和归一化处理 178 | label_index = get_label_predict_top1(img, model) # 获取预测图片的标签,这里的标签是一个数字,将来会把它从字典映射回汉字标签 179 | label = get_key_from_value(class_indecs, 180 | label_index) # 当label_index和class_indecs字典的value值相等时,返回class_indecs的keys。即找出预测出的数字标签对应的汉字 181 | predict_label.append(label) # 将一张图片预测出的汉字形式的类别(标签)加入到predict_label列表中 182 | return predict_label # 函数返回值为整个图片文件夹下的图片的汉字类别 183 | 184 | 185 | # ==================================================================================================================================================================- 186 | # 从训练好的模型中加载权重文件进行预测,这里预测top_5个最优类别,返回汉字形式的所有图片的预测标签组成的列表 187 | def test_image_predict_top_k(modle, test_image_path, directory, top_k): 188 | model.load_weights(WEIGHTS_PATH) # 由于训练完成后会生成权重文件,因此这里进行预测的时候就得加载训练后的权重文件 189 | image_list = generator_list_of_imagepath(test_image_path) # 把测试集(这里指的是"./test1/"文件夹)要预测的图片的路径加入到image_list列表中 190 | 191 | predict_label = [] 192 | class_indecs = label_of_directory(directory) # {类别:索引}的字典class_indecs,即把汉字类别映射为数字索引 193 | # 这个外循环是对文件夹下的所有图片组成的列表进行遍历 194 | for image in image_list: # 遍历image_list列表中的每个元素,即每张图片的路径,如./a/1.jpg 195 | img = load_image(image) # 加载一张图片,并做resize和归一化处理 196 | # return a list of label max->min 197 | label_index = get_label_predict_top_k(img, model, 198 | 5) # 获取预测图片的前top_k个最大概率的标签,这里的label_index是top_k个数字组成的列表,将来会把它从字典映射回汉字标签 199 | label_key_list = [] # 200 | # 下面这个内循环是对一张图片的五个预测标签进行遍历,把索引值标签转化为汉字标签 201 | for label in label_index: # 因为这里标签有5个候选值,因此需要把这5个候选值对应的汉字找出来,所以需要遍历每个标签索引值 202 | label_key = get_key_from_value(class_indecs, 203 | label) # 当label和class_indecs字典的value值相等时,返回class_indecs的keys。即找出预测出的数字标签对应的汉字 204 | label_key_list.append(str(label_key)) # 把索引值对应的关键字即汉字标签添加进label_key_list列表,该列表最终会有top_k个元素。 205 | # 注意这里需要把汉字转换为str。但是top1的时候他却没有转换为str? 后面有专门的tran_list2str()会把列表转换为str 206 | 207 | predict_label.append(label_key_list) # 把内循环得到的一张图片的预测标签(top_k个元素组成的列表)添加进predict_label列表 208 | 209 | return predict_label # 函数返回值为所有图片的预测标签组成的predict_label列表,该列表的每个元素也是列表,即一张图片的top_k个预测标签(汉字形式) 210 | 211 | # ==================================================================================================================================================================- 212 | # 获取预测图片的标签:输入的参数image是图片,返回值是该图片的标签,是一个数字,因为中间把onehot形式的标签变为了一个数字,所以返回值是一个数字,将来会把它从字典映射回汉字标签 213 | def get_label_predict_top1(image, model): 214 | """ 215 | image = load_image(image), input image is a ndarray 216 | retturn best of label 217 | """ 218 | predict_proprely = model.predict(image) # 这里会预测出该张图片的数字标签,是onehot形式,而不是汉字标签 219 | predict_label = np.argmax(predict_proprely, axis=1) # 找出每行中的最大值位置的索引值,即是汉字标签映射的键值。即把onehot形式的输出值转化为单个数字的输出值 220 | return predict_label # 这里的标签是一个数字 221 | 222 | # ==================================================================================================================================================================- 223 | # 获取预测图片的前top_k个最大可能的标签,返回值为top_k个数字组成的列表。 224 | # 输入的参数image是图片,返回值是该图片的标签,是数字形式的标签列表,因为中间把onehot形式的标签变为了一个数字,所以返回值是五个预测数字标签的列表,将来会把它从字典映射回汉字标签 225 | def get_label_predict_top_k(image, model, top_k): 226 | """ 227 | image = load_image(image), input image is a ndarray 228 | return top-5 of label 229 | """ 230 | # array 2 list 把数组转化为列表 231 | predict_proprely = model.predict(image) # 这里会预测出该张图片的数字标签,是onehot形式,而不是汉字标签 232 | predict_list = list(predict_proprely[0]) # 因为输出维度为(None,100)的二维矩阵,所以a[0]取得是一行,而a[0][2]是第0行第2列的一个元素 233 | # 对于一张图片进行预测,它的输出应该是一个(1,100)矩阵,而不是一个(100,)序列向量。但是不添加[0]也行啊,那干嘛还添加个[0]呢? 234 | # 如果是(1,100)的矩阵,那么list(predict_proprely[0])就是[array([0,0,...,1,...,0])]的形式了,咋办?说到底还是一个序列向量。 235 | min_label = min(predict_list) # 找出标签中的最小值,在下面的程序中会用到 236 | label_k = [] 237 | for i in range(top_k): # 循环top_k次,每次找出predict_list中的最大值的索引值,然后把该索引值对应的值(即最大值)从列表中remove 238 | label = np.argmax(predict_list) # 把predict_list中的最大值的索引值赋给label 239 | predict_list.remove(predict_list[label]) # 把最大值的索引值对应的值(即最大值)从predict_list列表中remove 240 | predict_list.insert(label, min_label) # 在移除最大值的位置处,把列表中的最小值插入到该位置 241 | label_k.append(label) # 把最大值的索引值添加到label_k列表中 242 | return label_k # 经过for循环,最终label_k列表中会有top_k个值,即softmax输出的值从大到小排列的top_k个值的索引值。这些索引值将来就会映射为汉字标签 243 | 244 | # ==================================================================================================================================================================- 245 | # save filename , label as csv # 将图片名称以及其预测类别存为csv文件 246 | def save_csv(test_image_path, predict_label): 247 | image_list = generator_list_of_imagepath(test_image_path) # image_list是每张图片的路径组成的列表 248 | save_arr = np.empty((10000, 2), dtype=np.str) 249 | save_arr = pd.DataFrame(save_arr, columns=['filename', 'label']) 250 | print(predict_label) 251 | predict_label = tran_list2str(predict_label) # 把图片的预测的汉字标签转换为str形式 252 | for i in range(len(image_list)): # 遍历测试集文件夹里面的图片个数次,即测试集有多少张图片,就遍历多少次 253 | filename = image_list[i].split('/')[-1] # 对第i张图片的路径字符串进行分割,取路径字符串最后一个'/'分隔符后面的字符串(即图片名称,如1.jpg),赋给filename 254 | save_arr.values[i, 0] = filename # 把第张图片文件名图片文件名赋给save_arr的第[i,0]个元素,即图片文件名称置于第0列 255 | save_arr.values[i, 1] = predict_label[i] # 把第张图片的预测标签赋给save_arr的第[i,1]个元素,即图片预测的标签置于第1列 256 | save_arr.to_csv('submit_test.csv', decimal=',', encoding='utf-8', index=False, index_label=False) 257 | print('submit_test.csv have been write, locate is :', os.getcwd()) # os.getcwd() 方法用于返回当前工作目录 258 | 259 | 260 | 261 | 262 | # =============================================================================================== 263 | if __name__ == '__main__': 264 | code_path = '/home/zhz/Downloads/CPS-OCR-Engine-master/ocr/' 265 | 266 | # train_path = code_path + 'dataset_1/train' 267 | # val_path = code_path + 'dataset_1/test' 268 | train_path = "/media/zhz/6cff368c-1c54-4cba-aeff-afb6309a2a9a/ctw-baseline/data/new_train_data/" 269 | val_path = "/media/zhz/6cff368c-1c54-4cba-aeff-afb6309a2a9a/ctw-baseline/data/new_val_data/" 270 | 271 | test_image_path = 'test1/' 272 | 273 | # num_classes = 3755 274 | num_classes = 3273 275 | # num_classes = 2602 276 | # BATCH_SIZE = 64 277 | WEIGHTS_PATH = 'best_weights_hanzi.hdf5' 278 | # max_Epochs = 10000 279 | 280 | simple_model = build_model(num_classes) 281 | print(simple_model.summary()) 282 | 283 | print("=====test label=====") 284 | simple_model.load_weights(WEIGHTS_PATH) 285 | model = simple_model 286 | # predict_label = test_image_predict_top_k(model, code_path + test_image_path, train_path, 5) 287 | predict_label = test_image_predict_top1(model, code_path + test_image_path, train_path) 288 | 289 | print("=====csv save=====") 290 | save_csv(code_path + test_image_path, predict_label) 291 | 292 | print("====done!=====") 293 | 294 | -------------------------------------------------------------------------------- /project/汉字识别/tinymind_chinese_font_recognition/tinymind_split_train_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 从数据集中划分train和validation两个文件 4 | train_test_split_ratio=0.1 or 0.2 5 | Tree目录: 6 | data: 7 | train: 8 | folder1 9 | ...... 10 | folder529 11 | validation: 12 | folder1 13 | ...... 14 | folder529 15 | """ 16 | import os 17 | import random 18 | import PIL.Image as Image # PIL是Python Imaging Library 图片库 19 | 20 | 21 | # 检查路径下面是否都是文件 22 | def isfile(path): 23 | for folder in os.listdir(path): # os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。这个列表以字母顺序 24 | if not os.path.isdir(path+folder): 25 | os.remove(path+folder) 26 | 27 | 28 | # 建立文件夹 29 | def mkdir(path): 30 | """ 31 | if folder is exists, or make new dir 32 | """ 33 | isexists = os.path.exists(path) 34 | if not isexists: 35 | os.makedirs(path) 36 | print(path) 37 | print('success') 38 | return True 39 | else: 40 | print(path) 41 | print('folder is exist') 42 | return False 43 | 44 | 45 | # 返回文件列表 ----- 46 | def eachFile(filepath): 47 | pathDir = os.listdir(filepath) # /train路径下的文件和文件夹,本例就是100个类别汉字文件夹 48 | child_file_name = [] 49 | full_child_file_list = [] 50 | for allDir in pathDir: #遍历100个类别的汉字文件夹,并不遍历汉字文件夹里面的汉字图片。allDir就是分别取值100个汉字文件夹的名称 51 | if not allDir == '.DS_Store': # 若allDir不为'.DS_Store',则执行下面的合并路径命令。我感觉把'.DS_Store'随便改成别的字符串也能行的样子。 52 | child = os.path.join(filepath, allDir) # os.path.join()将多个路径组合后返回。本句话是创建child路径为每个汉字类别的路径 53 | 54 | full_child_file_list.append(child) # 将每个汉字类别路径添加到full_child_file_list列表 55 | child_file_name.append(allDir) # 将每个汉字文件夹的名称添加到child_file_name 56 | return full_child_file_list, child_file_name # 返回每个汉字类别路径所组成的full_child_file_list列表,和每个汉字文件夹的名称组成的child_file_name列表 57 | 58 | 59 | # 转移ratio文件 60 | def move_ratio(data_list, original_str, replace_str): # 具体作用就是更换路径吗?我感觉这函数名称取得真烂,这函数名称和功能有任何关系吗? 61 | # 涉及到复制移动图片 62 | for x in data_list: 63 | fromImage = Image.open(x) 64 | x = x.replace(original_str, replace_str) # x是图片,x.replace(original_str, replace_str)意义何在?---把原来文件夹的路径替换为现在的路径,然后再保存图片 65 | fromImage.save(x) 66 | 67 | 68 | if __name__ == '__main__': 69 | 70 | # data_path = 'C:/Users/Jack/Documents/jupyter/font_recognition/chinese_font_recognition/train/' # 原始数据存放地址 71 | # data_tra_path = 'C:/Users/Jack/Documents/jupyter/font_recognition/chinese_font_recognition/new_train_val_data/font_tra/' # new_train_data新取的名 72 | # data_val_path = 'C:/Users/Jack/Documents/jupyter/font_recognition/chinese_font_recognition/new_train_val_data/font_val/' 73 | 74 | data_path = '/media/zhz/6cff368c-1c54-4cba-aeff-afb6309a2a9a/ctw-baseline/data/gray_char_images_/' # 原始数据存放地址 75 | data_tra_path = '/media/zhz/6cff368c-1c54-4cba-aeff-afb6309a2a9a/ctw-baseline/data/new_train_data_/' # new_train_data新取的名 76 | data_val_path = '/media/zhz/6cff368c-1c54-4cba-aeff-afb6309a2a9a/ctw-baseline/data/new_val_data_/' 77 | 78 | full_child_file, child_file = eachFile(data_path) # 返回每个汉字类别路径所组成的full_child_file列表,和每个汉字文件夹的名称组成的child_file列表 79 | 80 | # 建立相应的文件夹 -------建立了这么多文件夹,后面也没用到啊,那建立了做啥?----用到了,后面用到的路径在前面已经以字符串形式赋值了。 81 | for i in child_file: # 遍历每个汉字文件夹的名称组成的child_file列表 82 | tra_path = data_tra_path + '/' + str(i) # str(i)是汉字文件夹的名称,感觉中间的这个'/'可以去掉 83 | mkdir(tra_path) # 建立训练集文件夹 84 | val_path = data_val_path + '/' + str(i) 85 | mkdir(val_path) # 建立验证集文件夹 86 | 87 | # 划分train和val 88 | test_train_split_ratio = 0.9 89 | 90 | 91 | # 注意一点:不只/train/是路径,/train/a.jpg 也是路径,读取这个路径,也就是读取这个图片文件了 92 | for i in full_child_file: # i遍历每个汉字类别路径所组成的full_child_file列表 93 | pic_dir, pic_name = eachFile(i) # 返回每个汉字类别路径下的所有汉字图片所组成的pic_dir列表,和每个汉字类别路径下的所有汉字图片的名称组成的pic_name列表 94 | #由于eachFile()中有一句是child = os.path.join(filepath, allDir),这一句是合并路径,所以pic_dir应该是汉字图片的路径,如./白/hdjahdksaj.jpg,而并不是读取了图片 95 | random.shuffle(pic_dir) # shuffle :洗牌,搅乱 96 | train_list = pic_dir[0:int(test_train_split_ratio * len(pic_dir))] # pic_dir[0 : int(0.9*400)],即pic_dir[0:360],即把每个汉字类别文件夹下的前360个汉字路径为作为训练集 97 | val_list = pic_dir[int(test_train_split_ratio * len(pic_dir)):] #pic_dir[int(0.9*400) : ],即pic_dir[360:],即把每个汉字类别文件夹下的第360到400的汉字路径为作为验证集 98 | # 以上还仅仅只是对于一个汉字文件夹做的处理,i遍历full_child_file才是对所有的汉字类别做处理。 99 | # 问题:最后的训练集train_list不是使用的append,对于每次i,train_list,val_list都是重新赋值? 100 | # train_move, val_move 101 | print('proprecessing %s' % i) 102 | # print('train_list:',train_list) 103 | # # 由于下面代码在for循环中,这意思是对每个类别汉字文件夹的每张图片都要做repalce操作,是更换每张图片名称的操作吗? 104 | move_ratio(train_list, 'gray_char_images_', 'new_train_data_') # train_list是某个汉字文件夹的前360张图片路径列表。 105 | # 把原来train文件夹下的某类别汉字的前360张图片复制到new_train_val_data/font_tra路径下 106 | move_ratio(val_list, 'gray_char_images_', 'new_val_data_') # val_list 是某个汉字文件夹的后 40张图片路径列表 107 | 108 | 109 | -------------------------------------------------------------------------------- /project/汉字识别/tinymind_chinese_font_recognition/tinymind_train_model.py: -------------------------------------------------------------------------------- 1 | from keras.preprocessing.image import ImageDataGenerator 2 | from keras.applications.resnet50 import ResNet50 3 | from keras.layers import Conv2D, MaxPool2D, AveragePooling2D, Activation, Embedding 4 | from keras.layers import Flatten, Dense, BatchNormalization, Dropout, PReLU, Lambda 5 | from keras.models import Model, Input 6 | from keras.callbacks import LearningRateScheduler, ModelCheckpoint, TensorBoard 7 | from keras.optimizers import SGD 8 | import keras.backend as K 9 | from PIL import Image 10 | import matplotlib.pyplot as plt 11 | import pandas as pd 12 | import numpy as np 13 | import os 14 | 15 | width = 32 16 | height = 32 17 | 18 | # bn + prelu 19 | def bn_prelu(x): 20 | x = BatchNormalization()(x) 21 | x = PReLU()(x) 22 | return x 23 | 24 | 25 | # ==================================================================================================================================================================- 26 | # learning rate of epoch # 这里的epoch没用到啊,后面的程序直接调用的lr = LearningRateScheduler(lrschedule),根本没有给参数epoch赋值! 27 | def lrschedule(epoch): 28 | if epoch <= 40: 29 | return 0.1 30 | elif epoch <= 80: 31 | return 0.01 32 | else: 33 | return 0.001 34 | 35 | 36 | # label for directory in disk 每类汉字的标签 37 | # def label_of_directory(directory): # 这里的directory最后被赋值为train_path = code_path + 'new_train_val_data/font_tra/' 38 | # """ 39 | # sorted for label indices 40 | # return a dict for {'classes', 'range(len(classes))'} 41 | # """ 42 | # classes = [] 43 | # for subdir in sorted(os.listdir(directory)): # 对汉字文件夹路径排序 44 | # if os.path.isdir(os.path.join(directory, subdir)): # 如果汉字文件夹是路径,就把汉字文件夹路径添加进classes列表中 os.path.join() 将多个路径组合后返回 45 | # classes.append(subdir) 46 | 47 | # num_classes = len(classes) # 统计训练集文件夹下的汉字文件夹有多少个,即汉字类别有多少。明显是100个 48 | # class_indices = dict(zip(classes, range(len(classes)))) # 生成 汉字文件夹路径作为关键字,汉字类别数目作为键值 的字典,即{类别:索引} 49 | # return class_indices # 函数返回值为{类别:索引}的字典形式 50 | 51 | 52 | # # 读取数据 53 | # # 直接读取数据到内存?还是先占据个位置训练的时候再分批次读取进内存? 54 | # # 训练的时候分批次读取进内存,该函数需要在训练或预测模块函数内使用,不能直接在训练之外使用,不然会一直占据大量内存 55 | # def read_data(path): 56 | # image_list=[] 57 | # label_list=[] 58 | # for img_dir in sorted(os.listdir(path)): # 遍历图片文件夹,而非图片 59 | # label_list.append(img_dir) 60 | # for image in os.listdir(path+img_dir): # 遍历每个图片文件夹的图片 61 | # # print(image) 62 | # img=load_image(path+img_dir+'/'+image) 63 | # # print(img.shape) 64 | # image_list.append(img) 65 | # image_arr=np.array(image_list) 66 | # label_list=sorted(label_list*len(os.listdir(path+img_dir))) 67 | # # label_arr=np.array(label_list).reshape(len(label_list),1) # 这里只是每个文件夹的标签,并非文件夹下的图片的标签 68 | 69 | # # print(image_arr.shape) #shape=(?,128,128,1) 70 | # return image_arr,label_list # 函数返回值为所有图片组成的数组,但是并没有标签 71 | 72 | # # 标签变为onehot形式的标签 73 | # def trans2onehot_label(labels,columns): # 这里的label是由所有样本的标签组成的标签向量,标签向量为[1,3,8,12,..]的形式 74 | 75 | # new_label=np.zeros((len(labels),100),dtype=np.uint8) # 创建一个样本数行,100列的零矩阵 76 | 77 | # # 例如:某标签为12的话,那么第12个位置就设置为1 78 | # count=0 79 | # for value in labels: 80 | # for j in range(100): 81 | # if value==j: 82 | # new_label[count][value]=1 83 | # break 84 | # count+=1 85 | # return new_label 86 | 87 | 88 | # # 搭建模型 89 | # def build_model(out_dims, input_shape=(128, 128, 1)): 90 | # inputs_dim = Input(input_shape) # inputs_dim的type为tf.Tensor,shape为(None,128,128,1),dtype为float32 91 | # # model=Sequential() 92 | # # model.add(Conv2D(32,(3,3),strides=(2, 2), padding='valid')(inputs_dim)) 93 | 94 | # x = Conv2D(32, (3, 3), strides=(2, 2), padding='valid')( 95 | # inputs_dim) # “valid”代表只进行有效的卷积,即对边界数据不处理。“VALID”发现余下的窗口不够卷积核大小了所以就把后面的列直接去掉了 96 | # # conv2D(卷积核个数,卷积核尺寸,步长,填充方式)(输入尺寸),其中卷积核个数即输出的深度,本次卷积后shape为(None,63,63,32) 97 | 98 | # x = bn_prelu(x) 99 | # x = Conv2D(32, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,61,61,32) 100 | # x = bn_prelu(x) 101 | # x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,30,30,32) 102 | 103 | # x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,28,28,64) 104 | # x = bn_prelu(x) 105 | # x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,26,26,64) 106 | # x = bn_prelu(x) 107 | # x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,13,13,64) 108 | 109 | # x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,11,11,128) 110 | # x = bn_prelu(x) 111 | # x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,5,5,128) 112 | 113 | # x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,3,3,128) 114 | # x = bn_prelu(x) 115 | # x = AveragePooling2D(pool_size=(2, 2))(x) # 本次平均池化后,shape==(None,1,1,128) 116 | 117 | # x_flat = Flatten()(x) # shape==(None,1*1*128)即(None,128) 118 | 119 | # fc1 = Dense(512)(x_flat) # Dense(全连接层),512是该层的输出维度,x_flat是该层的输入维度,则输出shape==(None,512) 120 | # fc1 = bn_prelu(fc1) 121 | # dp_1 = Dropout(0.3)(fc1) 122 | 123 | # fc2 = Dense(out_dims)(dp_1) # 后面会赋值out_dims==100,即100个汉字类别,输出shape==(None,100),None表示样本数 124 | # fc2 = Activation('softmax')(fc2) 125 | 126 | # model = Model(inputs=inputs_dim, outputs=fc2) 127 | # return model 128 | 129 | 130 | #####搭建模型 131 | def build_model(out_dims, input_shape=(32, 32, 1)): 132 | inputs_dim = Input(input_shape) # inputs_dim的type为tf.Tensor,shape为(None,128,128,1),dtype为float32 133 | # model=Sequential() 134 | # model.add(Conv2D(32,(3,3),strides=(2, 2), padding='valid')(inputs_dim)) 135 | 136 | x = Conv2D(32, (3, 3), strides=(1, 1), padding='valid')( 137 | inputs_dim) # “valid”代表只进行有效的卷积,即对边界数据不处理。“VALID”发现余下的窗口不够卷积核大小了所以就把后面的列直接去掉了 138 | # conv2D(卷积核个数,卷积核尺寸,步长,填充方式)(输入尺寸),其中卷积核个数即输出的深度,本次卷积后shape为(None,63,63,32) (30) 139 | 140 | x = bn_prelu(x) 141 | x = Conv2D(32, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,61,61,32) (28) 142 | x = bn_prelu(x) 143 | x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,30,30,32) (14) 144 | 145 | x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,28,28,64) (12) 146 | x = bn_prelu(x) 147 | x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,26,26,64) (None,10,10,64) 148 | x = bn_prelu(x) 149 | x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,13,13,64) (None,5,5,64) 150 | 151 | x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,11,11,128) (None,3,3,128) 152 | x = bn_prelu(x) 153 | # x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,5,5,128) (None,1,1,128) 154 | 155 | # x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,3,3,128) 156 | # x = bn_prelu(x) 157 | x = AveragePooling2D(pool_size=(2, 2))(x) # 本次平均池化后,shape==(None,1,1,128) 158 | 159 | x_flat = Flatten()(x) # shape==(None,1*1*128)即(None,128) 160 | 161 | fc1 = Dense(512)(x_flat) # Dense(全连接层),512是该层的输出维度,x_flat是该层的输入维度,则输出shape==(None,512) 162 | fc1 = bn_prelu(fc1) 163 | dp_1 = Dropout(0.3)(fc1) 164 | 165 | fc2 = Dense(out_dims)(dp_1) # 后面会赋值out_dims==100,即100个汉字类别,输出shape==(None,100),None表示样本数 166 | fc2 = Activation('softmax')(fc2) 167 | 168 | model = Model(inputs=inputs_dim, outputs=fc2) 169 | return model 170 | 171 | ############### 目标尺寸(65,65) 172 | # def build_model(out_dims, input_shape=(height, width, 1)): 173 | # inputs_dim = Input(input_shape) # inputs_dim的type为tf.Tensor,shape为(None,128,128,1),dtype为float32 174 | # # model=Sequential() 175 | # # model.add(Conv2D(32,(3,3),strides=(2, 2), padding='valid')(inputs_dim)) 176 | 177 | # x = Conv2D(32, (3, 3), strides=(2, 2), padding='valid')( 178 | # inputs_dim) # “valid”代表只进行有效的卷积,即对边界数据不处理。“VALID”发现余下的窗口不够卷积核大小了所以就把后面的列直接去掉了 179 | # # conv2D(卷积核个数,卷积核尺寸,步长,填充方式)(输入尺寸),其中卷积核个数即输出的深度,本次卷积后shape为(None,63,63,32) (32) 180 | 181 | # x = bn_prelu(x) 182 | # x = Conv2D(32, (3, 3), strides=(1, 1), padding='valid')( 183 | # x) # “valid”代表只进行有效的卷积,即对边界数据不处理。“VALID”发现余下的窗口不够卷积核大小了所以就把后面的列直接去掉了 184 | # # conv2D(卷积核个数,卷积核尺寸,步长,填充方式)(输入尺寸),其中卷积核个数即输出的深度,本次卷积后shape为(None,63,63,32) (30) 185 | # x = bn_prelu(x) 186 | # x = Conv2D(32, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,61,61,32) (28) 187 | # x = bn_prelu(x) 188 | # x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,30,30,32) (14) 189 | 190 | # x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,28,28,64) (12) 191 | # x = bn_prelu(x) 192 | # x = Conv2D(64, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,26,26,64) (None,10,10,64) 193 | # x = bn_prelu(x) 194 | # x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,13,13,64) (None,5,5,64) 195 | 196 | # x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,11,11,128) (None,3,3,128) 197 | # x = bn_prelu(x) 198 | # # x = MaxPool2D(pool_size=(2, 2))(x) # 本次池化后,shape==(None,5,5,128) (None,1,1,128) 199 | 200 | # # x = Conv2D(128, (3, 3), strides=(1, 1), padding='valid')(x) # 本次卷积后,shape==(None,3,3,128) 201 | # # x = bn_prelu(x) 202 | # x = AveragePooling2D(pool_size=(2, 2))(x) # 本次平均池化后,shape==(None,1,1,128) 203 | 204 | # x_flat = Flatten()(x) # shape==(None,1*1*128)即(None,128) 205 | 206 | # fc1 = Dense(512)(x_flat) # Dense(全连接层),512是该层的输出维度,x_flat是该层的输入维度,则输出shape==(None,512) 207 | # fc1 = bn_prelu(fc1) 208 | # dp_1 = Dropout(0.3)(fc1) 209 | 210 | # fc2 = Dense(out_dims)(dp_1) # 后面会赋值out_dims==100,即100个汉字类别,输出shape==(None,100),None表示样本数 211 | # fc2 = Activation('softmax')(fc2) 212 | 213 | # model = Model(inputs=inputs_dim, outputs=fc2) 214 | # return model 215 | 216 | # 训练模型 217 | # def train_model(istrain): 218 | # if not istrain: 219 | # if os.path.exists(weights_path): 220 | # model.load_weights(weights_path) 221 | # else: 222 | # print('weights文件不存在,不能加载权重文件,请重新修改train_model函数的istrain参数') 223 | # else: 224 | # print('开始训练') 225 | # model=construct_model(100,input_shape=(128,128,1)) 226 | # sgd = SGD(lr=0.1, momentum=0.9, decay=5e-4, nesterov=True) 227 | # model.compile(optimizer='sgd', 228 | # loss='categorical_crossentropy', 229 | # metrics=['accuracy']) 230 | # # model.fit(data, labels) # starts training 这种训练方式是把所有的训练数据一起输入进去,没有分batch 231 | # for epochs in range(100): 232 | # train_loss=model.train_on_batch(x_train,y_train) 233 | # # model.predict(x_val,y_val) 234 | # val_loss,val_acc=model.evaluate(x_val,y_val) 235 | # print(('epoch=%d-----train_loss=%f---val_loss=%f---val_acc=%f',(epochs,train_loss,val_loss,val_acc))) 236 | # model.save(weights_path) 237 | # return model 238 | 239 | def model_train(model): 240 | lr = LearningRateScheduler(lrschedule) 241 | mdcheck = ModelCheckpoint(WEIGHTS_PATH, monitor='val_acc', save_best_only=True) # 在每个epoch后保存模型到WEIGHTS_PATH 242 | td = TensorBoard(log_dir=code_path + 'new_train_val_data/tensorboard_log/') 243 | 244 | sgd = SGD(lr=0.1, momentum=0.9, decay=5e-4, nesterov=True) 245 | print("model compile!!") 246 | model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy']) 247 | ''' 248 | model.compile(optimizer, loss, metrics=None, sample_weight_mode=None) 249 | 编译用来配置模型的学习过程,其参数有 250 | optimizer:字符串(预定义优化器名)或优化器对象,参考优化器 251 | loss:字符串(预定义损失函数名)或目标函数,参考损失函数 252 | metrics:列表,包含评估模型在训练和测试时的网络性能的指标,典型用法是metrics=['accuracy'] 253 | sample_weight_mode:如果你需要按时间步为样本赋权(2D权矩阵),将该值设为“temporal”。默认为“None”,代表按样本赋权(1D权)。在下面fit函数的解释中有相关的参考内容。 254 | kwargs:使用TensorFlow作为后端请忽略该参数,若使用Theano作为后端,kwargs的值将会传递给 K.function 255 | 256 | 如实例: 257 | model.compile( loss='categorical_crossentropy', optimizer='adam',metrics=['accuracy'] ) 258 | ''' 259 | print("model training!!") 260 | history = model.fit_generator(train_generator, 261 | steps_per_epoch=32000 // BATCH_SIZE, 262 | # steps_per_epoch=128 // BATCH_SIZE, 263 | epochs=max_Epochs, 264 | validation_data=val_generator, 265 | # validation_steps=128 // BATCH_SIZE, 266 | validation_steps=8000 // BATCH_SIZE, 267 | callbacks=[lr, mdcheck, td]) 268 | return history 269 | ''' http://keras-cn.readthedocs.io/en/latest/models/sequential/#fit_generator 270 | fit_generator 271 | fit_generator(self, generator, steps_per_epoch, epochs=1, verbose=1, callbacks=None, validation_data=None, 272 | validation_steps=None, class_weight=None, max_q_size=10, workers=1, pickle_safe=False, initial_epoch=0) 273 | 利用Python的生成器,逐个生成数据的batch并进行训练。生成器与模型将并行执行以提高效率。例如,该函数允许我们在CPU上进行实时的数据提升,同时在GPU上进行模型训练 274 | 函数的参数是: 275 | 276 | generator:生成器函数,生成器的输出应该为: 277 | 一个形如(inputs,targets)的tuple 278 | 一个形如(inputs, targets,sample_weight)的tuple。所有的返回值都应该包含相同数目的样本。生成器将无限在数据集上循环。每个epoch以经过模型的样本数达到samples_per_epoch时,记一个epoch结束 279 | steps_per_epoch:整数,当生成器返回steps_per_epoch次数据时计一个epoch结束,执行下一个epoch 280 | epochs:整数,数据迭代的轮数 281 | verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,2为每个epoch输出一行记录 282 | validation_data:具有以下三种形式之一 283 | 284 | 生成验证集的生成器 285 | 一个形如(inputs,targets)的tuple 286 | 一个形如(inputs,targets,sample_weights)的tuple 287 | validation_steps: 当validation_data为生成器时,本参数指定验证集的生成器返回次数 288 | class_weight:规定类别权重的字典,将类别映射为权重,常用于处理样本不均衡问题。 289 | sample_weight:权值的numpy array,用于在训练时调整损失函数(仅用于训练)。可以传递一个1D的与样本等长的向量用于对样本进行1对1的加权,或者在面对时序数据时,传递一个的形式为(samples,sequence_length)的矩阵来为每个时间步上的样本赋不同的权。这种情况下请确定在编译模型时添加了sample_weight_mode='temporal'。 290 | workers:最大进程数 291 | max_q_size:生成器队列的最大容量 292 | pickle_safe: 若为真,则使用基于进程的线程。由于该实现依赖多进程,不能传递non picklable(无法被pickle序列化)的参数到生成器中,因为无法轻易将它们传入子进程中。 293 | initial_epoch: 从该参数指定的epoch开始训练,在继续之前的训练时有用。 294 | 函数返回一个History对象 295 | ''' 296 | 297 | # 以下定义的这么多函数都是为了处理最后要预测的数据集,对于训练时并不需要使用到这些函数。本例即是"test1"文件夹下的数据 298 | # 以下是进行预测时候的步骤 299 | ''' 进行预测时候的步骤: 300 | def test_image_predict_top1(): # 从训练好的模型中加载权重文件进行预测,这里只预测一个最优类别,函数返回值为"test1"图片文件夹下所有图片的汉字类别组成的列表 301 | def test_image_predict_top_k(): # 从训练好的模型中加载权重文件进行预测,这里预测top_5个最优类别,返回汉字形式的所有图片的预测标签组成的列表 302 | 303 | 1. def generator_list_of_imagepath() # 把path路径下的所有图片的路径加入到image_list列表中,该列表的元素是图片的路径,如./a/1.jpg 304 | 305 | 2. def label_of_directory() # 把汉字标签映射为数字标签{类别:索引},如{ 白:0, 高:1, ...}的形式 306 | 307 | 3. def load_image() # 加载图片,并对图片进行resize以及归一化处理 308 | 309 | 4. def get_label_predict_top1() # 获取预测图片的标签:输入的参数image是图片,返回值是该图片的标签,是一个数字 310 | def get_label_predict_top_k() # 获取预测图片的前top_k个最大可能的标签,返回值为top_k个数字组成的列表。 311 | 312 | 5. def get_key_from_value() # 把索引值等于字典的键值的对应汉字标签关键字找出来 313 | 314 | 315 | # test_image_predict_top_k()预测出的标签是列表形式,即每个标签中间隔开了,因此需要把列表形式的标签转换为汉字标签不间隔的字符串形式,然后存在csv文件中 316 | def save_csv(): 317 | def tran_list2str() # 把list转换为str形式,由于本来一张图片预测出来了5个标签,这5个标签构成每张图片的对应标签列表里面的的5个元素, 318 | # 这五个元素是分隔开的,最后提交的文件格式是无间隔的几个汉字。因此需要把这五个元素以字符串形式连接起来 319 | ''' 320 | 321 | # ==================================================================================================================================================================- 322 | # label for directory in disk 该函数功能是返回 把汉字文件夹名称作为关键字,汉字类别索引作为键值 的字典,即把汉字标签映射为数字标签{类别:索引},如{ 白:0, 高:1, ...}的形式 323 | def label_of_directory(directory): # 这里的directory最后被赋值为train_path = code_path + 'new_train_val_data/font_tra/' 324 | """ 325 | sorted for label indices 326 | return a dict for {'classes', 'range(len(classes))'} 327 | """ 328 | classes = [] 329 | for subdir in sorted(os.listdir(directory)): # 对汉字文件夹目录排序。这里的目录不是绝对路径或相对路径,而是列出汉字文件夹的名称,如"白"、"高",而不是./train/"白" 330 | if os.path.isdir( 331 | os.path.join(directory, subdir)): # 如果汉字文件夹是目录 os.path.join() 将多个路径组合后返回 332 | classes.append(subdir) # 就把汉字文件夹目录添加进classes列表中,这里的汉字文件夹目录是如 "白"的形式。 333 | 334 | num_classes = len(classes) # 统计训练集文件夹下的汉字文件夹有多少个,即汉字类别有多少。明显是100个 335 | class_indices = dict(zip(classes, range(len(classes)))) # 生成 汉字文件夹名称作为关键字,汉字类别索引作为键值 的字典,即{类别:索引} 336 | return class_indices # 函数返回值为{类别:索引}的字典形式,如{ 白:0, 高:1, ...}的形式 337 | 338 | 339 | # zip()的用法:zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。 340 | # >>>a = [1,2,3] 341 | # >>> b = [4,5,6] 342 | # >>> c = [4,5,6,7,8] 343 | # >>> zipped = zip(a,b) # 打包为元组的列表 344 | # [(1, 4), (2, 5), (3, 6)] 345 | 346 | # >>> dict(a='a', b='b', t='t') # 传入关键字 347 | # {'a': 'a', 'b': 'b', 't': 't'} 348 | # >>> dict(zip(['one', 'two', 'three'], [1, 2, 3])) # 映射函数方式来构造字典 349 | # {'three': 3, 'two': 2, 'one': 1} 350 | # >>> dict([('one', 1), ('two', 2), ('three', 3)]) # 可迭代对象方式来构造字典 351 | # {'three': 3, 'two': 2, 'one': 1} 352 | 353 | 354 | # ==================================================================================================================================================================- 355 | # get key from value in dict 该函数功能是把索引值等于字典的键值的对应汉字标签关键字找出来,因为后面做预测时候需要的是预测出汉字类别,而不是他对应的数字标签 356 | def get_key_from_value(dict, index): 357 | for keys, values in dict.items(): 358 | if values == index: 359 | return keys 360 | 361 | 362 | # ==================================================================================================================================================================- 363 | # 该函数功能是把path路径下的所有图片的路径加入到image_list列表中,该列表的元素是图片的路径,如./a/1.jpg 364 | # 这是把"test1"文件夹下的所有图片路径添加进列表 365 | def generator_list_of_imagepath(path): 366 | image_list = [] 367 | for image in os.listdir(path): 368 | if not image == '.DS_Store': 369 | image_list.append(path + image) 370 | return image_list 371 | 372 | 373 | # ==================================================================================================================================================================- 374 | # read image and resize to gray 加载图片,并对图片进行resize以及归一化处理。该函数只在预测没有标签的数据集时才用到,即本例的'test1'文件夹, 375 | # 而在读取训练集验证集的时候并没有用到该函数。训练的时候读取训练集以及测试集是使用ImageDataGenerator.flow_from_directory()来读取的。 376 | def load_image(image): 377 | img = Image.open(image) 378 | img = img.resize((height, width)) 379 | img = np.array(img) # 难道这里之前的时候img不是array?答:是数组 380 | img = img / 255 381 | img = img.reshape((1,) + img.shape + (1,)) # reshape img to size(1, 128, 128, 1) 把二维图片矩阵变为四维的 382 | return img 383 | 384 | 385 | # ==================================================================================================================================================================- 386 | # 获取预测图片的标签:输入的参数image是图片,返回值是该图片的标签,是一个数字,因为中间把onehot形式的标签变为了一个数字,所以返回值是一个数字,将来会把它从字典映射回汉字标签 387 | def get_label_predict_top1(image, model): 388 | """ 389 | image = load_image(image), input image is a ndarray 390 | retturn best of label 391 | """ 392 | predict_proprely = model.predict(image) # 这里会预测出该张图片的数字标签,是onehot形式,而不是汉字标签 393 | predict_label = np.argmax(predict_proprely, axis=1) # 找出每行中的最大值位置的索引值,即是汉字标签映射的键值。即把onehot形式的输出值转化为单个数字的输出值 394 | return predict_label # 这里的标签是一个数字 395 | 396 | 397 | # ==================================================================================================================================================================- 398 | # 获取预测图片的前top_k个最大可能的标签,返回值为top_k个数字组成的列表。 399 | # 输入的参数image是图片,返回值是该图片的标签,是数字形式的标签列表,因为中间把onehot形式的标签变为了一个数字,所以返回值是五个预测数字标签的列表,将来会把它从字典映射回汉字标签 400 | def get_label_predict_top_k(image, model, top_k): 401 | """ 402 | image = load_image(image), input image is a ndarray 403 | return top-5 of label 404 | """ 405 | # array 2 list 把数组转化为列表 406 | predict_proprely = model.predict(image) # 这里会预测出该张图片的数字标签,是onehot形式,而不是汉字标签 407 | predict_list = list(predict_proprely[0]) # 因为输出维度为(None,100)的二维矩阵,所以a[0]取得是一行,而a[0][2]是第0行第2列的一个元素 408 | # 对于一张图片进行预测,它的输出应该是一个(1,100)矩阵,而不是一个(100,)序列向量。但是不添加[0]也行啊,那干嘛还添加个[0]呢? 409 | # 如果是(1,100)的矩阵,那么list(predict_proprely[0])就是[array([0,0,...,1,...,0])]的形式了,咋办?说到底还是一个序列向量。 410 | min_label = min(predict_list) # 找出标签中的最小值,在下面的程序中会用到 411 | label_k = [] 412 | for i in range(top_k): # 循环top_k次,每次找出predict_list中的最大值的索引值,然后把该索引值对应的值(即最大值)从列表中remove 413 | label = np.argmax(predict_list) # 把predict_list中的最大值的索引值赋给label 414 | predict_list.remove(predict_list[label]) # 把最大值的索引值对应的值(即最大值)从predict_list列表中remove 415 | predict_list.insert(label, min_label) # 在移除最大值的位置处,把列表中的最小值插入到该位置 416 | label_k.append(label) # 把最大值的索引值添加到label_k列表中 417 | return label_k # 经过for循环,最终label_k列表中会有top_k个值,即softmax输出的值从大到小排列的top_k个值的索引值。这些索引值将来就会映射为汉字标签 418 | 419 | 420 | # ==================================================================================================================================================================- 421 | # 从训练好的模型中加载权重文件进行预测,这里只预测一个最优类别,函数返回值为"test1"图片文件夹下所有图片的汉字类别组成的列表 422 | def test_image_predict_top1(model, test_image_path, directory): 423 | model.load_weights(WEIGHTS_PATH) # 加载训练好的权重文件到模型 424 | image_list = generator_list_of_imagepath(test_image_path) # 把测试集(这里指的是"./test1/"文件夹)要预测的图片的路径加入到image_list列表中 425 | 426 | predict_label = [] 427 | class_indecs = label_of_directory(directory) # {类别:索引}的字典class_indecs,即把汉字类别映射为数字索引 428 | for image in image_list: # 遍历image_list列表中的每个元素,即每张图片的路径,如./a/1.jpg 429 | img = load_image(image) # 加载一张图片,并做resize和归一化处理 430 | label_index = get_label_predict_top1(img, model) # 获取预测图片的标签,这里的标签是一个数字,将来会把它从字典映射回汉字标签 431 | label = get_key_from_value(class_indecs, 432 | label_index) # 当label_index和class_indecs字典的value值相等时,返回class_indecs的keys。即找出预测出的数字标签对应的汉字 433 | predict_label.append(label) # 将一张图片预测出的汉字形式的类别(标签)加入到predict_label列表中 434 | return predict_label # 函数返回值为整个图片文件夹下的图片的汉字类别 435 | 436 | 437 | # ==================================================================================================================================================================- 438 | # 从训练好的模型中加载权重文件进行预测,这里预测top_5个最优类别,返回汉字形式的所有图片的预测标签组成的列表 439 | def test_image_predict_top_k(modle, test_image_path, directory, top_k): 440 | model.load_weights(WEIGHTS_PATH) # 由于训练完成后会生成权重文件,因此这里进行预测的时候就得加载训练后的权重文件 441 | image_list = generator_list_of_imagepath(test_image_path) # 把测试集(这里指的是"./test1/"文件夹)要预测的图片的路径加入到image_list列表中 442 | 443 | predict_label = [] 444 | class_indecs = label_of_directory(directory) # {类别:索引}的字典class_indecs,即把汉字类别映射为数字索引 445 | # 这个外循环是对文件夹下的所有图片组成的列表进行遍历 446 | for image in image_list: # 遍历image_list列表中的每个元素,即每张图片的路径,如./a/1.jpg 447 | img = load_image(image) # 加载一张图片,并做resize和归一化处理 448 | # return a list of label max->min 449 | label_index = get_label_predict_top_k(img, model, 450 | 5) # 获取预测图片的前top_k个最大概率的标签,这里的label_index是top_k个数字组成的列表,将来会把它从字典映射回汉字标签 451 | label_key_list = [] # 452 | # 下面这个内循环是对一张图片的五个预测标签进行遍历,把索引值标签转化为汉字标签 453 | for label in label_index: # 因为这里标签有5个候选值,因此需要把这5个候选值对应的汉字找出来,所以需要遍历每个标签索引值 454 | label_key = get_key_from_value(class_indecs, 455 | label) # 当label和class_indecs字典的value值相等时,返回class_indecs的keys。即找出预测出的数字标签对应的汉字 456 | label_key_list.append(str(label_key)) # 把索引值对应的关键字即汉字标签添加进label_key_list列表,该列表最终会有top_k个元素。 457 | # 注意这里需要把汉字转换为str。但是top1的时候他却没有转换为str? 后面有专门的tran_list2str()会把列表转换为str 458 | 459 | predict_label.append(label_key_list) # 把内循环得到的一张图片的预测标签(top_k个元素组成的列表)添加进predict_label列表 460 | 461 | return predict_label # 函数返回值为所有图片的预测标签组成的predict_label列表,该列表的每个元素也是列表,即一张图片的top_k个预测标签(汉字形式) 462 | 463 | 464 | # ==================================================================================================================================================================- 465 | # translate list to str in label 把list转换为str形式 466 | def tran_list2str(predict_list_label): 467 | new_label = [] 468 | for row in range(len(predict_list_label)): # 遍历所有图片的预测出的标签组成的列表 469 | str = "" # 这里不是表示成对双引号,而是指无空格,即双引号之间没有空格,即汉字之间没有分隔符 470 | for label in predict_list_label[row]: # 遍历一张图片的预测标签列表的top_k个预测标签 471 | str += label # 遍历完一次内循环后,str为 你是谁啊你 472 | new_label.append(str) # 然后把 你是谁啊你 添加进new_label列表 473 | return new_label # 返回值为[你是谁啊你,你是谁啊你,你是谁啊你,...]的形式 474 | 475 | 476 | # ==================================================================================================================================================================- 477 | # save filename , label as csv # 将图片名称以及其预测类别存为csv文件 478 | def save_csv(test_image_path, predict_label): 479 | image_list = generator_list_of_imagepath(test_image_path) # image_list是每张图片的路径组成的列表 480 | save_arr = np.empty((10000, 2), dtype=np.str) 481 | save_arr = pd.DataFrame(save_arr, columns=['filename', 'label']) 482 | predict_label = tran_list2str(predict_label) # 把图片的预测的汉字标签转换为str形式 483 | for i in range(len(image_list)): # 遍历测试集文件夹里面的图片个数次,即测试集有多少张图片,就遍历多少次 484 | filename = image_list[i].split('/')[-1] # 对第i张图片的路径字符串进行分割,取路径字符串最后一个'/'分隔符后面的字符串(即图片名称,如1.jpg),赋给filename 485 | save_arr.values[i, 0] = filename # 把第张图片文件名图片文件名赋给save_arr的第[i,0]个元素,即图片文件名称置于第0列 486 | save_arr.values[i, 1] = predict_label[i] # 把第张图片的预测标签赋给save_arr的第[i,1]个元素,即图片预测的标签置于第1列 487 | save_arr.to_csv('submit_test.csv', decimal=',', encoding='utf-8', index=False, index_label=False) 488 | print('submit_test.csv have been write, locate is :', os.getcwd()) # os.getcwd() 方法用于返回当前工作目录 489 | 490 | 491 | # # 预测一个正确的:top1 492 | # def predict_top1(test_image_path): 493 | # model=train_model(path,istrain) 494 | # model.load_weights(weights_path) 495 | # predict_image=[] 496 | # for image in test_image_path: # 遍历测试集里面的所有图片 497 | # predict=model.predict(image) 498 | # predict_image.append(predict) 499 | # return [image,predict_image] 500 | 501 | 502 | # # 预测5个正确的:top5 503 | # def predict_top5(path): 504 | # model=train_model(path,istrain) 505 | # model.load_weights(weights_path) 506 | # model.predict(x_test) 507 | 508 | 509 | # # 输出结果文件 510 | # def save_csv(path): 511 | # data_arr=np.array(predict_top1(test_image_path),dtype=np.str) 512 | # data_arr = np.empty((10000, 2), dtype=np.str) 513 | # data_arr=pd.DataFrame(data_arr,columns=['filename','label']) 514 | # data_arr.to_csv('submit_test.csv', decimal=',', encoding='utf-8', index=False, index_label=False) 515 | # print('submit_test.csv have been write, locate is :', os.getcwd()) # os.getcwd() 方法用于返回当前工作目录 516 | 517 | 518 | if __name__ == '__main__': 519 | code_path = '/home/zhz/Downloads/CPS-OCR-Engine-master/ocr/' 520 | # train_path = code_path + 'new_train_val_data/font_tra/' 521 | # val_path = code_path + 'new_train_val_data/font_val/' 522 | 523 | train_path = "/media/zhz/6cff368c-1c54-4cba-aeff-afb6309a2a9a/ctw-baseline/data/new_train_data/" 524 | val_path = "/media/zhz/6cff368c-1c54-4cba-aeff-afb6309a2a9a/ctw-baseline/data/new_val_data/" 525 | 526 | test_image_path = 'test1/' 527 | 528 | # num_classes=3755 529 | num_classes = 3273 530 | # num_classes = 2602 531 | BATCH_SIZE = 64 532 | WEIGHTS_PATH = 'best_weights_hanzi.hdf5' 533 | max_Epochs = 200 534 | 535 | # ImageDataGenerator就是图片数据生成器,下面函数是构建训练集生成器 536 | train_datagen = ImageDataGenerator( # https://blog.csdn.net/weiwei9363/article/details/78635674 537 | rescale=1. / 255, # rescale值在执行其他处理前乘到整个图像上,这个值定为0~1之间的数 538 | # horizontal_flip=True #进行随机水平翻转 539 | # width_shift_range=0.15, # 宽度偏移,随机沿着水平方向,以图像的宽小部分百分比为变化范围进行平移; 540 | # height_shift_range=0.15 # 高度偏移,随机沿着垂直方向,以图像的高小部分百分比为变化范围进行平移; 541 | ) 542 | 543 | val_datagen = ImageDataGenerator( # 对验证集只需要对像素值缩放1/255就行 544 | rescale=1. / 255 545 | ) 546 | 547 | # 利用keras中image.ImageDataGenerator.flow_from_directory()实现从文件夹中提取图片和进行简单归一化处理,以及数据扩充。但是标签没有读进去啊? 548 | # 答:自动读取了标签,每一个子文件夹都会被认为是一个新的类。(类别的顺序将按照字母表顺序映射到标签值)。通过属性class_indices可获得文件夹名与类的序号的对应字典。 549 | # 所以使用flow_from_directory()给定训练集或者验证集路径之后,他就自动读取数据及其标签,不用管它怎么分的类。 550 | # 对训练集图像进行数据扩充 利用上面构建的训练集生成器来对图片进行归一化以及数据扩充。 551 | # 但是到底有没有扩充呢?图片数量并没有增加啊?难道是只增加在内存中,并没有存到本地文件夹里面? 552 | # 答:他没有实现扩充后的存储操作,应该是只做了数据扩充,却没有执行扩充让他读取进数据集。 553 | train_generator = train_datagen.flow_from_directory( # https://blog.csdn.net/u012193416/article/details/79368855 554 | 555 | train_path, # 训练集的路径 556 | target_size=((height, width)), # 在这里即对训练集的图片进行缩放,缩放为(128,128) 557 | batch_size=BATCH_SIZE, # 在这里对训练设置batch_size 558 | color_mode='grayscale', 559 | class_mode='categorical', # class_mode参数决定了返回的标签数组的形式, "categorical"会返回2D的one-hot编码标签 560 | # save_to_dir=code_path+ 'gen/' , 561 | # save_prefix='gen' 562 | ) 563 | ''' http://keras-cn.readthedocs.io/en/latest/preprocessing/image/#imagedatagenerator 564 | 1.flow_from_directory() 565 | flow_from_directory(directory): 以文件夹路径为参数,生成经过数据提升/归一化后的数据,在一个无限循环中无限产生batch数据 566 | 567 | directory: 目标文件夹路径,对于每一个类,该文件夹都要包含一个子文件夹.子文件夹中任何JPG、PNG、BNP、PPM的图片都会被生成器使用.详情请查看此脚本 568 | target_size: 整数tuple,默认为(256, 256). 图像将被resize成该尺寸 569 | color_mode: 颜色模式,为"grayscale","rgb"之一,默认为"rgb".代表这些图片是否会被转换为单通道或三通道的图片. 570 | classes: 可选参数,为子文件夹的列表,如['dogs','cats']默认为None. 若未提供,则该类别列表将从directory下的子文件夹名称/结构自动推断。每一个子文件夹都会被认为是一个新的类。(类别的顺序将按照字母表顺序映射到标签值)。通过属性class_indices可获得文件夹名与类的序号的对应字典。 571 | class_mode: "categorical", "binary", "sparse"或None之一. 默认为"categorical. 该参数决定了返回的标签数组的形式, "categorical"会返回2D的one-hot编码标签,"binary"返回1D的二值标签."sparse"返回1D的整数标签,如果为None则不返回任何标签, 生成器将仅仅生成batch数据, 这种情况在使用model.predict_generator()和model.evaluate_generator()等函数时会用到. 572 | batch_size: batch数据的大小,默认32 573 | shuffle: 是否打乱数据,默认为True 574 | seed: 可选参数,打乱数据和进行变换时的随机数种子 575 | save_to_dir: None或字符串,该参数能让你将提升后的图片保存起来,用以可视化 576 | save_prefix:字符串,保存提升后图片时使用的前缀, 仅当设置了save_to_dir时生效 577 | save_format:"png"或"jpeg"之一,指定保存图片的数据格式,默认"jpeg" 578 | flollow_links: 是否访问子文件夹中的软链接 579 | 580 | 2.flow() 581 | flow(self, X, y, batch_size=32, shuffle=True, seed=None, save_to_dir=None, save_prefix='', save_format='png'):接收numpy数组和标签为参数,生成经过数据提升或标准化后的batch数据,并在一个无限循环中不断的返回batch数据 582 | 583 | x:样本数据,秩应为4.在黑白图像的情况下channel轴的值为1,在彩色图像情况下值为3 584 | y:标签 585 | batch_size:整数,默认32 586 | shuffle:布尔值,是否随机打乱数据,默认为True 587 | save_to_dir:None或字符串,该参数能让你将提升后的图片保存起来,用以可视化 588 | save_prefix:字符串,保存提升后图片时使用的前缀, 仅当设置了save_to_dir时生效 589 | save_format:"png"或"jpeg"之一,指定保存图片的数据格式,默认"jpeg" 590 | yields:形如(x,y)的tuple,x是代表图像数据的numpy数组.y是代表标签的numpy数组.该迭代器无限循环. 591 | seed: 整数,随机数种子 592 | ''' 593 | 594 | # 以上用的是ImageDataGenerator.flow_from_directory()函数,该函数对图片进行简单归一化处理。本程序并没有使用数据扩充,因为总样本还是40000个。数据生成后并没有保存下来读取。 595 | # 还有一种是使用ImageDataGenerator.flow()函数,该函数将会返回一个生成器,这个生成器用来扩充数据,每次都会产生batch_size个样本。 https://blog.csdn.net/weiwei9363/article/details/78635674 596 | 597 | # 对验证集进行数据扩充 但是标签没有读进去啊? 598 | #答:自动读取了标签,因为读取每个文件夹下的图片就已经知道了他们分别是哪一个类别。所以使用flow_from_directory()给定训练集或者验证集路径之后,他就自动读取数据及其标签,不用管它怎么分的类。 599 | val_generator = val_datagen.flow_from_directory( 600 | val_path, 601 | target_size=((height, width)), 602 | batch_size=BATCH_SIZE, 603 | color_mode='grayscale', 604 | class_mode='categorical' # class_mode参数决定了返回的标签数组的形式, "categorical"会返回2D的one-hot编码标签 605 | ) 606 | 607 | simple_model = build_model(num_classes) 608 | print(simple_model.summary()) 609 | 610 | print("=====start train image of epoch=====") 611 | 612 | model_history = model_train(simple_model) 613 | 614 | print("=====show acc and loss of train and val====") 615 | # draw_loss_acc(model_history) 616 | 617 | # print("=====test label=====") 618 | # simple_model.load_weights(WEIGHTS_PATH) 619 | # model = simple_model 620 | # predict_label = test_image_predict_top_k(model, code_path + test_image_path, train_path, 1) 621 | 622 | # print("=====csv save=====") 623 | # save_csv(code_path + test_image_path, predict_label) 624 | 625 | # print("====done!=====") 626 | 627 | -------------------------------------------------------------------------------- /project/用 Python 和 OpenCV 检测和跟踪运动对象/motion_detector.py: -------------------------------------------------------------------------------- 1 | # 导入必要的软件包 2 | import argparse 3 | import datetime 4 | import imutils 5 | import time 6 | import cv2 7 | 8 | # 创建参数解析器并解析参数 9 | ap = argparse.ArgumentParser() 10 | ap.add_argument("-v", "--video", help="path to the video file") 11 | ap.add_argument("-a", "--min-area", type=int, default=500, help="minimum area size") 12 | args = vars(ap.parse_args()) 13 | 14 | # 如果video参数为None,那么我们从摄像头读取数据 15 | if args.get("video", None) is None: 16 | camera = cv2.VideoCapture(0) 17 | time.sleep(0.25) 18 | 19 | # 否则我们读取一个视频文件 20 | else: 21 | camera = cv2.VideoCapture(args["video"]) 22 | 23 | # 初始化视频流的第一帧 24 | firstFrame = None 25 | 26 | # 遍历视频的每一帧 27 | while True: 28 | # 获取当前帧并初始化occupied/unoccupied文本 29 | (grabbed, frame) = camera.read() 30 | text = "Unoccupied" 31 | 32 | # 如果不能抓取到一帧,说明我们到了视频的结尾 33 | if not grabbed: 34 | break 35 | 36 | # 调整该帧的大小,转换为灰阶图像并且对其进行高斯模糊 37 | frame = imutils.resize(frame, width=500) 38 | gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 39 | gray = cv2.GaussianBlur(gray, (21, 21), 0) 40 | 41 | # 如果第一帧是None,对其进行初始化 42 | if firstFrame is None: 43 | firstFrame = gray 44 | continue 45 | 46 | # 计算当前帧和第一帧的不同 47 | frameDelta = cv2.absdiff(firstFrame, gray) 48 | thresh = cv2.threshold(frameDelta, 25, 255, cv2.THRESH_BINARY)[1] 49 | 50 | # 扩展阀值图像填充孔洞,然后找到阀值图像上的轮廓 51 | thresh = cv2.dilate(thresh, None, iterations=2) 52 | (_frame,cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, 53 | cv2.CHAIN_APPROX_SIMPLE) 54 | 55 | # 遍历轮廓 56 | for c in cnts: 57 | # if the contour is too small, ignore it 58 | if cv2.contourArea(c) < args["min_area"]: 59 | continue 60 | 61 | # compute the bounding box for the contour, draw it on the frame, 62 | # and update the text 63 | # 计算轮廓的边界框,在当前帧中画出该框 64 | (x, y, w, h) = cv2.boundingRect(c) 65 | cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) 66 | text = "Occupied" 67 | 68 | # draw the text and timestamp on the frame 69 | # 在当前帧上写文字以及时间戳 70 | cv2.putText(frame, "Room Status: {}".format(text), (10, 20), 71 | cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2) 72 | cv2.putText(frame, datetime.datetime.now().strftime("%A %d %B %Y %I:%M:%S%p"), 73 | (10, frame.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 255), 1) 74 | 75 | # 显示当前帧并记录用户是否按下按键 76 | cv2.imshow("Security Feed", frame) 77 | cv2.imshow("Thresh", thresh) 78 | cv2.imshow("Frame Delta", frameDelta) 79 | key = cv2.waitKey(1) & 0xFF 80 | 81 | # 如果q键被按下,跳出循环 82 | if key == ord("q"): 83 | break 84 | 85 | # 清理摄像机资源并关闭打开的窗口 86 | camera.release() 87 | cv2.destroyAllWindows() -------------------------------------------------------------------------------- /project/用 Python 和 OpenCV 检测和跟踪运动对象/用 Python 和 OpenCV 检测和跟踪运动对象 - Python - 伯乐在线.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhzgithub/project_tinymind/6e26cbca81bc0ad50d8c25f26a453044cddce252/project/用 Python 和 OpenCV 检测和跟踪运动对象/用 Python 和 OpenCV 检测和跟踪运动对象 - Python - 伯乐在线.pdf --------------------------------------------------------------------------------