プログラミングの芽

面白そうな技術を使って、楽しいことやりたい

機械学習に使う画像とアノテーションファイルをtestとtrainフォルダに振り分ける

2500枚とそれに付随するアノテーションファイルを良い感じに振り分けることを目的とします。

必要なもの

import pandas as pd
import numpy as np
import cv2
from sklearn.model_selection import train_test_split


アノテーションファイルをtrain、test用にランダムに振り分ける

df = pd.read_csv('coin_labels.csv')
namelist_train, namelist_test = train_test_split(df, stratify=df['class'])

train_test_split()の第二引数は今回だとclassにしてます。
こうするとクラスの分布を良い感じに散らばして分けてくれるみたいです。
参考
scikit-learn でトレーニングデータとテストデータを作成する – Python でデータサイエンス

0列目を消してそれぞれをcsvに保存

namelist_train.to_csv("train.csv",index=False)
namelist_test.to_csv("test.csv",index=False)


画像の振り分けをします。
変数やら準備

dir_path = "./coin/"

train_img_name = np.array(namelist_train["filename"],dtype="unicode")
test_img_name = np.array(namelist_test["filename"],dtype="unicode")

train_cnt = train_img_name.shape[0]
test_cnt = test_img_name.shape[0]

train_img_path = []
test_img_path = []


train、testフォルダに振り分ける

for i in range(train_cnt):
    train_img_path.append(dir_path+train_img_name[i])
    train_img = cv2.imread(train_img_path[i])
    cv2.imwrite("./train/{0}".format(train_img_name[i]), train_img)
    
for i in range(test_cnt):
    test_img_path.append(dir_path+test_img_name[i])
    test_img = cv2.imread(test_img_path[i])
    cv2.imwrite("./test/{0}".format(test_img_name[i]), test_img)


以上です。
プログラミングのセンスがない人って、変数をむやみやたらに使いたがる上に変数名もよくわからないみたいですね。私ですね。

機械学習に使う画像を水増しした分のアノテーションファイルを作成する。

以下の記事の作業を行ったという想定の元進めていきます。
atarms.hatenablog.com
atarms.hatenablog.com

正解ラベルの部分をトリミングしているため、画像全体が正解という事になります。
それを90度、180度、270度と回転させただけなので、画像全体が正解という事に変わりはありません。
従って、すべての画像は画像の解像度=ラベルの位置ということになります。

正解ラベルは、上の記事通りにやっていれば
元画像0~600.jpg、90度回転画像601~1201.jpg、180度回転画像...
といった感じで管理していると思うので、アノテーションファイルのラベルを600個1ユニットとしてコピペしていくだけでできます。

必要なもの

import os
import numpy as np
import re
import cv2
import csv


配列に画像のファイル名を入れる

dir_path="./"
img_path=os.listdir(dir_path)

img_path2 = []
for x in img_path:
    m = re.search("[0-9]+", x)
    tuple = (m.group(), x)
    img_path2.append(tuple)
img_path2.sort(key = lambda x: (int(x[0])))

for x in img_path2:
    img_path = np.append(img_path,x[1])
img_path = [x[1] for x in img_path2]

img_path = np.array(img_path,dtype="unicode")


画像の相対パスを配列に入れる

img_path2=[]
for i in range(img_path.shape[0]):
    img_path2.append(dir_path+img_path[i])
img_path2=np.array(img_path2,dtype="unicode")


画像の解像度を配列に入れる(正解ラベルの座標に使う)

img_size=[]#画像サイズ用
for i in range(img_path2.shape[0]):
    img=cv2.imread(img_path2[i])
    img_size.append(img.shape[0:2])
img_size=np.array(img_size,dtype="int64")


元画像のアノテーションファイルを配列に入れる(正解ラベル抽出のため)

origin_csv=[]
with open('labels.csv') as f:
    reader = csv.reader(f)
    header = next(reader) #headerを読み飛ばす
    for line in f: #1行ずつ読み込む(行数分でfor)
        line = line.rstrip("\n").split(",")
        origin_csv.append(line) #csv_data[]に1行ずつ追加
origin_csv = np.array(origin_csv,dtype="unicode")


アノテーションファイル作成

img_csv=[]

img_csv.insert(0,["filename", "width", "height", "class", "xmin", "ymin", "xmax", "ymax"]) #ヘッダ作成

class_cnt = 0
for i in range(img_path2.shape[0]):
    if origin_csv.shape[0] == class_cnt:
        class_cnt = 0
        img_csv.append([img_path[i], img_size[i,0], img_size[i,1], origin_csv[class_cnt,3], 0, 0, img_size[i,0], img_size[i,1]])
        class_cnt = class_cnt + 1
    else:
        img_csv.append([img_path[i], img_size[i,0], img_size[i,1], origin_csv[class_cnt,3], 0, 0, img_size[i,0], img_size[i,1]])
        class_cnt = class_cnt + 1

img_csv=np.array(img_csv)
with open("new_labels.csv","a",newline="") as file:
    writer = csv.writer(file)
    for i in range(len(img_csv)):
        writer.writerow(img_csv[i])


こんなアノテーションファイルができる。
f:id:atarms:20190130211811p:plain

かなりコードがぐちゃぐちゃしてます。ごめんなさい。
備忘録の面が強いのでご容赦。

【Python】画像を90度ずつ回転させて学習データの水増しをする

画像を機械学習にかける際、学習対象物をさまざまな方向、角度から撮影されたものを使うことで汎用性の高いモデルが期待できます。
極論いえば、自分でカメラを使って撮影しまくるのが良いのですが、そんなことはしてられないです。
そのため、学習に使う画像の水増し手法として今回は90度ずつ回転させた画像を生成するコードを残しておきます。

機械学習における画像水増しの注意ですが、時には回転や左右反転などをしたことによって学習の劣化に繋がるケースもあります。
その辺調べれば出ますのでご留意願います。
私は今回、小銭画像の水増しを行うので左右反転は不適切だと判断して行いません。
左右反転されてる小銭なんて存在しませんからね。

今回は大量の画像(ここでは600枚)に対して、1枚を90度・180度・270度と回転させた計4枚に水増しする事を目的とします。
f:id:atarms:20190129220439p:plain:w300

必要なもの

import os
import cv2
import re
import numpy as np


フォルダ内の画像パスを配列に入れる

path = "./"
files = []
img_path = []
for x in os.listdir(path):
    if os.path.isfile(path + x):
        files.append(x) 
        
for y in files:
    if(y[-4:] == '.jpg'): 
        img_path.append(y)  
        
img_path2 = []
for x in img_path:
    m = re.search("[0-9]+", x)
    tuple = (m.group(), x)
    img_path2.append(tuple)
img_path2.sort(key = lambda x: (int(x[0])))

for x in img_path2:
    img_path.append(x[1])
img_path = [x[1] for x in img_path2]

img_path = np.array(img_path)

参考
yさんのまったりブログ: Python: ファイル名の番号でソートする
Python3でlistのソート | New Domains

画像を読み込んで90度、180度、270度回転させてそれぞれの画像を保存する。

cnt = img_path.shape[0]

for i in range(cnt):
    img = cv2.imread("./"+img_path[i])

    edit_img = np.rot90(img, k=-1)
    cv2.imwrite('./90_coin{0}.jpg'.format(i), edit_img)
    edit_img = np.rot90(edit_img, k=-1)
    cv2.imwrite('./180_coin{0}.jpg'.format(i), edit_img)
    edit_img = np.rot90(edit_img, k=-1)
    cv2.imwrite('./270_coin{0}.jpg'.format(i), edit_img)


以上です。

追記

cnt = img_path.shape[0]

#img = []
for i in range(cnt):
    img = cv2.imread("./"+img_path[i])
    
    edit_img = np.rot90(img, k=-1)
    cv2.imwrite('./coin{0}.jpg'.format(cnt+i), edit_img)
    edit_img = np.rot90(edit_img, k=-1)
    cv2.imwrite('./coin{0}.jpg'.format(cnt+cnt+i), edit_img)
    edit_img = np.rot90(edit_img, k=-1)
    cv2.imwrite('./coin{0}.jpg'.format(cnt+cnt+cnt+i), edit_img)

画像回転部分のプログラムに関して、ファイル名について書き換えました。
今回の私の目的だと別のファイル名を振り分けると不都合が生じました。
このプログラムで処理すれば、加工前画像の通し番号に続けたファイル名で保存できます。

アノテーション情報(座標)から画像をトリミングして保存する

今回は大量の画像を一括で処理することを目的とします。
数枚であればプログラム数行でできます。

前提として以下のようなlabels.csvファイルとそれに対応した画像があるとします。
f:id:atarms:20190130205640p:plain
filename: ファイル名
width height: 画像ファイルのサイズ
class: 分類ラベル
xmin ymin xmax ymax: 画像内のどこに分類ラベルに該当する画像があるのかを示すバウンディングボックス座標
今回使うのはfilename、xmin、ymin、xmax、ymax

必要なもの

import numpy as np
import csv
import cv2


アノテーションファイル.csvの情報を扱いやすいように配列に入れる。

csv_data=[]
with open("labels.csv","r", encoding='UTF-8') as f:
    reader = csv.reader(f)
    header = next(reader) #headerを読み飛ばす
    for line in f: #1行ずつ読み込む(行数分でfor)
        line = line.rstrip("\n").split(",")
        csv_data.append(line) #csv_data[]に1行ずつ追加
csv_data=np.array(csv_data,dtype="unicode") #numpy配列に変換
print(csv_data.shape)
print(csv_data[:,:]) #データ、要素の2次元配列

>>(631, 8)
[['01.jpg' '4032' '3024' ... '612' '1288' '1778']
 ['01.jpg' '4032' '3024' ... '819' '2768' '1912']
 ['02.jpg' '4032' '3024' ... '878' '2477' '1951']
 ...
 ['49.jpg' '4032' '3024' ... '2151' '2795' '2612']
 ['49.jpg' '4032' '3024' ... '607' '2043' '1101']
 ['49.jpg' '4032' '3024' ... '107' '1954' '596']]


画像のディレクトリ+先ほどの配列1列目を結合した画像パスを別の配列に入れる
今回は相対パスで作業を進めます。

dir_path="./" #画像のディレクトリ
img_path=[]
cnt=(csv_path.shape[0]) #データ数をcntに代入
for i in range(cnt): #cnt分繰り返す
    img_path.append(dir_path+csv_path[i,0]) #img[]に1枚ずつ読み込み
img_path=np.array(img_path) #画像パスの1次元配列
print(img_path)

>>['./01.jpg' './01.jpg' './02.jpg' './02.jpg' './03.jpg' './03.jpg'
 './04.jpg' './04.jpg' './05.jpg' './05.jpg' './06.jpg' './06.jpg'
...
 './49.jpg' './49.jpg' './49.jpg' './49.jpg' './49.jpg' './49.jpg'
 './49.jpg']


ついでにバウンディングボックスの座標も取り出しておく

bbox=[]
cnt=(csv_data.shape[0]) #データ数をcntに代入
for i in range(cnt): #データ数分繰り返す
    bbox.append(csv_data[i,4:]) #bbox[]にバウンディングボックスの座標を1つずつ格納
bbox=np.array(bbox,dtype="int64") 
print(bbox)

>>[[ 125  612 1288 1778]
 [1668  819 2768 1912]
 [1413  878 2477 1951]
 ...
 [2336 2151 2795 2612]
 [1570  607 2043 1101]
 [1466  107 1954  596]]


img_pathとbboxを使って画像をトリミング後、指定フォルダに保存

cnt = (csv_data.shape[0])
for i in range(cnt): #ラベル分処理する
    img_array = cv2.imread(img_path[i]) #i番目の画像を読み込み
    dst = img_array[(bbox[i,1]):(bbox[i,3]),(bbox[i,0]):(bbox[i,2])] #i番目の座標を使ってトリミング
    cv2.imwrite('./coin/coin{0}.jpg'.format(i), dst) #coin[i].jpgで保存

以上のプログラムよって以下の画像から
f:id:atarms:20190129195810j:plain:w500
以下の2枚の画像に分割できます。
f:id:atarms:20190129195836j:plain:w200 f:id:atarms:20190129195844j:plain:w200

小銭検出

暇なので1円、5円、10円、100円、500円を学習させてみました。
f:id:atarms:20190129134204p:plain
本当はアプリ化して、自動で画面内の小銭を集計してくれるものを作ろうと思ったんですけど、やっぱり精度が厳しいですね。
疑問として、なぜ全て検出されてないのか?
間違いなく他のも検出できるだけの精度は、この画像を見ている限りあるのですが。

気晴らしにやった程度なのでこれ以上は深入りするのをやめます・・・。

リアルタイム顔面自動モザイク

f:id:atarms:20190130143536j:plain:w400
これをリアルタイム処理できるようにする。
静止画でいいならものすごく簡単にできる。

# -*- coding: utf-8 -*-
import cv2
import matplotlib.pyplot as plt

def mosaic(src, ratio=0.1): #ratio=モザイクの強さ
    small = cv2.resize(src, None, fx=ratio, fy=ratio, interpolation=cv2.INTER_NEAREST)
    return cv2.resize(small, src.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)

def mosaic_area(src, x, y, width, height, ratio=0.1):
    dst = src.copy()
    dst[y:y + height, x:x + width] = mosaic(dst[y:y + height, x:x + width], ratio)
    return dst

face_cascade_path = '~/haarcascade_frontalface_default.xml' #cv2に付属しているカスケード分類器のパス
face_cascade = cv2.CascadeClassifier(face_cascade_path)

                   
# VideoCaptureのインスタンスを作成
cap = cv2.VideoCapture(0)

while True:
    # VideoCaptureから1フレーム読み込む
    ret, frame = cap.read()
    
    src_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) #グレースケールに変換
    faces = face_cascade.detectMultiScale(src_gray) #顔検出 座標(x,y,w,h)を返す
    for x, y, w, h in faces:
        #バウンディングボックスを表示する場合
        #cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2) 
        #face = frame[y: y + h, x: x + w]
        #face_gray = src_gray[y: y + h, x: x + w]

  #モザイク処理をする場合 
        dst_face = mosaic_area(frame, x, y, w, h)
    
    cv2.imshow('frame', dst_face)
    # キー入力を1ms待って、k が27(ESC)だったらBreakする
    k = cv2.waitKey(1)
    if k == 27:
        break

# キャプチャをリリースして、ウィンドウをすべて閉じる
cap.release()
cv2.destroyAllWindows()


顔だけじゃなくて目の分類器もあるので、目だけモザイクとかもできると思います。
注意点としては、リアルタイムだと結構モザイクがばがばになります。
配信で使いたいとかなら工夫がいりますね。

pythonによる2リンクロボットアームの順運動学

import math
import matplotlib.pyplot as plt

def fk(l1,l2,d1,d2):
    x1 = l1*math.cos(math.radians(d1))
    y1 = l1*math.sin(math.radians(d1))

    x2 = l1*math.cos(math.radians(d1)) 
            + l2*math.cos(math.radians(d1+d2))
    y2 = l1*math.sin(math.radians(d1)) 
            + l2*math.sin(math.radians(d1+d2))

    x =[0,x1,x2]
    y =[0,y1,y2]

    plt.plot(x,y,marker="0")
    plt.show()
fk(10,10,30,45)

>>f:id:atarms:20190128202306p:plain