LoginSignup
17
16

More than 5 years have passed since last update.

YoloV2独自学習データ+Movidius Neural Compute Stick+Raspberry Pi による複数動体検知環境の構築

Last updated at Posted at 2018-02-04

I wrote it in English in the comment section.

◆はじめに

Intel Movidius Neural Compute Stick + tinyYoloV2 + Raspberry Pi の環境で独自データセットを使用した複数動体検知にトライする。
学習データの生成にあたっては現実的な学習時間に収めるため、リッチなGPUを搭載した端末がRaspberry Piとは別に必須。
参考までに、Xeon + GPU未利用 64バッチ、10,000イテレーションで27日間必要との試算。
ちなみに、Quadro P2000利用 64バッチ、10,000イテレーションで実測3時間30分。
Quadro P2000利用 64バッチ、50,000イテレーションで実測18時間30分。
自宅には5年前のモバイル用ロースペックGPU Geforce 650Mしかなかったので仕方なくそれで実施。
基本的には先駆者の方々の事例を参考にデータ生成から実行までのDeep Learning実運用を想定した一気通貫の手順への焼き直しのみのため、完全オリジナルの部分は無い。
以下、見出しに記載している【...】は実行環境を指す。

動画素材収集完了後から30分以内で学習着手できる手順を目指す。

◆環境

(1)【学習用PC】 GIGABYE U2442F
・MEM:16GB
・CPU:第3世代 Intel Core i7-3517U(1.9GHz)
・GPU:Geforce GT 650M (VRAM:2GB)
・OS:Ubuntu 16.04 LTS (Windows10とのデュアルブート)
・CUDA 8.0.61
・cuDNN v6.0
・Caffe
・OpenCV 3.4.0
・Samba

(2)【実行環境】 Raspberry Pi 3 ModelB
・Raspbian Stretch
・NCSDK v1.12.00
・Intel Movidius Neural Compute Stick
・OpenCV 3.4.0
・Samba
・tinyYoloV2+NCS実行環境、構築手順は下記
(1)https://qiita.com/PINTO/items/b084fe3dc716c42e2867
(2)https://qiita.com/PINTO/items/7f13fcb7c894c27691b2

◆穴が開くほど読み込んで参考にさせていただいたサイト、謝辞

https://github.com/leetenki/YOLO_train_data_generator
https://qiita.com/darkimpact0626/items/7868bfcdca111f8793ea
https://qiita.com/MOKSckp/items/061d2d0dfacf1fa8a83d
https://qiita.com/lnkusu/items/0073cb428bb1950d1e0c
https://qiita.com/icoxfog417/items/53e61496ad980c41a08e
https://qiita.com/livlea/items/a94df4667c0eb37d859f
https://github.com/pjreddie/darknet
http://hakkentanoshii.seesaa.net/article/450649219.html
https://timebutt.github.io/static/how-to-train-yolov2-to-detect-custom-objects/
https://github.com/AlexeyAB/darknet
https://qiita.com/bohemian916/items/9630661cd5292240f8c7

◆ おおまかな作業の流れ

1.適当に動画撮影
2.動画から機械的に静止画を大量生成
3.大量の静止画から物体部分を機械的に抽出して背景が透過した物体画像生成
4.別途用意した背景静止画と3.で生成した物体静止画をランダムに回転・縮小・拡大・配置・ノイズ追加しながら合成して大量に水増し
5.学習
6.Intel Movidius Neural Compute Stick 用学習データへ変換
7.Raspberry Pi上で6.を使用してtinyYoloによる複数動体検知

◆【学習用PC】 動画→静止画変換

1.下記コマンドを実行
※下記コマンドサンプルはmp4動画1秒あたり10枚のPNGファイルを生成
※自分は、「スマートフォン Arrows M03」 で8秒間撮影した動画を使用

Youtubeサンプル動画 Nintendo Switch / 東芝のリモコン

$ ffmpeg -i xxxx.mp4 -vcodec png -r 10 image_%04d.png

◆【学習用PC】 指定フォルダ内の複数静止画ファイル、複数物体周囲をまとめて機械的に透過加工

  • 背景が白色に近い色・物体が白色/灰色以外の配色で構成されている場合のみ動作
  • 1画像内に複数物体が写っている場合は物体数分の画像ファイルへ分割して加工
  • 入力画像が長方形であっても最終生成画像は物体を含む96×96の正方形
  • エッジ抽出の都合上、重なり合っている物体は1つと認識される
  • 検出された物体の面積が1000pxに満たない場合は当該物体を抽出対象から除外
  • 最終生成された画像内に物体が存在しないと判断される場合はファイルを生成しない
  • メイン部は、ほぼオリジナルロジック

(1) 編集元画像 1920x1080
(1).png
(2) 元画像の背景白色化
(2).png
(3) 物体検出
(3).png
(4) 背景透過処理後PNGファイル2枚 96x96
※上段のイメージ上下左右両端には黒い枠が見えるが下段のように実際の生成画像は白黒の部分が透過している
(4).png
_image_0009_0000.png_image_0009_0001.png

ObjectExtraction.py
import numpy as np
import cv2
import os
import math

# 画像入出力先パス、拡張子
# EXTには処理対象とする画像ファイルの拡張子を指定
INP = "/home/xxxx/images/"
EXT = ".png"

# 背景全面白色塗りつぶし
def mask_back_white(img):
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    white_min = np.array([0, 0, 50], np.uint8)
    white_max = np.array([180, 39, 255], np.uint8)
    white_region = cv2.inRange(hsv, white_min, white_max)
    white = np.full(img.shape, 255, dtype=img.dtype)
    background = cv2.bitwise_and(white, white, mask=white_region)
    inv_mask = cv2.bitwise_not(white_region)
    extracted = cv2.bitwise_and(img, img, mask=inv_mask)
    masked = cv2.add(extracted, background)
    return masked

# 物体部分全面白色塗りつぶし
def mask_front_white(img):
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    white_min = np.array([0, 0, 1], np.uint8)
    white_max = np.array([255, 255, 255], np.uint8)
    white_region = cv2.inRange(hsv, white_min, white_max)
    white = np.full(img.shape, 255, dtype=img.dtype)
    background = cv2.bitwise_and(white, white, mask=white_region)
    inv_mask = cv2.bitwise_not(white_region)
    extracted = cv2.bitwise_and(img, img, mask=inv_mask)
    masked = cv2.add(extracted, background)
    return masked

# ノイズ除去
def morphology(img):
    kernel = np.ones((12,12),np.uint8)
    opening = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)  
    return opening

# 物体検出
def detect_contour(img, min_size):
    contoured = img.copy()
    forcrop = img.copy()
    # グレースケール画像生成
    dst = cv2.cvtColor(cv2.bitwise_not(img), cv2.COLOR_BGR2GRAY)
    # 輪郭検出
    im2, contours, hierarchy = cv2.findContours(dst, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    crops = []
    # 輪郭抽出
    for c in contours:
        # min_sizeに満たない面積の物体は無視
        if cv2.contourArea(c) < min_size:
            continue
        # バウンディングボックスサイズの検出とパディング追加調整
        x, y, w, h = cv2.boundingRect(c)
        x, y, w, h = padding_position(x, y, w, h, 5)
        # 物体部の画像切り取り
        if y < 0:
            h = h + y
            y = 0
        if x < 0:
            w = w + x
            x = 0
        cropped = forcrop[y:(y + h), x:(x + w)]
        cropped = resize_image(cropped, (96, 96))
        crops.append(cropped)
        # 輪郭・バウンディングボックス描写
        cv2.drawContours(contoured, c, -1, (0, 0, 255), 3)
        cv2.rectangle(contoured, (x, y), (x + w, y + h), (0, 255, 0), 7)
    return contoured, crops

# パディング調整
def padding_position(x, y, w, h, p):
    return x - p, y - p, w + p * 2, h + p * 2

# 画像リサイズ 2018.02.12 回転時に見切れが発生するバグ修正
def resize_image(img, size):
    img_size = img.shape[:2]
    newheight = img_size[0]
    newwidth  = img_size[1]
    # サイズ縮小
    if newheight > size[1] or newwidth > size[0]:
        if newheight > newwidth:
            newheight = int(round(math.sqrt(size[0] ** 2 + size[1] ** 2) / 2))
            raito =  newheight / img_size[0]
            newwidth = int(img_size[1] * raito)
        else:
            newwidth = int(round(math.sqrt(size[0] ** 2 + size[1] ** 2) / 2))
            raito = newwidth / img_size[1] 
            newheight = int(img_size[0] * raito)
        img = cv2.resize(img, (newwidth, newheight))
        img_size = img.shape[:2]
    # 画像のセンタリング
    row = (size[1] - newheight) // 2
    col = (size[0] - newwidth) // 2
    resized = np.zeros(list(size) + [img.shape[2]], dtype=np.uint8)
    resized[row:(row + newheight), col:(col + newwidth)] = img
    return resized

############ メイン処理部 ##############

# ファイル名の取得
fileList = os.listdir(INP)

for file in fileList:
    # ファイル拡張子の抽出
    base, ext = os.path.splitext(file)
    # 処理対象の拡張子のみ処理実施
    if ext == EXT:
        # 元画像読み込み
        filename = INP + file
        img = cv2.imread(filename)
        # 元画像背景白色化
        img = mask_back_white(img)
        # 元画像ノイズ除去
        #img = morphology(img)
        # 物体エリア検出
        contoured, crops = detect_contour(img, 1000)
        # 背景透過処理
        for i, c in enumerate(crops):
            # 背景黒色化(元画像のマスク用画像)
            lower = np.array([255,255,255])
            upper = np.array([255,255,255])
            img_mask = cv2.inRange(c, lower, upper)
            img_mask = cv2.bitwise_not(img_mask,img_mask)
            img_mask = cv2.bitwise_and(c, c, mask=img_mask)
            # 物体白色化(元画像のマスク用画像)
            img_mask = mask_front_white(img_mask)
            # マスク用画像のグレースケール変換(2値化)
            img_mask = cv2.cvtColor(img_mask, cv2.COLOR_BGR2GRAY)
            # 元画像とマスク用画像の合成
            img = cv2.split(c)
            img = cv2.merge(img + [img_mask])
            mono = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[:,:,0].mean()
            # 無地の画像は出力しない判定
            if mono != 0:
                # 加工完了画像ファイル出力
                OUT = INP + "_" + base + "-" + str(("%04d" % i)) + ext
                cv2.imwrite(OUT, img)

◆【学習用PC】 学習用画像データ他の作成

  • 静止画からランダムに回転・縮小・拡大・配置・ノイズ追加を繰り返して水増し画像生成
  • 任意の物体画像と任意の背景画像を自由に合成
  • 前処理で生成した連番付き画像ファイル名の連番部を無視し、複数画像をひとつのラベルへ集約
  • 参考にさせていただいたロジックは、自分にとっては神プログラム、今回手順で何が一番大変かというと、「複数静止画像の作成」、画像ばかりかアノテーションまで自動で実行してくれる。leetenki様、bohemian916様、有難うございます。

1.下記コマンドを実行

$ cd ~
$ git clone https://github.com/leetenki/YOLO_train_data_generator
$ cd YOLO_train_data_generator
$ sudo chmod +x *.sh
$ ./setup.sh

[2018/02/06] leetenki様 複数画像同一ラベル対応のため改良させていただきました。
[2018/02/07] leetenki様 コントラスト強弱調整、平滑化、ガウシアンノイズ追加、salt&pepperノイズ追加、反転 をランダムに実行するように改良させていただきました。

2.改良ロジックへの修正、下記コマンドを実行

$ nano generate_sample.py
generate_sample.py
import cv2
import os
import glob
import numpy as np
from PIL import Image

def overlay(src_image, overlay_image, pos_x, pos_y):
    ol_height, ol_width = overlay_image.shape[:2]

    src_image_RGBA = cv2.cvtColor(src_image, cv2.COLOR_BGR2RGB)
    overlay_image_RGBA = cv2.cvtColor(overlay_image, cv2.COLOR_BGRA2RGBA)

    src_image_PIL=Image.fromarray(src_image_RGBA)
    overlay_image_PIL=Image.fromarray(overlay_image_RGBA)

    src_image_PIL = src_image_PIL.convert('RGBA')
    overlay_image_PIL = overlay_image_PIL.convert('RGBA')

    tmp = Image.new('RGBA', src_image_PIL.size, (255, 255,255, 0))
    tmp.paste(overlay_image_PIL, (pos_x, pos_y), overlay_image_PIL)
    result = Image.alpha_composite(src_image_PIL, tmp)

    return  cv2.cvtColor(np.asarray(result), cv2.COLOR_RGBA2BGRA)

def delete_pad(image): 
    orig_h, orig_w = image.shape[:2]
    mask = np.argwhere(image[:, :, 3] > 128)
    (min_y, min_x) = (max(min(mask[:, 0])-1, 0), max(min(mask[:, 1])-1, 0))
    (max_y, max_x) = (min(max(mask[:, 0])+1, orig_h), min(max(mask[:, 1])+1, orig_w))
    return image[min_y:max_y, min_x:max_x]

def rotate_image(image, angle):
    orig_h, orig_w = image.shape[:2]
    matrix = cv2.getRotationMatrix2D((orig_h/2, orig_w/2), angle, 1)
    return cv2.warpAffine(image, matrix, (orig_h, orig_w))

def scale_image(image, scale):
    orig_h, orig_w = image.shape[:2]
    return cv2.resize(image, (int(orig_w*scale), int(orig_h*scale)))

def random_sampling(image, h, w): 
    orig_h, orig_w = image.shape[:2]
    y = np.random.randint(orig_h-h+1)
    x = np.random.randint(orig_w-w+1)
    return image[y:y+h, x:x+w]

def random_rotate_scale_image(image):
    image = rotate_image(image, np.random.randint(360))
    image = scale_image(image, 1 + np.random.rand() * 2)
    return delete_pad(image)

def random_overlay_image(src_image, overlay_image):
    src_h, src_w = src_image.shape[:2]
    overlay_h, overlay_w = overlay_image.shape[:2]
    y = np.random.randint(src_h-overlay_h+1)
    x = np.random.randint(src_w-overlay_w+1)
    bbox = ((x, y), (x+overlay_w, y+overlay_h))
    return overlay(src_image, overlay_image, x, y), bbox

def yolo_format_bbox(image, bbox):
    orig_h, orig_w = image.shape[:2]
    center_x = (bbox[1][0] + bbox[0][0]) / 2 / orig_w
    center_y = (bbox[1][1] + bbox[0][1]) / 2 / orig_h
    w = (bbox[1][0] - bbox[0][0]) / orig_w
    h = (bbox[1][1] - bbox[0][1]) / orig_h
    return(center_x, center_y, w, h)

# List内重複排除関数
def remove_duplicates(x):
    y=[]
    for i in x:
        if i not in y:
            y.append(i)
    return y

# ヒストグラム均一化関数
def equalizeHistRGB(src):

    RGB = cv2.split(src)
    Blue   = RGB[0]
    Green = RGB[1]
    Red    = RGB[2]
    for i in range(3):
        cv2.equalizeHist(RGB[i])
    img_hist = cv2.merge([RGB[0],RGB[1], RGB[2]])
    return img_hist

# ガウシアンノイズ関数
def addGaussianNoise(src):
    row,col,ch= src.shape
    mean = 0
    var = 0.1
    sigma = 15
    gauss = np.random.normal(mean,sigma,(row,col,ch))
    gauss = gauss.reshape(row,col,ch)
    noisy = src + gauss
    return noisy

# salt&pepperノイズ関数
def addSaltPepperNoise(src):
    row,col,ch = src.shape
    s_vs_p = 0.5
    amount = 0.004
    out = src.copy()

    # Salt mode
    try:
        num_salt = np.ceil(amount * src.size * s_vs_p)
        coords = [np.random.randint(0, i-1 , int(num_salt)) for i in src.shape]
        out[coords[:-1]] = (255,255,255)
    except:
        pass

    # Pepper mode
    try:
        num_pepper = np.ceil(amount* src.size * (1. - s_vs_p))
        coords = [np.random.randint(0, i-1 , int(num_pepper)) for i in src.shape]
        out[coords[:-1]] = (0,0,0)
    except:
        pass
    return out


base_path = os.getcwd()
fruit_files = glob.glob("orig_images/*")
fruits = []
labels = []
labelsdist = []
for fruit_file in fruit_files:
    labels.append(fruit_file.split("/")[-1].split(".")[0].split("_")[0])
    fruits.append(cv2.imread(fruit_file, cv2.IMREAD_UNCHANGED))
background_image = cv2.imread("background.jpg")
labelsdist = remove_duplicates(labels)

# write label file
with open("label.txt", "w") as f:
    for label in labelsdist:
        f.write("%s\n" % (label))

background_height, background_width = (416, 416)
train_images = 10000
test_images = 2000

# ルックアップテーブルの生成
min_table = 50
max_table = 205
diff_table = max_table - min_table
gamma1 = 0.75
gamma2 = 1.5
LUT_HC = np.arange(256, dtype = 'uint8')
LUT_LC = np.arange(256, dtype = 'uint8')
LUT_G1 = np.arange(256, dtype = 'uint8')
LUT_G2 = np.arange(256, dtype = 'uint8')
LUTs = []
# 平滑化用配列
average_square = (10,10)
# ハイコントラストLUT作成
for i in range(0, min_table):
    LUT_HC[i] = 0
for i in range(min_table, max_table):
    LUT_HC[i] = 255 * (i - min_table) / diff_table                        
for i in range(max_table, 255):
    LUT_HC[i] = 255
# その他LUT作成
for i in range(256):
    LUT_LC[i] = min_table + i * (diff_table) / 255
    LUT_G1[i] = 255 * pow(float(i) / 255, 1.0 / gamma1) 
    LUT_G2[i] = 255 * pow(float(i) / 255, 1.0 / gamma2)
LUTs.append(LUT_HC)
LUTs.append(LUT_LC)
LUTs.append(LUT_G1)
LUTs.append(LUT_G2)

for i in range(train_images):
    sampled_background = random_sampling(background_image, background_height, background_width)

    class_id = np.random.randint(len(labels))
    fruit = fruits[class_id]
    fruit = random_rotate_scale_image(fruit)

    result, bbox = random_overlay_image(sampled_background, fruit)
    yolo_bbox = yolo_format_bbox(result, bbox)

    # コントラスト変換実行
    if np.random.randint(2) == 1:
        level = np.random.randint(4)
        result = cv2.LUT(result, LUTs[level])

    # 平滑化実行
    if np.random.randint(2) == 1:
        result = cv2.blur(result, average_square)

    # ヒストグラム均一化実行
    if np.random.randint(2) == 1:
        result = equalizeHistRGB(result)

    # ガウシアンノイズ付加実行
    if np.random.randint(2) == 1:
        result = addGaussianNoise(result)

    # Salt & Pepperノイズ付加実行
    if np.random.randint(2) == 1:
        result = addSaltPepperNoise(result)

    # 反転実行
    if np.random.randint(2) == 1:
        result = cv2.flip(result, 1)

    image_path = "%s/images/train_%s_%s.jpg" % (base_path, i, labels[class_id])
    cv2.imwrite(image_path, result)

    with open("train.txt", "a") as f:
        f.write("%s\n" % (image_path))

    label_path = "%s/labels/train_%s_%s.txt" % (base_path, i, labels[class_id]) 
    with open(label_path, "w") as f:
        f.write("%s %s %s %s %s" % (labelsdist.index(labels[class_id]), yolo_bbox[0], yolo_bbox[1], yolo_bbox[2], yolo_bbox[3]))

    print("train image", i, labels[class_id], yolo_bbox)

for i in range(test_images):
    sampled_background = random_sampling(background_image, background_height, background_width)

    class_id = np.random.randint(len(labels))
    fruit = fruits[class_id]
    fruit = random_rotate_scale_image(fruit)

    result, bbox = random_overlay_image(sampled_background, fruit)
    yolo_bbox = yolo_format_bbox(result, bbox)

    # コントラスト変換実行
    if np.random.randint(2) == 1:
        level = np.random.randint(4)
        result = cv2.LUT(result, LUTs[level])

    # 平滑化実行
    if np.random.randint(2) == 1:
        result = cv2.blur(result, average_square)

    # ヒストグラム均一化実行
    if np.random.randint(2) == 1:
        result = equalizeHistRGB(result)

    # ガウシアンノイズ付加実行
    if np.random.randint(2) == 1:
        result = addGaussianNoise(result)

    # Salt & Pepperノイズ付加実行
    if np.random.randint(2) == 1:
        result = addSaltPepperNoise(result)

    # 反転実行
    if np.random.randint(2) == 1:
        result = cv2.flip(result, 1)

    image_path = "%s/images/test_%s_%s.jpg" % (base_path, i, labels[class_id])
    cv2.imwrite(image_path, result)

    with open("test.txt", "a") as f:
        f.write("%s\n" % (image_path))

    label_path = "%s/labels/test_%s_%s.txt" % (base_path, i, labels[class_id]) 
    with open(label_path, "w") as f:
        f.write("%s %s %s %s %s" % (labelsdist.index(labels[class_id]), yolo_bbox[0], yolo_bbox[1], yolo_bbox[2], yolo_bbox[3]))

    print("test image", i, labels[class_id], yolo_bbox)

3.学習させたい元静止画像のファイル名をpyrenamer等を利用して「(ラベル名)_xxxx.png」に一括変更し、「~/YOLO_train_data_generator/orig_images」へコピー
※ xxxx の箇所は同一ラベル名で重複しないように連番なり、文字列なり、自由に設定。4桁でなくても良い。

(例).
 labelA_0001.png → 「labelA」に集約
 labelA_0002.png → 「labelA」に集約
 labelA_0003.png → 「labelA」に集約
 labelB_0001.png → 「labelB」に集約
 labelB_0002.png → 「labelB」に集約
 labelC_0001.png → 「labelC」に集約
   :
(例).ラベルがswitchとremoconの場合
 switch_0001.png → 「switch」に集約
 switch_0002.png → 「switch」に集約
 switch_0003.png → 「switch」に集約
 switch_0004.png → 「switch」に集約
 remocon_0001.png → 「remocon」に集約
 remocon_0002.png → 「remocon」に集約
   :

4.下記コマンドを実行
※ train.txt、test.txt、label.txt が生成される
※ images配下に水増し済みの静止画像が生成される
※ デフォルトの水増し枚数は10,000枚、変更する場合は generate_sample.py の 「train_images = 10000」 を修正する

$ python3 generate_sample.py

<生成画像サンプル> (合成、コントラスト、ノイズ、回転、反転、拡大・縮小)
Screenshot from 2018-02-11 11-11-25.png

◆【学習用PC】 YOLOv2学習用プログラム「darknet」の導入

1.下記コマンドを実行

$ cd ~
$ git clone https://github.com/pjreddie/darknet
$ cd ~/darknet
$ nano MakeFile

2.下記のとおりMakeFileの中身を修正する(cudaのパスは各自環境に応じて変更)

★一箇所目

#GPU=0
#CUDNN=0
#OPENCV=0
GPU=1
CUDNN=1
OPENCV=1

★二箇所目

ifeq ($(GPU), 1) 
#COMMON+= -DGPU -I/usr/local/cuda/include/
COMMON+= -DGPU -I/usr/local/cuda-8.0/include/
CFLAGS+= -DGPU
#LDFLAGS+= -L/usr/local/cuda/lib64 -lcuda -lcudart -lcublas -lcurand
LDFLAGS+= -L/usr/local/cuda-8.0/lib64 -lcuda -lcudart -lcublas -lcurand
endif

3.下記コマンドを実行

$ make

◆【学習用PC】 YOLOv2訓練用インプットファイルの作成

1.学習の収束を早めるための学習済み重みデータをダウンロード

$ cd ~/darknet
$ wget https://pjreddie.com/media/files/darknet19_448.conv.23

2.下記コマンドを実行、生成された my-dataset.names はラベル名の一覧

$ cp ~/YOLO_train_data_generator/label.txt ~/darknet/data/my-dataset.names
(例)my-dataset.names
switch
remocon

3.下記コマンドを実行

$ cd ~/darknet
$ cp ~/darknet/cfg/voc.data ~/darknet/cfg/my-dataset.data
$ nano cfg/my-dataset.data

4.下記のように編集して保存

my-dataset.data
classes= 2
train  = /home/xxxx/YOLO_train_data_generator/train.txt
valid  = /home/xxxx/YOLO_train_data_generator/test.txt
names = /home/xxxx/darknet/data/my-dataset.names
backup = ./backup

5.下記コマンドを実行

$ cd ~/darknet
$ cp ~/darknet/cfg/tiny-yolo-voc.cfg ~/darknet/cfg/my-dataset.cfg
$ nano cfg/my-dataset.cfg

6.下記のとおり編集して保存

※114行目 filters=num∗(classes+coords+1)

my-dataset.cfg
[convolutional]
size=1
stride=1
pad=1
filters=35
activation=linear

※120行目 classes=20 → classes=2

my-dataset.cfg
[region]
anchors = 1.08,1.19,  3.42,4.41,  6.63,11.38,  9.42,5.11,  16.62,10.52
bias_match=1
classes=2
coords=4
num=5
softmax=1
jitter=.2
rescore=1

◆【学習用PC】 YOLOv2を独自データセットで訓練する

1.訓練用コマンド実行(処理途中で中断する場合は Ctrl+C)

$ cd ~/darknet
$ mkdir backup
$ ./darknet detector train cfg/my-dataset.data cfg/my-dataset.cfg ./darknet19_448.conv.23 1>> backup/log.txt
学習中のGPU状態.ファンが全力稼働ですぐ壊れそう...
$ nvidia-smi
Sun Feb 11 15:11:22 2018       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.111                Driver Version: 384.111                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GT 650M     Off  | 00000000:01:00.0 N/A |                  N/A |
| N/A   69C    P0    N/A /  N/A |   1210MiB /  1999MiB |     N/A      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0                    Not Supported                                       |
+-----------------------------------------------------------------------------+

学習の途中経過は「$ tail -f backup/log.txt」で確認

log.txt
Region Avg IOU: 0.547641, Class: 0.495596, Obj: 0.007643, No Obj: 0.007328, Avg Recall: 0.750000,  count: 8
Region Avg IOU: 0.614511, Class: 0.501755, Obj: 0.007514, No Obj: 0.007322, Avg Recall: 0.714286,  count: 7
Region Avg IOU: 0.644735, Class: 0.506262, Obj: 0.007720, No Obj: 0.007322, Avg Recall: 0.800000,  count: 5
Region Avg IOU: 0.622272, Class: 0.500563, Obj: 0.007540, No Obj: 0.007323, Avg Recall: 0.875000,  count: 8
87: 9.613853, 10.698938 avg, 0.001000 rate, 7.826897 seconds, 5568 images
Loaded: 0.000043 seconds
Region Avg IOU: 0.474748, Class: 0.500394, Obj: 0.007809, No Obj: 0.007312, Avg Recall: 0.375000,  count: 8

★中断したところから訓練再開するコマンド(xxxxの部分はbackupフォルダにある実在ファイル名を確認して読み替える、my-dataset.backup が直近の中断断面)

$ cd ~/darknet
$ ./darknet detector train cfg/my-dataset.data cfg/my-dataset.cfg backup/my-dataset_xxxx.weights 1>> backup/log.txt

★推論テスト用コマンド

$ cd ~/darknet
$ ./darknet detect cfg/my-dataset.cfg backup/my-dataset_xxxx.weights data/xxxx.jpg

2.出来上がったweightsファイルとcfgファイルを所定のパスへコピー
(1)~/darknet/backup/my-dataset_final.weights 又は ~/darknet/backup/my-dataset_xxxx.weights を my-dataset.weights にリネームして RaspberryPiの ~/YoloV2NCS/models/yolomodels/ へコピー

(2)my-dataset.cfg を RaspberryPiの ~/YoloV2NCS/models/yolomodels/ へコピー

◆【RaspberryPi】 YoloモデルをCaffeモデルに変換

1.下記コマンドを実行

$ cd ~/YoloV2NCS/models
$ nano convertyo.sh

2.下記該当箇所を修正

#filename=tiny-yolo-voc
filename=my-dataset
python ../python/create_yolo_prototxt.py $yolocfg $yolocfgcaffe
#python ../python/create_yolo_caffemodel.py -m $yolocfgcaffe -w $yoloweight -o $yoloweightcaffe
python3 ../python/create_yolo_caffemodel.py -m $yolocfgcaffe -w $yoloweight -o $yoloweightcaffe

3.下記コマンドを実行

$ nano ~/YoloV2NCS/python/create_yolo_caffemodel.py

4.python2系のロジックからpython3系のロジックへ修正する (例)「print ""」 を 「print("")」

5.下記コマンドを実行

$ ./convertyo.sh

◆【Raspberry Pi】 CaffeモデルをNCS用モデルへ変換

1.下記変換コマンド実行

$ cd ~/YoloV2NCS/models
$ mvNCCompile ./caffemodels/my-dataset.prototxt -w ./caffemodels/my-dataset.caffemodel -s 12

◆【Raspberry Pi】 NCS専用モデルファイル(graph)を所定のパスへコピー

1.下記コマンド実行

cp ~/YoloV2NCS/models/graph ~/YoloV2NCS/detectionExample/graph

◆【Raspberry Pi】 複数動体検知プログラムの微調整と味見

※今回作成した学習モデルのクラス数に応じて下記のとおりRegion.cppを編集し、バイナリをリビルドする
1.下記コマンドを実行

$ nano ~/YoloV2NCS/src/Region.cpp
修正例.(ラベルがswitchとremoconの2クラス分の場合)
//const std::string objectnames[] = {"aeroplane","bicycle","bird","boat","bottle","bus","car","cat","chair","cow","diningtable","dog","horse","motorbike","person","pottedplant","sheep","sofa","train","tvmonitor"};
const std::string objectnames[] = {"switch","remocon"};

2.YoloV2実行バイナリのリビルド、下記コマンドを実行

$ cd ~/YoloV2NCS
$ make

3.MultiStick.pyの修正、下記コマンドを実行

$ nano ~/YoloV2NCS/detectionExample/MultiStick.py
修正例.(ラベルがswitchとremoconの2クラス分の場合)
#    classes = 20
    classes = 2

4.複数動体検知プログラムの味見  https://qiita.com/PINTO/items/7f13fcb7c894c27691b2

$ cd ~/YoloV2NCS/detectionExample
$ python3 MultiStick.py

OK、ちゃんと認識する。

17
16
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
16