本專欄參考的原作者文章聲明如下。
PS:本專欄對原作者的文章存在適當的修改與補充,使之更適合本作者所闡述的訓練要求!如有侵權,請聯系13512076879@163.com。
————————————————
版權聲明:本文為CSDN博主「欣欣以向榮」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_37783617/article/details/96866163
————————————————
1. caffe框架下openmv的訓練步驟
目前 OPenMV 只提供Caffe模型到network網絡的轉換,未來可能會支持TensorFlow,但目前不行。通過Caffe框架學習,我們最終的目標肯定是得到 ******.network 的網絡庫文件
訓練網絡的主要步驟如下:
- 配置環境,安裝Caffe
- 采集數據集
- 訓練網絡
- 量化模型
- 將模型轉換為二進制格式
- 在OPenMV上部署模型
- 運行網絡
- 故障排除
2.caffe環境的搭建(以本文環境為例介紹)
- windows 10
- python 2.7
- pycharm
- vs2013
- openmv cam h4
- openmv ide
3.vs2013 編譯caffe
本專題請參考我的另一篇文章:https://www.cnblogs.com/clayyjh/p/12630126.html
4.制作數據集
由於原文章之前訓練使用的數據集為64*64,然而這種方法訓練得到的network模型對於openmv來說太大,會造成堆內存溢出,無法運行。
故本文使用數據集為32*32.
本文使用的數據集為使用畫圖板制作,並經過一些數據處理方法,使得數據集更加豐富。
制作步驟:
4.1.使用以下代碼新建文件夾保存原始數據(E:/pydoc/blog/為本文程序根目錄 E:/pydoc/blog/為本文程序根目錄 E:/pydoc/blog/為本文程序根目錄)
import os, sys def genDir(): base = 'E:/pydoc/blog/MY_numbers/' i = 0 name=['ZERO','ONE','TWO','THREE','FOUR','FIVE','SIX','SEVEN','EIGHT','NINE'] for j in range(10): file_name = base+name[i] os.mkdir(file_name) i=i+1 genDir()
結果如圖:
4.2. 使用以下代碼新建文件夾保存擴展數據:
import os, sys def genDir(): base = 'E:/pydoc/blog/MY_numbers/' i = 0 name=['ZERO','ONE','TWO','THREE','FOUR','FIVE','SIX','SEVEN','EIGHT','NINE'] for j in range(10): file_name = base+str(i)+'_'+name[i] os.mkdir(file_name) i=i+1 genDir()
結果如圖:
4.3 制作原始數據集.
利用畫圖板制作手寫體數據集,0-9每個5張圖片,大小為32*32.
打開畫圖板
將這些圖片對應地保存到./MY_numbers/ZERO(ONE,TWO,···,NINE)
4.4.使用以下代碼擴展數據集,代碼保存到根目錄,命名為augment_images.py。
import os, sys import argparse import random import cv2 import numpy as np import imgaug as ia from imgaug import augmenters as iaa from tqdm import tqdm def main(): # CMD args parser parser = argparse.ArgumentParser(description='Augment image datasets') parser.add_argument("--input", action = "store", help = "Input images dir") parser.add_argument("--output", action = "store", help = "Output images dir") parser.add_argument("--count", action = "store", help = "Number of augmented sets to make", type=int, default=1) # Parse CMD args args = parser.parse_args() if (args.input == None or args.output == None): parser.print_help() sys.exit(1) ia.seed(1) paths = os.listdir(args.input) for x in range(args.count): seq = iaa.Sequential([ iaa.Fliplr(0.5), # horizontal flips # Small gaussian blur with random sigma between 0 and 0.5. # But we only blur about 50% of all images. iaa.Sometimes(0.5, iaa.GaussianBlur(sigma=(0, 0.2)) ), # Add gaussian noise. # For 50% of all images, we sample the noise once per pixel. # For the other 50% of all images, we sample the noise per pixel AND # channel. This can change the color (not only brightness) of the pixels. iaa.Sometimes(0.5, iaa.AdditiveGaussianNoise( loc=0, scale=(0.0, 0.005*255), per_channel=0.5 ) ), # Make some images brighter and some darker. # In 20% of all cases, we sample the multiplier once per channel, # which can end up changing the color of the images. iaa.Sometimes(0.5, iaa.Multiply((0.8, 1.2), per_channel=0.0), ), # Apply affine transformations to each image. # Scale/zoom images. iaa.Sometimes(0.5, iaa.Affine( rotate=(-20, 20), ), ), # Translate/move images. iaa.Sometimes(0.5, iaa.Affine( scale={"x": (0.8, 1.2), "y": (0.8, 1.2)}, ), ), # Rotate images. iaa.Sometimes(0.5, iaa.Affine( translate_percent={"x": (-0.1, 0.1), "y": (-0.1, 0.1)}, ), ), ], random_order=True) # apply augmenters in random order print("Augmenting images set %d/%d"%(x+1, args.count)) for i in tqdm(xrange(len(paths))): img = cv2.imread(args.input+'/'+paths[i], cv2.IMREAD_GRAYSCALE) img = seq.augment_image(img) f = os.path.splitext(paths[i]) cv2.imwrite(args.output+'/'+f[0] + '_aug%d'%(x) + f[1], img) print('Finished processing all images\n') if __name__ == '__main__': main()
4.5. 使用批處理腳本運行augment_images.py,命名為augment_pic.bat。
python augment_images.py --input MY_numbers/ZERO/ --output MY_numbers/0_ZERO/ --count 20 echo. python augment_images.py --input MY_numbers/ONE/ --output MY_numbers/1_ONE/ --count 20 echo. python augment_images.py --input MY_numbers/TWO/ --output MY_numbers/2_TWO/ --count 20 echo. python augment_images.py --input MY_numbers/THREE/ --output MY_numbers/3_THREE/ --count 20 echo. python augment_images.py --input MY_numbers/FOUR/ --output MY_numbers/4_FOUR/ --count 20 echo. python augment_images.py --input MY_numbers/FIVE/ --output MY_numbers/5_FIVE/ --count 20 echo. python augment_images.py --input MY_numbers/SIX/ --output MY_numbers/6_SIX/ --count 20 echo. python augment_images.py --input MY_numbers/SEVEN/ --output MY_numbers/7_SEVEN/ --count 20 echo. python augment_images.py --input MY_numbers/EIGHT/ --output MY_numbers/8_EIGHT/ --count 20 echo. python augment_images.py --input MY_numbers/NINE/ --output MY_numbers/9_NINE/ --count 20 pause
結果如下:
4.6 根目錄新建文件./blog/data.將擴展數據文件夾拷貝到data文件夾下。
4.7 在./blog目錄下,按住shift,鼠標右鍵打開powershell窗口,輸入tree命令,文件結構如下:
4.8 至此,數據集制作完畢。
5. 制作數據標簽。
5.1 制作lmdb標簽
5.1.1 新建create_labels.py 文件,代碼如下:
# coding=utf-8
import os, sys import argparse import random import numpy as np from tqdm import tqdm import time import shutil def shuffle_in_unison(a, b): assert len(a) == len(b) shuffled_a = np.empty(a.shape, dtype=a.dtype) shuffled_b = np.empty(b.shape, dtype=b.dtype) permutation = np.random.permutation(len(a)) for old_index, new_index in enumerate(permutation): shuffled_a[new_index] = a[old_index] shuffled_b[new_index] = b[old_index] return shuffled_a, shuffled_b def move_files(input, output): ''' Input: 數據集文件夾,不同分類的數據存儲在不同子文件夾中 Output: 輸出的所有文件,文件命名格式為 class_number.jpg; 輸出必須是絕對路徑 ''' index = -1 for root, dirs, files in os.walk(input): if index != -1: print 'Working with path', root print 'Path index', index filenum = 0 for file in (files if index == -1 else tqdm(files)): fileName, fileExtension = os.path.splitext(file) if fileExtension == '.jpg' or fileExtension == '.JPG' or fileExtension == '.png' or fileExtension == '.PNG': full_path = os.path.join(root, file) # print full_path if (os.path.isfile(full_path)): file = os.path.basename(os.path.normpath(root)) + str(filenum) + fileExtension try: test = int(file.split('_')[0]) except: file = str(index) + '_' + file # print os.path.join(output, file) shutil.copy(full_path, os.path.join(output, file)) filenum += 1 index += 1 def create_text_file(input_path, percentage): ''' 為 Caffe 創建 train.txt 和 val.txt 文件 ''' images, labels = [], [] os.chdir(input_path) for item in os.listdir('.'): if not os.path.isfile(os.path.join('.', item)): continue try: label = int(item.split('_')[0]) images.append(item) labels.append(label) except: continue images = np.array(images) labels = np.array(labels) images, labels = shuffle_in_unison(images, labels) X_train = images[0:int(len(images) * percentage)] y_train = labels[0:int(len(labels) * percentage)] X_test = images[int(len(images) * percentage):] y_test = labels[int(len(labels) * percentage):] os.chdir('..') trainfile = open("train.txt", "w") for i, l in zip(X_train, y_train): trainfile.write(i + " " + str(l) + "\n") testfile = open("test.txt", "w") for i, l in zip(X_test, y_test): testfile.write(i + " " + str(l) + "\n") trainfile.close() testfile.close() def main(): # CMD 指令參數 parser = argparse.ArgumentParser(description='Create label files for an image dataset') parser.add_argument("--input", action = "store", help = "Input images dir") parser.add_argument("--output", action = "store", help = "Output images dir") parser.add_argument("--percentage", action = "store", help = "Test/Train split", type=float, default=0.85) #測試數據占訓練數據的比重 # Parse CMD args args = parser.parse_args() if (args.input == None or args.output == None): parser.print_help() sys.exit(1) move_files(args.input, args.output) create_text_file(args.output, args.percentage) print('Finished processing all images\n') if __name__ == '__main__': main()
5.1.2 新建文件夾./blog/lmdbin,制作批處理腳本create_lists.bat.
python create_labels.py --input data/ --output lmdbin/ pause
運行腳本,根目錄會生成兩個文件 train.txt 和 test.txt:
現在已經得到了訓練數據的清單
5.1.3 生成lmdb
生成LMDB格式數據需要使用Caffe自帶的函數 convert_imageset,所以這些函數運行需要在編譯好地caffe文件夾下運行,否則會報錯!!!
函數介紹:
convert_imageset [FLAGS] ROOTFOLDER/ LISTFILE DB_NAME
FLAGS這個參數組的內容: -gray: 是否以灰度圖的方式打開圖片。程序調用opencv庫中的imread()函數來打開圖片,默認為false -shuffle: 是否隨機打亂圖片順序。默認為false -backend:需要轉換成的db文件格式,可選為leveldb或lmdb,默認為lmdb -resize_width/resize_height: 改變圖片的大小。在運行中,要求所有圖片的尺寸一致,因此需要改變圖片大小。 程序調用opencv庫的resize()函數來對圖片放大縮小,默認為0,不改變 -check_size: 檢查所有的數據是否有相同的尺寸。默認為false,不檢查 -encoded: 是否將原圖片編碼放入最終的數據中,默認為false -encode_type: 與前一個參數對應,將圖片編碼為哪一個格式:‘png','jpg'...... ———————————————— 版權聲明:本文為CSDN博主「欣欣以向榮」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。 原文鏈接:https://blog.csdn.net/qq_37783617/article/details/96841981
ROOTFOLDER/: 圖片存放的絕對路徑,lmdbin的路徑
LISTFILE: 圖片文件列表清單,一般為一個txt文件,一行一張圖片
DB_NAME: 最終生成的db文件存放目錄
執行腳本文件:(腳本需要在./caffe-master/Build/x64/Release/下)
convert_imageset --shuffle E:/pydoc/blog/lmdbin/ E:/pydoc/blog/train.txt E:/pydoc/blog/train_lmdb
echo.
convert_imageset --shuffle E:/pydoc/blog/lmdbin/ E:/pydoc/blog/test.txt E:/pydoc/blog/test_lmdb
根目錄里生成了test_lmdb文件夾和train_lmdb文件夾
上述兩個文件夾下各生成兩個數據包:
到此,lmdb的數據集准備完成!
如果有需要,可以執行腳本生成均值文件:
優點:圖片減去均值再訓練,會提高訓練速度和精度。因此,一般都會有這個操作。
但是必須保證所有圖片的規格大小一致
執行腳本:(腳本需要在./caffe-master/Build/x64/Release/下)
compute_image_mean -backend=lmdb E:/pydoc/blog/train_lmdb mean.binaryproto
pause
6.訓練神經網絡
6.1 准備prototxt文件
下載openmv-master,解壓到./blog文件夾下。
打開openmv-master\ml\cmsisnn\models\lenet,可以看到:
lenet.network (適用於OPenMV的神經網絡,是一個二進制文件)
lenet_solver.prototxt (供Caffe使用的配置訓練參數的文件)
lenet_train_test.prototxt (網絡各層訓練和測試的參數)
test.sh (Linux腳本文件,用於測試模型)
train.sh(Linux腳本文件,用於訓練模型)
我們把后四個文件拷貝到要網絡的根目錄下備用!!!
6.2 修改訓練參數
6.2.1打開lenet_solver.prototxt文件
修改紅圈的幾處地方:
第一處:net: "[lenet_train_test.prototxt文件的存放路徑]"
第二處:test_iter: [該數值表示測試每次數據的量]=測試數據總量/batch_size
比如我們有150個測試數據,每次測試10個就只需要測試15次,修改test_iter:15
第三處:最大迭代次數(根據數據集大小設定) 過小精度低,過大會導致震盪
第四處:快照次數,根據訓練數據集大小設定
第五處:快照保存的地址
第六處:如果之前caffe編譯是在CPU環境下,此處改為CPU。
6.2.2打開lenet_train_test.prototxt文件
name: "LeNet" layer { name: "data" type: "Data" top: "data" top: "label" include { phase: TRAIN } transform_param { scale: 0.00390625 mean_file: "mean.binaryproto" //沒有均值文件的刪除該行 } data_param { source: "train_lmdb" //訓練用lmdb文件夾的相對地址 batch_size: 32 //訓練間隔,一般為64,我的數據集小,使用32 backend: LMDB } } layer { name: "data" type: "Data" top: "data" top: "label" include { phase: TEST } transform_param { scale: 0.00390625 mean_file: "mean.binaryproto" //沒有均值文件的刪除該行 } data_param { source: "test_lmdb" //測試用lmdb文件夾的相對地址 batch_size: 10 //測試間隔,與前一個文件中的test_iter有對應關系 10*15=150(測試數量) backend: LMDB //數據集存儲結構 } } layer { name: "conv1" type: "Convolution" bottom: "data" top: "conv1" param { lr_mult: 1 } param { lr_mult: 2 } convolution_param { num_output: 20 kernel_size: 5 //如果圖片規格小,可以適當減小卷積核的大小 stride: 1 weight_filler { type: "xavier" } bias_filler { type: "constant" } } } layer { name: "pool1" type: "Pooling" bottom: "conv1" top: "pool1" pooling_param { pool: MAX kernel_size: 2 stride: 2 } } layer { name: "conv2" type: "Convolution" bottom: "pool1" top: "conv2" param { lr_mult: 1 } param { lr_mult: 2 } convolution_param { num_output: 50 kernel_size: 5 //如果圖片規格小,可以適當減小卷積核的大小 stride: 1 weight_filler { type: "xavier" } bias_filler { type: "constant" } } } layer { name: "pool2" type: "Pooling" bottom: "conv2" top: "pool2" pooling_param { pool: MAX kernel_size: 2 stride: 2 } } layer { name: "ip1" type: "InnerProduct" bottom: "pool2" top: "ip1" param { lr_mult: 1 } param { lr_mult: 2 } inner_product_param { num_output: 100 weight_filler { type: "xavier" } bias_filler { type: "constant" } } } layer { name: "relu1" type: "ReLU" bottom: "ip1" top: "ip1" } layer { name: "ip2" type: "InnerProduct" bottom: "ip1" top: "ip2" param { lr_mult: 1 } param { lr_mult: 2 } inner_product_param { num_output: 10 weight_filler { type: "xavier" } bias_filler { type: "constant" } } } layer { name: "accuracy" type: "Accuracy" bottom: "ip2" bottom: "label" top: "accuracy" include { phase: TEST } } layer { name: "loss" type: "SoftmaxWithLoss" bottom: "ip2" bottom: "label" top: "loss" }
6.3 訓練模型
6.3.1 編寫 train.bat : (腳本需要在./caffe-master/Build/x64/Release/下)
caffe train --solver=E:/pydoc/blog/lenet_solver.prototxt
pause
6.3.2 編寫 test.bat:(腳本需要在./caffe-master/Build/x64/Release/下)
caffe test --model=E:/pydoc/blog/lenet_train_test.prototxt --weights=E:/pydoc/blog/lenet/_iter_500.caffemodel
pause
輸出:
可見,模型准確率為:87.8%。
7.生成openmv二進制文件。
7.1 打開./openmv-master/openmv-master/ml/cmsisnn,nn_quantizer.py 和 nn_convert.py ,我們將這兩個腳本拷貝到根目錄./blog下
7.2 編寫批處理文件:
python nn_quantizer.py --cpu --model E:/pydoc/blog/lenet_train_test.prototxt --weights E:/pydoc/blog/lenet/_iter_500.caffemodel --save E:/pydoc/blog/lenet/output.pkl
pause
7.3 生成二進制文件
使用 OpenMV NN 轉換器腳本將模型轉換為二進制格式,可由 OpenMV Cam 運行。該轉換器腳本會輸出每個層類型的代碼,后跟該層的維度和權重。
在 OpenMV Cam 上,固件讀取該二進制文件,並使用鏈表數據結構在內存中構建網絡。
編寫批處理腳本
python nn_convert.py --model E:/pydoc/blog/lenet/output.pkl --mean E:/pydoc/blog/mean.binaryproto --output E:/pydoc/blog/lenet/output.network
pause
輸出:
至此:全部結束!!!
生成network后如何在openmv上運行,請參考openmv視頻教程:https://singtown.com/learn/50543/