2022年度第12回卒研セミナー(2022/07/07)

関連サイトと資料

Git, cython, pycocotoolsライブラリのインストール

  1. Build Tools for Visual Studio 2022 のインストールを参考に、 Build Tools for Visual Studio 2022をインストールしてください。
  2. Git 2.28 のインストール(Windows 上)を参考に、 Gitをインストールしてください。
  3. Git コマンドで使用するプロキシを設定する (http.proxy) | まくまくGitノートを参考に、 Gitでproxyを使えるようにしてください。
  4. 下の図の赤丸をクリックして、コマンドプロンプトを開いてください。


  5. 「conda activate torch_gpu」と入力して、エンターキーを押してください。 プロンプトの左側のカッコの中の表示が「base」から「torch_gpu」に変化し、仮想環境「torch_gpu」に入ったことを示しています。


  6. 仮想環境「torch_gpu」で、 「pip install cython --proxy=https://ccproxyz.kanagawa-it.ac.jp:10080」と入力して、 エンターキーを押してください。
  7. 仮想環境「torch_gpu」で、 「pip install install git+https://github.com/philferriere/cocoapi.git#subdirectory=PythonAPI」と入力して、 エンターキーを押してください。

マスク画像の生成

import os
import numpy as np
from PIL import Image
from pycocotools.coco import COCO
from tqdm import tqdm
    
# COCO2014 Datasetから読み込む
catnames = 'person'  # 読み込むカテゴリー
dataFolder='e:/Dataset/coco/train2014'
annFile='e:/Dataset/coco/annotations_trainval2014/instances_train2014.json'
  
# 画像とマスク画像を保存するディレクトリを作る
if not os.path.isdir('imgs'):
    os.mkdir('imgs')
if not os.path.isdir('mask'):
    os.mkdir('mask')
  
# COCOデータセットの読み込み
coco = COCO(annFile)
cats = coco.loadCats(coco.getCatIds())
  
# カテゴリーから対象となる画像を取得する
catIds = coco.getCatIds(catNms=catnames)
imgIds = coco.getImgIds(catIds=catIds)
    

for id in tqdm(imgIds):
  # 画像を読み込む
  img = coco.loadImgs([id])[0]
  
  # 画像に対するアノテーションを取得する
  annIds = coco.getAnnIds(imgIds=[id], catIds=catIds, iscrowd=None)
  anns = coco.loadAnns(annIds)
  
  # アノテーションをマスク画像化する
  mask = None
  for ann in anns:
    if mask is None:
      mask = coco.annToMask(ann)
    else:
      mask[coco.annToMask(ann) != 0] = 1
  
  # 256x256の白黒画像にする
  mask = mask.astype(np.uint8) * 255
  mask = Image.fromarray(mask).resize((256,256))
  mask = mask.convert("L")
  
  # 画像を保存する
  imgFile = f'{dataFolder}/{img["file_name"]}'
  image = Image.open(imgFile)
  image = image.convert("RGB")
  image = image.resize((256,256))
  image.save(f'imgs/{id}.png')
  mask.save(f'mask/{id}.png')
    

sample image(imgs/262145.png)

mask image(mask/262145.png)

転移学習

使用したGPU学習に要した時間
GeForce RTX208010時間41分58秒(641分58秒)

import numpy as np
import os
from PIL import Image
from tqdm import tqdm
import glob
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import transforms
from torchvision.models.segmentation import deeplabv3
from torch.utils.data import DataLoader
from torch.utils.data import Dataset
  
# proxy
os.environ["http_proxy"] = "http://ccproxyz.kanagawa-it.ac.jp:10080"
os.environ["https_proxy"] = "http://ccproxyz.kanagawa-it.ac.jp:10080"
    

# GPUを使うかどうか
USE_DEVICE = 'cuda:0' if torch.cuda.is_available() else 'cpu'
device = torch.device(USE_DEVICE)
  
# PyTorchの内部を決定論的に設定する
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
  
# 乱数を初期化する
np.random.seed(0)
torch.manual_seed(0)
    

# PyTorchの流儀でデータセットをクラスで定義する
class MyDataset(Dataset):
    def __init__(self):
        # ファイルの一覧
        self.filelist = os.listdir('imgs/')
        # 画像用のtransform
        self.trans1 = transforms.Compose([
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                 std=[0.229, 0.224, 0.225])
        ])
        # マスク画像用のtransform
        self.trans2 = transforms.ToTensor()
  
    def __getitem__(self, idx):
        # 画像とマスク画像を読み込む
        img = Image.open('imgs/' + self.filelist[idx])
        msk = Image.open('mask/' + self.filelist[idx])
        # 画像をTensorにする
        img = self.trans1(img)
        msk = self.trans2(msk) # ToTensorは0〜1の範囲にする
        # 画像をマスク画像を返す
        return img, msk
  
    def __len__(self):
        return len(self.filelist)
    

# 学習で使用するニューラルネットワーク
model = torch.hub.load('pytorch/vision:v0.6.0', 'deeplabv3_resnet101', pretrained=True)
  
# 出力層のチャンネル数を1にする
model.classifier = deeplabv3.DeepLabHead(2048, 1)
  
# GPUを使う場合はGPUメモリに乗せる
model.to(device)
  
# 出力層のみ学習する
params = model.classifier.parameters()
optimizer = torch.optim.SGD(params, lr=0.001,  momentum=0.9)
  
# 損失関数を用意する
loss = nn.BCELoss() # バイナリクロスエントロピー
    

# 学習時のバッチサイズ
BATCH_SIZE = 4
  
# 学習エポック数
NUM_EPOCHS = 8
  
# データセットの読み込みクラス
dataset = MyDataset()
  
# データを読み込む
data_loader = DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True)
    

# 学習ループ
for epoch in range(NUM_EPOCHS):
    total_loss = [] # 各バッチ実行時の損失値
    model.train() # モデルを学習用に設定する
    for X, y in tqdm(data_loader): # 画像を読み込んでtensorにする
        X = X.to(device) # GPUを使うときはGPUメモリ上に乗せる
        y = y.to(device) # GPUを使うときはGPUメモリ上に乗せる
  
        # ニューラルネットワークを実行して損失値を求める
        out = model(X)
        out = torch.sigmoid(out['out'])
        losses = loss(out, y)
  
        # 新しいバッチ分の学習を行う
        optimizer.zero_grad() # 一つ前の勾配をクリア
        losses.backward() # 損失値を逆伝播させる
        optimizer.step() # 新しい勾配からパラメーターを更新する
  
        # 損失値を保存しておく
        total_loss.append(losses.detach().cpu().numpy())
  
    # エポック終了時のスコアを求める
    total_loss = np.mean(total_loss) # 各バッチの損失の平均
    # エポック終了時のスコアを表示する
    print(f'epoch #{epoch}: train_loss:{total_loss}')
    

# 最終的なモデルを保存する
torch.save(model.state_dict(), 'chapt05-model1.pth')
    

GeForce RTX2080での動作結果

人物の背景の入れ替え

import numpy as np
import os
from PIL import Image
import cv2
import torch
from torch import nn
from torchvision import transforms
from torchvision.models.segmentation import deeplabv3
  
# proxy
os.environ["http_proxy"] = "http://ccproxyz.kanagawa-it.ac.jp:10080"
os.environ["https_proxy"] = "http://ccproxyz.kanagawa-it.ac.jp:10080"
    

# GPUを使うかどうか
USE_DEVICE = 'cuda:0' if torch.cuda.is_available() else 'cpu'
device = torch.device(USE_DEVICE)
  
# 保存しておいたモデルを読み込む
model = torch.hub.load('pytorch/vision:v0.6.0', 'deeplabv3_resnet101', pretrained=True)
  
# 出力層のチャンネル数を1にする
model.classifier = deeplabv3.DeepLabHead(2048, 1)
  
# GPUを使用しない場合はデバイスを指定
model.load_state_dict(torch.load('chapt05-model1.pth'))
  
# モデルを推論用に設定する
model.eval()
model.to(device)
    

# 画像をTensorにするtransform
transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406],
                             std=[0.229, 0.224, 0.225])
    ])
    

person_img = 'p1.png'
bg_img = 'bg1.png'
result_img = 'p1_change.png' 
  
frame1 = cv2.imread(person_img, cv2.IMREAD_COLOR) # 人物の画像
frame2 = cv2.imread(bg_img, cv2.IMREAD_COLOR) # 背景の画像
  
# サイズを合わせる
frame1 = cv2.resize(frame1, (1280,720))
frame2 = cv2.resize(frame2, (1280,720))
  
# PILのImageにする
frame = cv2.cvtColor(frame1, cv2.COLOR_BGR2RGB)
pil_image = Image.fromarray(frame)
pil_image = pil_image.resize((256,256)) # サイズを256x256にする
          
# PyTorchのTensorにする
tensor = transform(pil_image) # 正規化してTensorに
tensor = tensor.to(device)
tensor = tensor.reshape((1,3,256,256)) # 1バッチ分のデータにする
  
# モデルを実行する
out = model(tensor)
  
# 実行結果をマスク画像のデータにする
mask = torch.sigmoid(out['out'])
mask = mask.detach().cpu().numpy()[0,0,:,:] # 1チャンネル化
mask = cv2.resize(mask, (1280,720)) # サイズを合わせる
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2RGB) # カラー化
  
# マスクをアルファ値として、人物を抜き出す
cur_frame = (frame1 * mask) + (frame2 * (1.0 - mask))
cur_frame = cur_frame.astype(np.uint8) # 8bit画像にする
  
# 保存
cv2.imwrite(result_img, cur_frame)
    

背景画像

人物画像

人物領域の推定画像

背景を入れ替えた人物画像

背景画像

人物画像

人物領域の推定画像

背景を入れ替えた人物画像