はじめに
機械学習の中でも特に画像についてデータセットを自分で作ろうとすると正方形に画像を切り取ったものを大量に欲しくなりますよね。
例えばこのような画像から、目だったり、顔だったりを正方形に切り出そうとします。
今回のプログラムではマウスで選択することでこのように範囲が選択され、
自動で保存されるというプログラムを作りました。
大量に作業するように、
マウスだけで連続して一枚の画像から切り出し、
ボタン一つで次の画像に移れ、
ボタン一つでできるセーブ機能をつけました。
なお、以下のサイトのプログラムをもとに作成しました。
1から画像処理を学ぶ-指定範囲選択(python)
コード
Githubにも置いておきます。
import sys
import cv2
import os.path
import glob
import os
# グローーバル変数
drawing = False
complete_region = False
ix,iy,width,height = -1,-1,0,0
# マウスコールバック関数(正方形)
def my_mouse_callback_square(event,x,y,flags,param):
global ix,iy,width,height,box,drawing,complete_region
if event == cv2.EVENT_MOUSEMOVE: # マウスが動いた時
if(drawing == True):
width = x - ix
height = y - iy
#オリジナルから追加した分
length = max(abs(width), abs(height))
if width>=0:
width = length
else:
width = -length
if height>=0:
height = length
else:
height = -length
elif event == cv2.EVENT_LBUTTONDOWN: # マウス左押された時
drawing = True
ix = x
iy = y
width = 0
height = 0
elif event == cv2.EVENT_LBUTTONUP: # マウス左離された時
drawing = False
complete_region = True
if(width < 0):
ix += width
width *= -1
if(height < 0):
iy += height
height *= -1
def main(filename):
num = 0
global ix,iy,width,height,drawing,complete_region
source_window = "draw_rectangle"
img = cv2.imread(filename) # 画像の読み込み
img_original = img.copy() #切り取り用の画像
temp = img.copy() # 画像コピー
cv2.namedWindow(source_window)
cv2.setMouseCallback(source_window, my_mouse_callback_square)
while(1):
cv2.imshow(source_window,temp)
if(drawing): # 左クリック押されてたら
temp = img.copy() # 画像コピー
cv2.rectangle(temp,(ix,iy),(ix + width, iy+ height),(0,255,0),2) # 矩形を描画
if(complete_region): # 矩形の選択が終了したら
complete_region = False
cv2.rectangle(img,(ix,iy),(ix + width, iy+ height),(0,0,255),2)
cv2.rectangle(temp,(ix,iy),(ix + width, iy+ height),(0,0,255),2)
if width==0:
continue
roi = img_original[iy:iy+height, ix:ix+width] # 元画像から選択範囲を切り取り
#画像を保存
output_path = os.path.join(output_dir, os.path.splitext(os.path.basename(filename))[0]+'_hand{0}.jpg'.format(num))
cv2.imwrite(output_path,roi)
num +=1
# キー操作
k = cv2.waitKey(1) & 0xFF
if k == 27: # esc押されたら終了
cv2.destroyAllWindows()
sys.exit()
if k == 13: # enter押されたら次へ
break
elif k ==ord('s'): # 's'押されたら進行状況を保存
print(filename)
with open("save.txt", "w") as f:
f.write(filename)
cv2.destroyAllWindows()
#過去のセーブを読み込む
def open_save():
if not os.path.exists("save.txt"):
return False
with open("save.txt", "r") as f:
last_img = f.read()
return last_img
#セーブ内容から始めるようにする
def list_posision(last_img, files):
if last_img == False:
return 0
for i, file in enumerate(files):
if file == last_img:
return i
output_dir = "./hand_cut_images/"
imgs_dir = "./original_images/*"
files = glob.glob(imgs_dir)
del files[0:list_posision(open_save(), files)]
for filename in files:
main(filename)
使い方
最後のほうの
output_dir = "./hand_cut_images/"
imgs_dir = "./original_images/*"
を自分の環境に合わせてください。
この時、imgs_dirはフォルダ名
+ /*
としてください。
/*
とつけることで、フォルダ内のすべての画像を指定したことになります。
今回の入力画像があるフォルダ内はこんな感じです。
次にプログラムを実行すると、
このようにウィンドウが出てきます。
このウィンドウ内で切り取りたい部分をマウスで長押ししながら動かすと、このように緑の正方形の枠がマウスに合わせて変化します。
ちょうどいいところでマウスを離すと、赤い枠となって指定範囲が保存されます。
その後も同じように、赤い枠を無視して範囲を指定すると保存されます。
最終的にはこんな感じに(最も何を切り取っているかわかりませんが)
出力フォルダにはこのようになっています。
Enterを押すと次の画像に代わります。
進行状況を保存したいときはsを押してください。
あくまでどの画像から再開するかを保存できるだけで、どこを切り取ったかは保存していません。
save.txtという名前で保存されます。
ここで、保存後、その画像を削除してしまうとプログラムが動かなくなるので、どのときはsave.txtを削除して下さい。
画像が最後まで行くか、escを押すと、プログラムが中断されます。
元のプログラムからの変更点
正方形
元のプログラムでは、画像を矩形で範囲選択して切り取るというプログラムでした。
内容的には、マウスを押した際の座標を記録し、マウスを動かした際の座標との変分をwidth
,height
として範囲を指定していました。
今回は正方形にするために、width
とheight
の絶対値の大きいほうをlength
としてwidth
,height
の代わりに使用するようにしました。
#オリジナルから追加した分
length = max(abs(width), abs(height))
if width>=0:
width = length
else:
width = -length
if height>=0:
height = length
else:
height = -length
クリック問題
指定範囲を保存する際にマウスを押した点と離した点が同じだと、範囲ではなく点となってしまい、エラーとなっていしまう問題がありました。
そこで、lengthが0の時は保存しないようにしました。
プログラム内はwidth
で判断していますが、width
にlength
を代入しているので同じものと思ってください。
if(complete_region): # 矩形の選択が終了したら
complete_region = False
cv2.rectangle(img,(ix,iy),(ix + width, iy+ height),(0,0,255),2)
cv2.rectangle(temp,(ix,iy),(ix + width, iy+ height),(0,0,255),2)
if width==0:
continue
連続切り取り
1枚のプログラムから何枚も切り取れるように、sを押したら保存ではなく選択範囲を指定したら自動で保存に切り替えました。
if(complete_region): # 矩形の選択が終了したら
complete_region = False
cv2.rectangle(img,(ix,iy),(ix + width, iy+ height),(0,0,255),2)
cv2.rectangle(temp,(ix,iy),(ix + width, iy+ height),(0,0,255),2)
if width==0:
continue
roi = img_original[iy:iy+height, ix:ix+width] # 元画像から選択範囲を切り取り
#画像を保存
output_path = os.path.join(output_dir, os.path.splitext(os.path.basename(filename))[0]+'_hand{0}.jpg'.format(num))
cv2.imwrite(output_path,roi)
num +=1
切り取り箇所の表示
一枚の画像の中から何枚も切り抜いていると、どこを切り取ってどこを切り取っていないかわからなくなります。
なのですでに切り取ったところを赤の枠線で囲うようにしました。
もちろん切り取り時にこの枠線は入らないようにしています。
そのために、切り取り用、画像更新用、背景用の3つの画像を内部で用意しています。
img = cv2.imread(filename) # 画像の読み込み
img_original = img.copy() #切り取り用の画像
temp = img.copy() # 画像コピー
大量画像の処理
元のプログラムでは、プログラム一回で1枚の画像からしか切り抜きできませんでした。
このプログラムでは、まず画像パスをすべて取得し、その後forで渡していく方法に変更しました。
output_dir = "./hand_cut_images/"
imgs_dir = "./painted_images/*"
files = glob.glob(imgs_dir)
del files[0:list_posision(open_save(), files)]
for filename in files:
main(filename)
セーブ機能
画像を複数扱おうとするときにforで順番に送っているため、途中でプログラムを中断してしまうとまた初めからになってしまいます。
そこでsボタンを押すことで現在の画像パスをtxtファイルに保存し、次回実行時にそこから読み取って途中から再開できるようにしました。
プログラムとしては画像のパスのリストのうち、保存されたパスの1つ前までを削除することで途中から再開できるようにしています。
#過去のセーブを読み込む
def open_save():
if not os.path.exists("save.txt"):
return False
with open("save.txt", "r") as f:
last_img = f.read()
return last_img
#セーブ内容から始めるようにする
def list_posision(last_img, files):
if last_img == False:
return 0
for i, file in enumerate(files):
if file == last_img:
return i
output_dir = "./hand_cut_images/"
imgs_dir = "./original_images/*"
files = glob.glob(imgs_dir)
del files[0:list_posision(open_save(), files)]
for filename in files:
main(filename)
保存ファイル名
元のファイル名 + _hand + 連番
となっています。
変えたい人は自分で変えてください。
#画像を保存
output_path = os.path.join(output_dir, os.path.splitext(os.path.basename(filename))[0]+'_hand{0}.jpg'.format(num))
cv2.imwrite(output_path,roi)
num +=1
まとめ
機械学習の画像のデータセットを自分で作りたいときに、正方形に簡単に切り取れるソフトがぱっとなかったので、既存のプログラムを改造して作りました。
正方形にさえ切り取ってしまえば、あとはリサイズして64*64にしたりできるので、データセットが自作できるようになります。
データセットの作り方や、リサイズの仕方は以下の記事をご覧ください。