openmv caffe專欄 1


本專欄參考的原作者文章聲明如下。

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 的網絡庫文件

訓練網絡的主要步驟如下:

  1. 配置環境,安裝Caffe
  2. 采集數據集
  3. 訓練網絡
  4. 量化模型
  5. 將模型轉換為二進制格式
  6. 在OPenMV上部署模型
  7. 運行網絡
  8. 故障排除

2.caffe環境的搭建(以本文環境為例介紹)

  1. windows 10
  2. python 2.7
  3. pycharm
  4. vs2013
  5. openmv cam h4
  6. 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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM