【Python2】Keras_ResNet 在Cifar10数据集上分类,Flask框架部署目标检测模型
文章目錄
- 1.導入庫
- 2.數據準備
- 2.1 加載訓練集
- 2.2 加載測試集
- 2.3 對類別做One-Hot編碼
- 2.4 對圖片像素的0-255值做歸一化,并減去均值
- 3.搭建神經網絡
- 3.1 定義函數resnet_layer,返回值是經過resnet_layer計算的結果
- 3.2 定義函數resnet_v1,返回值是模型對象
- 3.3 定義函數resnet_v2,返回值是模型對象
- 3.4 實例化模型對象
- 3.5 多GPU并行訓練
- 3.6 打印模型架構信息
- 4. 模型訓練
- 4.1 規劃學習率(訓練到后期時學習率需減小)
- 4.2 模型訓練時的參數設置
- 4.3 使用圖像增強的結果做模型訓練
- 5.模型評估
- 5.1 加載訓練好的模型
- 5.2 計算訓練集的準確率
- 5.3 計算測試集的準確率
- 6. 模型測試結果可視化
- 6.1 隨機選100張圖可視化
- 6.2 隨機選取100張圖片的同時,要求10個類別,每個類別取10張
- 7.Keras中權重文件的讀寫
- 7.1 使用load_model方法加載模型文件
- 7.2 使用save_weights方法保存權重文件
- 7.3 使用load_weights方法加載權重文件
- 8. 圖像分類:純終端運行
- 8.1 在code文件夾下新建image_server.py寫入下面服務端代碼
- 8.2 在code文件夾下新建image_client.py寫入下面客戶端代碼
- 8.3 cmd文件雙擊啟動
- 9.圖像分類:有web頁面
- 9.1 新建_11_web_image_server_2.py文件寫入下面服務端代碼:
- 9.2 新建templates文件夾里_12_web_page.html文件
- 9.3 cmd文件雙擊啟動
- 10.目標檢測:純終端運行
- 10.1 新建_21_yolov3.py文件
- 10.2 cmd文件雙擊啟動
1.導入庫
1.keras(resnet):https://github.com/keras-team/keras/blob/master/examples/cifar10_resnet.py
2.用到的cifar10數據集和模型權重鏈接:https://pan.baidu.com/s/1L4oZAPg_9B_YipPeihmBwQ 提取碼:8jcw
5個data_batch文件,每個1萬數據
2.數據準備
2.1 加載訓練集
官方代碼沒有指定路徑:
2.2 加載測試集
# 加載數據集cifar10里面的測試集 def load_test_dataset(dirPath='../resources/cifar-10-batches-py/'):fileName = 'test_batch'filePath = os.path.join(dirPath, fileName)test_X, test_y = load_batch(filePath)print('test_X矩陣轉置前:', test_X.shape)test_X = test_X.transpose(0, 2, 3, 1)print('test_X矩陣轉置后:', test_X.shape)return test_X, test_ydirPath = '../resources/cifar-10-batches-py/' test_imageData, test_y = load_test_dataset()2.3 對類別做One-Hot編碼
# 對類別ID做One-Hot編碼 from keras.utils import to_categoricalclass_quantity = 10 train_Y = to_categorical(train_y, class_quantity) test_Y = to_categorical(test_y, class_quantity)2.4 對圖片像素的0-255值做歸一化,并減去均值
train_X = train_imageData.astype('float32') / 255 test_X = test_imageData.astype('float32') / 255 pixel_mean = np.mean(train_X, axis=0) print('pixel_mean.shape:', pixel_mean.shape) train_X = train_X - pixel_mean test_X = test_X - pixel_mean3.搭建神經網絡
3.1 定義函數resnet_layer,返回值是經過resnet_layer計算的結果
def resnet_layer(inputs, #定義了一層resnet_layernum_filters=16,kernel_size=3,strides=1,activation='relu',batch_normalization=True,conv_first=True):conv = Conv2D(num_filters,kernel_size=kernel_size,strides=strides,padding='same',kernel_initializer='he_normal',kernel_regularizer=l2(1e-4))x = inputsif conv_first: #為resnet V1x = conv(x) #conv為一函數,相當于下圖weightif batch_normalization:x = BatchNormalization()(x)if activation is not None:x = Activation(activation)(x)else: #為resnet V2if batch_normalization:x = BatchNormalization()(x) #BatchNormalization()實例化一個函數對象if activation is not None:x = Activation(activation)(x)x = conv(x)return x3.2 定義函數resnet_v1,返回值是模型對象
def resnet_v1(input_shape, depth, num_classes=10):if (depth - 2) % 6 != 0:raise ValueError('depth should be 6n+2 (eg 20, 32, 44 in [a])')# Start model definition.num_filters = 16num_res_blocks = int((depth - 2) / 6)inputs = Input(shape=input_shape)x = resnet_layer(inputs=inputs)# Instantiate the stack of residual unitsfor stack in range(3):for res_block in range(num_res_blocks):strides = 1if stack > 0 and res_block == 0: # first layer but not first stackstrides = 2 # downsampley = resnet_layer(inputs=x,num_filters=num_filters,strides=strides)y = resnet_layer(inputs=y,num_filters=num_filters,activation=None)if stack > 0 and res_block == 0: # first layer but not first stack# linear projection residual shortcut connection to match# changed dimsx = resnet_layer(inputs=x,num_filters=num_filters,kernel_size=1,strides=strides,activation=None,batch_normalization=False)x = keras.layers.add([x, y])x = Activation('relu')(x)num_filters *= 2# Add classifier on top.# v1 does not use BN after last shortcut connection-ReLUx = AveragePooling2D(pool_size=8)(x)y = Flatten()(x)outputs = Dense(num_classes,activation='softmax',kernel_initializer='he_normal')(y)# Instantiate model.model = Model(inputs=inputs, outputs=outputs)return model3.3 定義函數resnet_v2,返回值是模型對象
def resnet_v2(input_shape, depth, num_classes=10):if (depth - 2) % 9 != 0:#深度必須是9n+2,比如20層,56層,110層raise ValueError('depth should be 9n+2 (eg 56 or 110 in [b])')# Start model definition.num_filters_in = 16 # 卷積核數量num_res_blocks = int((depth - 2) / 9)inputs = Input(shape=input_shape)x = resnet_layer(inputs=inputs, # resnet lay 0如下表,第一次調用了一層resnet_layernum_filters=num_filters_in,conv_first=True)# Instantiate the stack of residual units 實例化剩余單元的堆棧for stage in range(3):for res_block in range(num_res_blocks):activation = 'relu'batch_normalization = Truestrides = 1if stage == 0: #如果stage和res_block == 0,不進行activation,batch_normalization,num_filters_out = num_filters_in * 4if res_block == 0: # first layer and first stageactivation = Nonebatch_normalization = Falseelse:num_filters_out = num_filters_in * 2if res_block == 0: # first layer but not first stagestrides = 2 # downsample# bottleneck residual unity = resnet_layer(inputs=x,num_filters=num_filters_in,kernel_size=1,strides=strides,activation=activation,batch_normalization=batch_normalization,conv_first=False)y = resnet_layer(inputs=y,num_filters=num_filters_in,conv_first=False)y = resnet_layer(inputs=y,num_filters=num_filters_out,kernel_size=1,conv_first=False)if res_block == 0:# linear projection residual shortcut connection to match# changed dimsx = resnet_layer(inputs=x,num_filters=num_filters_out,kernel_size=1,strides=strides,activation=None,batch_normalization=False)x = keras.layers.add([x, y]) #實現shotcutnum_filters_in = num_filters_out#如上三個resnet_layer調用一個shotcut# Add classifier on top.# v2 has BN-ReLU before Poolingx = BatchNormalization()(x) #如下三行代碼對應下表對應后面幾行x = Activation('relu')(x)x = AveragePooling2D(pool_size=8)(x)y = Flatten()(x)outputs = Dense(num_classes,activation='softmax',kernel_initializer='he_normal')(y)# Instantiate model.model = Model(inputs=inputs, outputs=outputs)return model3.4 實例化模型對象
# Model version # Orig paper: version = 1 (ResNet v1), Improved ResNet: version = 2 (ResNet v2) version = 2 # Computed depth from supplied model parameter n n = 2 if version == 1:depth = n * 6 + 2 elif version == 2:depth = n * 9 + 2# 根據ResNet版本,獲取對應的模型對象 if version == 2:model = resnet_v2(input_shape=input_shape, depth=depth) else:model = resnet_v1(input_shape=input_shape, depth=depth)3.5 多GPU并行訓練
https://github.com/matterport/Mask_RCNN/blob/master/mrcnn/parallel_model.py
打開parallel_model.py文件,在原來文件加入紅點一行:
3.6 打印模型架構信息
# Model name, depth and version model_type = 'ResNet%dv%d' % (depth, version) print(model_type) model.summary()
如下所示三個resnet_layer為一個shotcut,但如上圖是兩個layer再加,作者用的三個效果更好
所以ResNet20_V2:這個20是conv共19層(layer0)+dense1層
4. 模型訓練
4.1 規劃學習率(訓練到后期時學習率需減小)
def lr_schedule(epoch):lr = 1e-3if epoch > 180:lr *= 0.5e-3elif epoch > 160:lr *= 1e-3elif epoch > 120:lr *= 1e-2elif epoch > 80:lr *= 1e-1print('Learning rate: ', lr)return lr4.2 模型訓練時的參數設置
# Training parameters batch_size = 64 # orig paper trained all networks with batch_size=128 epochs = 200# Prepare model model saving directory. save_dir = os.path.abspath('../resources/saved_models') model_name = 'cifar10_%s_model.{epoch:03d}.h5' % model_type if not os.path.isdir(save_dir):os.makedirs(save_dir) filepath = os.path.join(save_dir, model_name)# Prepare callbacks for model saving and for learning rate adjustment. checkpoint = ModelCheckpoint(filepath=filepath,monitor='val_acc',verbose=0,save_best_only=True) lr_scheduler = LearningRateScheduler(lr_schedule) lr_reducer = ReduceLROnPlateau(factor=np.sqrt(0.1),cooldown=0,patience=5,min_lr=0.5e-6) callbacks = [checkpoint, lr_reducer, lr_scheduler]4.3 使用圖像增強的結果做模型訓練
data_augmentation = True if data_augmentation:print('Using real-time data augmentation.')# This will do preprocessing and realtime data augmentation:datagen = ImageDataGenerator(# set input mean to 0 over the datasetfeaturewise_center=False,# set each sample mean to 0samplewise_center=False,# divide inputs by std of datasetfeaturewise_std_normalization=False,# divide each input by its stdsamplewise_std_normalization=False,# apply ZCA whiteningzca_whitening=False,# epsilon for ZCA whiteningzca_epsilon=1e-06,# randomly rotate images in the range (deg 0 to 180)rotation_range=0,# randomly shift images horizontallywidth_shift_range=0.1,# randomly shift images verticallyheight_shift_range=0.1,# set range for random shearshear_range=0.,# set range for random zoomzoom_range=0.,# set range for random channel shiftschannel_shift_range=0.,# set mode for filling points outside the input boundariesfill_mode='nearest',# value used for fill_mode = "constant"cval=0.,# randomly flip imageshorizontal_flip=True,# randomly flip imagesvertical_flip=False,# set rescaling factor (applied before any other transformation)rescale=None,# set function that will be applied on each inputpreprocessing_function=None,# image data format, either "channels_first" or "channels_last"data_format=None,# fraction of images reserved for validation (strictly between 0 and 1)validation_split=0.0)datagen.fit(x_train)# Fit the model on the batches generated by datagen.flow().model.fit_generator(datagen.flow(x_train, y_train, batch_size=batch_size),validation_data=(x_test, y_test),epochs=epochs,verbose=1, workers=4,callbacks=callbacks)
附:python的生成器與keras.preprocessing.image文件的ImageDataGenerator類的關系:
問號可以找在哪個路徑:
如下圖打開image.py發現是繼承image:
找image:
5.模型評估
5.1 加載訓練好的模型
from keras.models import load_model from keras.optimizers import Adammodel_filePath = '../resources/saved_models/cifar10_ResNet56v2_model.162.h5' model = load_model(model_filePath) model.compile(loss='categorical_crossentropy',optimizer=Adam(lr=0.001),metrics=['accuracy'])5.2 計算訓練集的準確率
scores = model.evaluate(train_X, train_Y, verbose=1, batch_size=1000) print('Test loss:%.6f' %scores[0]) print('Test accuracy:%.6f' %scores[1])5.3 計算測試集的準確率
scores = model.evaluate(test_X, test_Y, verbose=1, batch_size=1000) print('Test loss:%.6f' %scores[0]) print('Test accuracy:%.6f' %scores[1])6. 模型測試結果可視化
6.1 隨機選100張圖可視化
import math import matplotlib.pyplot as plt import numpy as np %matplotlib inline import matplotlib matplotlib.rcParams['font.sans-serif'] = ['SimHei'] import randomdef draw_image(position, image, title, isTrue):plt.subplot(*position)plt.imshow(image)plt.axis('off')if not isTrue:plt.title(title, color='red')else:plt.title(title)def batch_draw_images(model, batch_size, test_imageData, test_X, test_y, id2name_dict):index_list = list(range(len(test_imageData)))selected_index_list = random.sample(index_list, batch_size)true_imageData = test_imageData[selected_index_list]true_X = test_X[selected_index_list]true_y = np.array(test_y)[selected_index_list]predict_Y = model.predict(true_X)predict_y = np.argmax(predict_Y, axis=1)row_number = math.ceil(batch_size ** 0.5)column_number = row_numberplt.figure(figsize=(row_number+8, column_number+8))for i in range(row_number):for j in range(column_number):index = i * column_number + jif index < batch_size:position = (row_number, column_number, index+1)image = true_imageData[index]actual_classId = true_y[index]predict_classId = predict_y[index]isTrue = actual_classId==predict_classIdactual_className = id2name_dict[actual_classId]predict_className = id2name_dict[predict_classId]title = 'actual:%s\npredict:%s' %(actual_className, predict_className)draw_image(position, image, title, isTrue)batch_size = 100 #展示100張圖 className_list = ['飛機', '汽車', '鳥', '貓', '鹿', '狗', '青蛙', '馬', '船', '卡車'] id2name_dict = {a:b for a, b in enumerate(className_list)} batch_draw_images(model, batch_size, test_imageData, test_X, test_y, id2name_dict) plt.show()6.2 隨機選取100張圖片的同時,要求10個類別,每個類別取10張
def get_selectedIndexList(test_y, batch_size):assert batch_size % 10 == 0, 'batch_size must be times by 10, or you change function get_selectedIndexList'column_number = int(batch_size / 10)classId_ndarray = np.unique(test_y)selected_index_list = []for i, classId in enumerate(classId_ndarray):index_ndarray = np.where(test_y==classId)[0]selected_index_ndarray = np.random.choice(index_ndarray, column_number)selected_index_list.extend(selected_index_ndarray.tolist())return selected_index_list def batch_draw_images_2(model, selected_index_list, test_imageData, test_X, test_y, id2name_dict):true_imageData = test_imageData[selected_index_list]true_X = test_X[selected_index_list]true_y = np.array(test_y)[selected_index_list]predict_Y = model.predict(true_X)predict_y = np.argmax(predict_Y, axis=1)row_number = math.ceil(batch_size ** 0.5)column_number = row_numberplt.figure(figsize=(row_number+8, column_number+8))for i in range(row_number):for j in range(column_number):index = i * column_number + jif index < batch_size:position = (row_number, column_number, index+1)image = true_imageData[index]actual_classId = true_y[index]predict_classId = predict_y[index]isTrue = actual_classId==predict_classIdactual_className = id2name_dict[actual_classId]predict_className = id2name_dict[predict_classId]title = 'actual:%s\npredict:%s' %(actual_className, predict_className)draw_image(position, image, title, isTrue)batch_size = 100 className_list = ['飛機', '汽車', '鳥', '貓', '鹿', '狗', '青蛙', '馬', '船', '卡車'] id2name_dict = {a:b for a, b in enumerate(className_list)} selected_index_list = get_selectedIndexList(test_y, batch_size) batch_draw_images_2(model, selected_index_list, test_imageData, test_X, test_y, id2name_dict) plt.show()7.Keras中權重文件的讀寫
7.1 使用load_model方法加載模型文件
from keras.models import load_model from keras.optimizers import Adammodel_filePath = '../resources/saved_models/cifar10_ResNet56v2_model.162.h5' model = load_model(model_filePath) model.compile(loss='categorical_crossentropy',optimizer=Adam(lr=0.001),metrics=['accuracy'])7.2 使用save_weights方法保存權重文件
weights_h5FilePath = '../resources/saved_models/resnet56v2_weights.h5' model.save_weights(weights_h5FilePath)7.3 使用load_weights方法加載權重文件
input_shape = (32, 32, 3) depth = 56 model = resnet_v2(input_shape, depth) weights_h5FilePath = '../resources/saved_models/resnet56v2_weights.h5' model.load_weights(weights_h5FilePath) model.compile(loss='categorical_crossentropy',optimizer=Adam(lr=0.001),metrics=['accuracy'])8. 圖像分類:純終端運行
服務端開啟服務后,flask庫等待接收post請求。
客戶端在cmd中傳入需要檢測圖片的路徑,requests庫發起post請求并打印返回值。
新建:模型部署(在線預測)文件夾,創建以下兩個文件夾,將網盤資源放入resources中。鏈接:https://pan.baidu.com/s/1X0PbGLnjO2vQsQFcwdMgYw 提取碼:z8ax
在resources文件夾中:
在yolov3文件夾中:
8.1 在code文件夾下新建image_server.py寫入下面服務端代碼
# -*- coding: utf-8 -*- # 導入常用的庫 import time import os import cv2 import numpy as np from flask import request, Flask # 導入flask庫的Flask類和request對象 app = Flask(__name__) #實例化Flask對象,這個對象變量是appimport pickle # 訓練時保存的pixel_mean.pickle ,導入pickle加載圖像數據要減去的(像素均值pixel_mean) with open('../resources/pixel_mean.pickle', 'rb') as file: pixel_mean = pickle.load(file) #'rb'是以二進制流讀入文件賦給file變量className_list = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'] id2name_dict = {a:b for a, b in enumerate(className_list)} # 定義字典id2name_dict:把種類索引 轉換為 種類名稱 from keras.models import load_model from keras.optimizers import Adam model_filePath = '../resources/cifar10_ResNet56v2_model.162.h5' # 加載圖像分類模型ResNet56 model = load_model(model_filePath) model.compile(loss='categorical_crossentropy',optimizer=Adam(lr=0.001),metrics=['accuracy']) def get_imageNdarray(imageFilePath):image_ndarray = cv2.imread(imageFilePath) # 根據圖片文件路徑,cv2.imread獲取圖像數據矩陣 resized_image_ndarray = cv2.resize(image_ndarray,(32, 32), #大小統一變為32*32interpolation=cv2.INTER_AREA)return resized_image_ndarraydef process_imageNdarray(image_ndarray, pixel_mean): #模型預測前必要的圖像處理,image_ndarray是32*32*3和pixel_mean大小一樣rgb_image_ndarray = image_ndarray[:, :, ::-1]image_ndarray_1 = rgb_image_ndarray / 255image_ndarray_2 = image_ndarray_1 - pixel_meanreturn image_ndarray_2def predict_image(model, imageFilePath, id2name_dict): #使用模型對指定圖片文件路徑完成圖像分類,返回值為預測的種類名稱 image_ndarray = get_imageNdarray(imageFilePath) #get_imageNdarray方法獲得圖像數據矩陣processed_image_ndarray = process_imageNdarray(image_ndarray, pixel_mean)#process_imageNdarray方法獲得圖像處理后的數據inputs = processed_image_ndarray[np.newaxis, ...] #np.newaxis將32*32*3變為1*32*32*3,因為模型只接受4維矩陣predict_Y = model.predict(inputs)[0] # 4維第一維表示batch,所以可輸入多張圖片predict_y = np.argmax(predict_Y) # Y:對于每個類別的預測概率,y:哪個類別概率最大predict_className = id2name_dict[predict_y] # 種類id轉成種類名稱print('對此圖片路徑 %s 的預測結果為 %s' %(imageFilePath, predict_className))return predict_className@app.route("/", methods=['POST'])# 只有下面函數和服務端相關,其他都是定義一個函數然后返回結果 # 定義回調(callback)函數,接收來自/的post請求,并返回預測結果。回調函數就像是開了一個線程,我先運行主線程,相關的計算問題拋給回調函數,讓它先算,我繼續運行我的程序,等你算出來了發回給我就行,不影響我當前執行 def anyname_you_like(): # 其他地方不會引用該函數startTime = time.time() # 單位是秒received_file = request.files['file'] #往下涉及到了前端:因為request(從flask庫導入)是個對象所以才有.files屬性,里面的'file'對應image_client.py中file_dict(按要求輸入)中'file'imageFileName = received_file.filename #received_file類似于列表if received_file: # 如果不為空received_dirPath = '../resources/received_images' #上一級文件夾if not os.path.isdir(received_dirPath):os.makedirs(received_dirPath) #如果沒有的話就創建它imageFilePath = os.path.join(received_dirPath, imageFileName)#(圖像文件名)放入(圖像文件夾)中形成(圖像文件路徑)received_file.save(imageFilePath)#將(接收到的文件)保存到(文件路徑)print('圖片文件保存到此路徑:%s' % imageFilePath)usedTime = time.time() - startTimeprint('接收圖片并保存,總共耗時%.2f秒' % usedTime)startTime = time.time() #調用predict_image方法,上面定義過predict_className = predict_image(model, imageFilePath, id2name_dict)usedTime = time.time() - startTimeprint('完成對接收圖片的分類預測,總共耗時%.2f秒' % usedTime)return predict_classNameelse:return 'failed'# 主函數 if __name__ == "__main__":print('在開啟服務前,先測試predict_image函數')imageFilePath = '../resources/images/001.jpg'predict_className = predict_image(model, imageFilePath, id2name_dict) #測試函數有沒有寫對,寫錯的話下一步就不進行,model是在外面定義的,在main方法里可以用。app.run("127.0.0.1", port=5000) #第一個參數ip地址,第二個參數是開在哪個端口#如果接收到 127.0.0.1:5000/ 的話調用上面回調函數anyname_you_like()方法,如果/后加其他東西就要寫其他回調函數8.2 在code文件夾下新建image_client.py寫入下面客戶端代碼
import requests import osif __name__ == "__main__": # 主函數 url = "http://127.0.0.1:5000"while True: #可循環輸入圖片input_content = input('輸入圖片路徑,輸入-1退出,默認值(../resources/images/001.jpg): ') #input()以字符串的方式獲取用戶輸入if input_content.strip() == "": input_content = '../resources/images/001.jpg'if input_content.strip() == "-1":breakelif not os.path.exists(input_content.strip()):print('輸入圖片路徑不正確,請重新輸入')else:imageFilePath = input_content.strip() # 下面是正文:怎么把數據發過去# 下面一行用os.path.split將文件路徑中文件名提取出來,第一段文件夾名,第二段文件名imageFileName = os.path.split(imageFilePath)[1] # 取第二段文件名 file_dict = { # file_dict為一字典,鍵是'file',值是一元組包含三元素 #必須按file_dict方法傳入'file':(imageFileName, # 做post請求時有這三行規范,第一段文件名open(imageFilePath,'rb'), # 第二段二進制流'image/jpg')} # 第三段后綴名類型 result = requests.post(url, files=file_dict) # 這里files參數(默認)對應服務端的filespredict_className = result.text # result是響應response為一對象有.text屬性,result.text為一字符串print('圖片路徑:%s 預測結果為:%s\n' %(imageFilePath, predict_className))8.3 cmd文件雙擊啟動
在code文件夾下新建名為開啟服務_ResNet56v2圖片分類模型.cmd文件,寫入:
python image_server.py pause在code文件夾下新建名為客戶端發送圖片數據做分類檢測.cmd文件,寫入:
python image_client.py pause運行結果:傳入如下圖圖片路徑如:../resources/images/004jpg
9.圖像分類:有web頁面
提交post請求后,不做網頁跳轉,停留在原始網頁。從而使圖像、預測種類名同時顯示在同一網頁,在網頁中可以改變圖片的顯示大小。
9.1 新建_11_web_image_server_2.py文件寫入下面服務端代碼:
# -*- coding: utf-8 -*- # 導入常用的庫 import time import os import cv2 import numpy as np # 導入flask庫 from flask import Flask, render_template, request, jsonify # render_template為返回一個網頁,返回jsonify數據 # Flask, render_template, request, jsonify是四個方法from keras.models import load_model from keras.optimizers import Adam # 導入pickle庫 import pickle# 實例化Flask對象 app = Flask(__name__) # 設置開啟web服務后,如果更新html文件,可以使更新立即生效 app.jinja_env.auto_reload = True app.config['TEMPLATES_AUTO_RELOAD'] = True# 調用pickle庫的load方法,加載圖像數據處理時需要減去的像素均值pixel_mean with open('../resources/pixel_mean.pickle', 'rb') as file:pixel_mean = pickle.load(file) # 加載已經訓練好的cifar10數據集的ResNet56圖片分類模型的模型文件 model_filePath = '../resources/cifar10_ResNet56v2_model.162.h5' model = load_model(model_filePath) model.compile(loss='categorical_crossentropy',optimizer=Adam(lr=0.001),metrics=['accuracy']) # 定義函數classId_to_className,把種類索引轉換為種類名稱 def classId_to_className(classId):category_list = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']className = category_list[classId]return className# 根據圖片文件的路徑獲取圖像矩陣 def get_imageNdarray(imageFilePath):image_ndarray = cv2.imread(imageFilePath)resized_image_ndarray = cv2.resize(image_ndarray,(32, 32),interpolation=cv2.INTER_AREA)return resized_image_ndarray# 模型預測前必要的圖像處理 def process_imageNdarray(image_ndarray, pixel_mean):rgb_image_ndarray = image_ndarray[:, :, ::-1]image_ndarray_1 = rgb_image_ndarray / 255image_ndarray_2 = image_ndarray_1 - pixel_meanreturn image_ndarray_2# 使用模型對指定圖片文件路徑完成圖像分類,返回值為預測的種類名稱 def predict_image(model, imageFilePath):image_ndarray = get_imageNdarray(imageFilePath)processed_image_ndarray = process_imageNdarray(image_ndarray, pixel_mean)inputs = processed_image_ndarray[np.newaxis, ...]predict_Y = model.predict(inputs)[0]predict_y = np.argmax(predict_Y)predict_classId = predict_ypredict_className = classId_to_className(predict_classId)print('對此圖片路徑 %s 的預測結果為 %s' %(imageFilePath, predict_className))return predict_className# 訪問首頁時的調用函數 @app.route('/') def index_page(): # flask庫要求'_05_web_page.html'必須在templates文件夾下 return render_template('_05_web_page.html') # anyname_you_like()是使用predict_image這個API服務時的調用函數 ,POST指向一個url鏈接 # 下行/前:.route是IP地址和端口。/后:predict_image:調用下行anyname_you_like()方法 @app.route("/predict_image", methods=['POST']) def anyname_you_like():startTime = time.time()# 解析接收到的圖片 # post傳送 request請求 ,post過來是個字典file相當于一個鍵received_file = request.files['input_image'] #input_image對應_05_web_page.html中input_imageimageFileName = received_file.filenameif received_file:# 保存接收的圖片到指定文件夾received_dirPath = '../resources/received_images'if not os.path.isdir(received_dirPath):os.makedirs(received_dirPath)imageFilePath = os.path.join(received_dirPath, imageFileName)received_file.save(imageFilePath)print('圖片文件保存到此路徑:%s' % imageFilePath)usedTime = time.time() - startTimeprint('接收圖片并保存,總共耗時%.2f秒' % usedTime)# 對指定圖片路徑的圖片做分類預測,并打印耗時,返回預測種類名稱startTime = time.time()predict_className = predict_image(model, imageFilePath)usedTime = time.time() - startTimeprint('完成對接收圖片的分類預測,總共耗時%.2f秒' % usedTime)return jsonify(predict_className=predict_className) # jsonify是個字典 # 所有post請求返回要么jsonify,要么新的網頁鏈接# 主函數(和之前一樣) if __name__ == "__main__":print('在開啟服務前,先測試predict_image函數')imageFilePath = '../resources/images/001.jpg'predict_className = predict_image(model, imageFilePath)app.run("127.0.0.1", port=5000)9.2 新建templates文件夾里_12_web_page.html文件
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"> #meta參數意思,字符編碼設為UTF-8<title>實現需求web</title> #對應頁面標題<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> #從網絡下載并運用jQuery庫<script>$(document).ready(function(){ #$符就是find element$("#image_file").change(function(){ #不管什么標簽,只要id="image_file",就找到下面input這個元素,只要發生改變change,函數function()作為參數傳入change這個方法里var file = $(this)[0].files[0]; #取到文件$(".img").attr("src", URL.createObjectURL(file));}); # #號表示id=,.號表示class=$("button#button_1").click(function(){ #當被點擊促發function()函數var formData = new FormData($("#upload_form")[0]); #找("#upload_form")這個元素,結果是一個list,找到第一個。$("#upload_form")[0]這個作為參數傳給FormData,FormData變為對象再賦值給formData變量,formData變量在下面被調用,不能寫在函數外,唯獨python可以$.ajax({ #字典類型url: "/predict_image", #對應.py文件中"/predict_image"發起POST請求type: 'POST',data: formData, #發送表格數據processData: false,contentType: false,success: function(return_data){ #如果發出請求成功傳回結果$("#image_className").text("圖片中物體的種類名稱:" + return_data["predict_className"]) #找到"#image_className"標簽,改文本內容為后面}, #"predict_className"是個鍵,找到對應值error: function(return_data){ #返回的狀態碼是404alert("上傳失敗!")}}) });}); </script></head><body> #<body>里是網頁顯示的內容<form id="upload_form" enctype="multipart/form-data"> #</form>做表格,方便做post請求<input type="file" name="input_image" id="image_file"/></form> <div> #<div>就是分開一行<img src="" class="img" /></div><button id="button_1">提交信息</button> #設置一個按鈕顯示提交信息<p id="image_className"></p> #p代表文本</body> </html>9.3 cmd文件雙擊啟動
在code文件夾下新建名為_11_開啟web服務.cmd文件,寫入
python _11_web_image_server_2.py pause復制下圖綠框到瀏覽器,每上傳一張圖,resources文件夾里多出received_images文件夾,里面存著上傳的圖片。
10.目標檢測:純終端運行
10.1 新建_21_yolov3.py文件
# -*- coding: utf-8 -*- # 導入常用的庫 import os import time import numpy as np import cv2 # 導入keras庫 from keras import backend as K from keras.layers import Input # 導入yolo3文件夾中mode.py、utils.py這2個代碼文件中的方法 from yolo3.model import yolo_eval, yolo_body from yolo3.utils import letterbox_image # 導入PIL畫圖庫 from PIL import Image, ImageFont, ImageDraw# 通過種類的數量,每個種類對應的顏色,顏色變量color為rgb這3個數值組成的元組 import colorsys def get_colorList(category_quantity):hsv_list = []for i in range(category_quantity):hue = i / category_quantitysaturation = 1value = 1hsv = (hue, saturation, value)hsv_list.append(hsv)colorFloat_list = [colorsys.hsv_to_rgb(*k) for k in hsv_list]color_list = [tuple([int(x * 255) for x in k]) for k in colorFloat_list]return color_list # 定義類Detector class Detector(object):defaults = {"weights_h5FilePath": '../resources/yolov3/yolov3_weights.h5',"anchor_txtFilePath": '../resources/yolov3/yolov3_anchors.txt',"category_txtFilePath": '../resources/yolov3/coco.names',"score" : 0.3,"iou" : 0.35,"model_image_size" : (416, 416) #must be a multiple of 32}@classmethoddef get_defaults(cls, n):if n in cls.defaults:return cls.defaults[n]else:return 'Unrecognized attribute name "%s"' %n# 實例化對象后的初始化方法def __init__(self, **kwargs):self.__dict__.update(self.defaults) # 設置默認值self.__dict__.update(kwargs) # 根據傳入的參數更新self.category_list = self.get_categoryList()self.anchor_ndarray = self.get_anchorNdarray()self.session = K.get_session()self.boxes, self.scores, self.classes = self.generate()# 從文本文件中解析出物體種類列表category_list,要求每個種類占一行def get_categoryList(self):with open(self.category_txtFilePath, 'r', encoding='utf8') as file:fileContent = file.read()line_list = [k.strip() for k in fileContent.split('\n') if k.strip()!='']category_list = line_listreturn category_list # 從表示anchor的文本文件中解析出anchor_ndarraydef get_anchorNdarray(self):with open(self.anchor_txtFilePath, 'r', encoding='utf8') as file:number_list = [float(k) for k in file.read().split(',')]anchor_ndarray = np.array(number_list).reshape(-1, 2)return anchor_ndarray# 加載模型,準備好繪圖的顏色,準備模型運算的輸出def generate(self):# 在Keras中,如果模型訓練完成后只保存了權重,那么需要先構建網絡,再加載權重文件num_anchors = len(self.anchor_ndarray)num_classes = len(self.category_list)self.yolo_model = yolo_body(Input(shape=(None, None, 3)),num_anchors//3,num_classes)self.yolo_model.load_weights(self.weights_h5FilePath)# 給不同類別的物體準備不同顏色的方框category_quantity = len(self.category_list)self.color_list = get_colorList(category_quantity)# 目標檢測的輸出:方框box,得分score,類別classself.input_image_size = K.placeholder(shape=(2, ))boxes, scores, classes = yolo_eval(self.yolo_model.output,self.anchor_ndarray,category_quantity,self.input_image_size,score_threshold=self.score,iou_threshold=self.iou)return boxes, scores, classes# 檢測圖片def detect_image(self, image):startTime = time.time()# 模型網絡結構運算所需的數據準備boxed_image = letterbox_image(image, tuple(reversed(self.model_image_size)))image_data = np.array(boxed_image).astype('float') / 255image_data = np.expand_dims(image_data, 0) # Add batch dimension.# 模型網絡結構運算out_boxes, out_scores, out_classes = self.session.run([self.boxes, self.scores, self.classes],feed_dict={self.yolo_model.input: image_data,self.input_image_size: [image.size[1], image.size[0]],})# 調用ImageFont.truetype方法實例化畫圖字體對象font = ImageFont.truetype(font='../resources/yolov3/FiraMono-Medium.otf',size=np.floor(2e-2 * image.size[1] + 0.5).astype('int32'))thickness = (image.size[0] + image.size[1]) // 300# 循環繪制若干個方框for i, c in enumerate(out_classes):# 調用ImageDraw.Draw方法實例化畫圖對象draw = ImageDraw.Draw(image)# 定義方框上方文字內容predicted_class = self.category_list[c]score = out_scores[i]label = '{} {:.2f}'.format(predicted_class, score)label_size = draw.textsize(label, font)box = out_boxes[i]top, left, bottom, right = boxtop = max(0, np.floor(top + 0.5).astype('int32'))left = max(0, np.floor(left + 0.5).astype('int32'))bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))right = min(image.size[0], np.floor(right + 0.5).astype('int32'))# 如果方框在圖片中的位置過于靠上,調整文字區域if top - label_size[1] >= 0:text_origin = np.array([left, top - label_size[1]])else:text_origin = np.array([left, top + 1])# 方框厚度為多少,則畫多少個矩形for j in range(thickness):draw.rectangle([left + j, top + j, right - j, bottom - j],outline=self.color_list[c])# 繪制方框中的文字draw.rectangle([tuple(text_origin), tuple(text_origin + label_size)],fill=self.color_list[c])draw.text(text_origin, label, fill=(0, 0, 0), font=font)del draw# 打印檢測圖片使用的時間usedTime = time.time() - startTimeprint('檢測這張圖片用時%.2f秒' %(usedTime))return image# 使用cv2庫顯示圖片 def cv2_display(image_ndarray):windowName = "object_detection_result"cv2.imshow(windowName, image_ndarray)while True:pressKey = cv2.waitKey(0)# 按Esc鍵或者q鍵可以關閉顯示窗口if 27 == pressKey or ord('q') == pressKey:cv2.destroyAllWindows()break# 主函數 if __name__ == "__main__":detector = Detector(weights_h5FilePath='../resources/yolov3/yolov3_weights.h5',anchor_txtFilePath='../resources/yolov3/yolov3_anchors.txt',category_txtFilePath='../resources/yolov3/coco.names')while True:input_content = input('輸入圖片路徑,輸入-1退出,默認值(../resources/images/person.jpg): ')if input_content.strip() == "":input_content = '../resources/images/person.jpg'if input_content.strip() == "-1":breakelif not os.path.exists(input_content.strip()):print('輸入圖片路徑不正確,請重新輸入')else:imageFilePath = input_content.strip()image = Image.open(imageFilePath)drawed_image = detector.detect_image(image)# 使用cv2庫顯示已經畫框的圖片,cv2庫圖像數據矩陣第3維度的通道順序是bgrdrawed_image_ndarray = np.array(drawed_image)bgr_image_ndarray = drawed_image_ndarray[..., ::-1]cv2_display(bgr_image_ndarray)10.2 cmd文件雙擊啟動
在code文件夾下新建名為_22_使用YOLOv3檢測單張圖片.cmd文件,寫入
python _21_yolov3.py pause在cmd中輸入圖片路徑,或者按Enter鍵使用默認值,運行結果如下圖所示。在cv窗口界面可以按Esc鍵或者q鍵退出,在cmd中可以多次輸入路徑檢測多張圖片。
總結
以上是生活随笔為你收集整理的【Python2】Keras_ResNet 在Cifar10数据集上分类,Flask框架部署目标检测模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Python1】双系统安装,深度学习环
- 下一篇: 【Python3】Tensorflow_