動画に対して、1024×1024のフレーム画像を取得し、256×256の領域64個(縦8×横8、オーバーラップあり)に対して識別器を適用し、出力した確率を格納した(フレーム数,8,8)のnumpy配列を作成したいのですが、現在の実装では
①OpenCVでフレーム画像読み込み
②PIL画像に変換
③識別する領域をクロップして変数に格納
④識別
⑤出力した確率を、配列の対応する場所に格納
⑥③へ
という単純な実装で、処理時間が1フレームあたり約1秒かかり、とても遅いです。
処理したい動画は30000フレームちょっとあり、10時間ほどかかります。
0.1秒ほどで処理するのが理想で、高速化のためいくつか思いついたことを試したのですが上手くいきません。
高速化する良い方法がありましたら教えていただきたいです
Python
1 #推論 2 for i in tqdm(range(num_frame), position=0): 3 cap.set(cv2.CAP_PROP_POS_FRAMES, i+1) 4 ret, frame = cap.read() 5 6 if not ret: 7 break 8 else: 9 pil_frame = cv2pil(frame) 10 11 with torch.no_grad(): 12 for j in range(len(x)): 13 for k in range(len(y)): 14 cropped_img = pil_frame.crop((int(x[j]), int(y[k]), int(x[j])+256, int(y[k])+256)) 15 cropped_img_tensor = transform(cropped_img, phase="test").unsqueeze(0) #transformしたあと、推論するためバッチの次元を追加 16 17 output = model(cropped_img_tensor) 18 prob = softmax(output)[0][1].item() #softmaxを計算 19 prob_array[i][k][j] = prob
↓実行時間計測
Python
1 for i in tqdm(range(num_frame), position=0): 2 a = time.time() ############################### 3 cap.set(cv2.CAP_PROP_POS_FRAMES, i+1) 4 ret, frame = cap.read() 5 b = time.time()############################ 6 7 if not ret: 8 break 9 else: 10 pil_frame = cv2pil(frame) 11 12 c = time.time() ####################### 13 14 with torch.no_grad(): 15 for j in range(len(x)): 16 for k in range(len(y)): 17 cropped_img = pil_frame.crop((int(x[j]), int(y[k]), int(x[j])+256, int(y[k])+256)) 18 cropped_img_tensor = transform(cropped_img, phase="test").unsqueeze(0) #transformしたあと、推論するためバッチの次元を追加 19 20 output = model(cropped_img_tensor) 21 prob = softmax(output)[0][1].item() #softmaxを計算 22 prob_array[i][k][j] = prob 23 24 d = time.time() ############################## 25 print(b-a) 26 print(c-b) 27 print(d-c)
0%| | 0/32762 [00:00<?, ?it/s]
0.05161237716674805
0.017586469650268555
2.1721038818359375
0%| | 1/32762 [00:02<20:23:48, 2.24s/it]
0.044759511947631836
0.016994237899780273
0.6606061458587646
0%| | 2/32762 [00:02<16:14:59, 1.79s/it]
0.056168556213378906
0.016913175582885742
0.6579580307006836
0%| | 3/32762 [00:03<13:22:14, 1.47s/it]
0.06777358055114746
0.01434183120727539
0.6527915000915527
0%| | 4/32762 [00:04<11:21:56, 1.25s/it]
0.07939982414245605
0.014380693435668945
0.6701173782348633
0%| | 5/32762 [00:05<10:02:29, 1.10s/it]
0.09820103645324707
0.014620542526245117
0.6635544300079346
0%| | 6/32762 [00:05<9:08:54, 1.01s/it]
0.10292172431945801
0.015182971954345703
0.6512439250946045
0%| | 7/32762 [00:06<8:30:15, 1.07it/s]
0.11549758911132812
0.01836109161376953
0.649014949798584
0%| | 8/32762 [00:07<8:05:24, 1.12it/s]
0.13360857963562012
0.016347885131835938
0.6655523777008057
・
・
これはVGG16で実行したときの時間です
32762はフレーム数です
一番時間がかかるのは推論だと思うのですが、なぜかモデルをMobileNet_v2にしてもほとんど実行時間は変わりませんでした
#追記
・1/4
cap.read()すると勝手に次のフレームに行くみたいなので、余計なcap.setをコメントアウトしたところ、実行時間が約1.3倍になり、7時間ほどで処理できるようになりました。
30000フレームちょっとの動画の
read()に7分
cv2pilに8分
cropとtransformに30分
output = model(cropped_img_tensor)に約6時間
かかるようです。
Python
1 for i in tqdm(range(num_frame), position=0): 2 #cap.set(cv2.CAP_PROP_POS_FRAMES, i+1) 3 ret, frame = cap.read()
・1/5
dataloaderを使用し、64枚一気に推論するように変更したところ、1秒で2.6フレームほど処理可能で、3時間ちょっとで処理できるようになりました。
しかし、以前の実装では大丈夫だったのですが、なぜか実行毎に推論結果が同じにならず、原因を調査中です。
Python
1getitemを変更 2 def __getitem__(self, index): 3 i = index // len(self.x) 4 j = index % len(self.y) 5 cropped_img = self.frame.crop((int(self.x[j]), int(self.y[i]), int(self.x[j])+256, int(self.y[i])+256)) 6 cropped_img = self.transform(cropped_img) 7 return cropped_img 8 9推論部分を変更 10 for i in tqdm(range(num_frame), position=0): 11 ret, frame = cap.read() 12 13 if not ret: 14 break 15 else: 16 pil_frame = cv2pil(frame) 17 18 train_dataset = Dataset(x, y, pil_frame, transform=Transform(resize, mean, std), phase="test") 19 train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=64, num_workers=2, pin_memory=True) 20 21 with torch.no_grad(): 22 for inputs in train_dataloader: 23 if use_cuda: 24 inputs = inputs.cuda() 25 outputs = model(inputs) 26 prob_array[i] = softmax(outputs)[:, 1].to('cpu').detach().numpy().copy().reshape(len(x), len(y)) #8×8にreshape
・1/8
torch.manual_seed(1)とrandom.seed(1)を行うと推論結果が実行毎に同じになるようになりましたが、以前のように1枚ずつ推論していたときとは少し異なる結果となっています
1枚ずつ推論する方では、実行結果が毎回同じになっています
・1/17
解決しました!!
私の初歩的なミスでした。申し訳ありません。
データ拡張のTransformを
Python
1class Transform(): 2 def __init__(self, resize, mean, std): 3 self.data_transform = { 4 "train": transforms.Compose([ 5 #Histogram_Equalization(), 6 Luminance_Histogram_Equalization(), 7 transforms.RandomRotation((-20,20)), 8 transforms.RandomVerticalFlip(), 9 transforms.RandomHorizontalFlip(), 10 transforms.Resize(resize), 11 transforms.ToTensor(), 12 transforms.Normalize(mean, std) 13 ]), 14 "test": transforms.Compose([ 15 #Histogram_Equalization(), 16 # Luminance_Histogram_Equalization(), 17 transforms.Resize(resize), 18 transforms.ToTensor(), 19 transforms.Normalize(mean, std) 20 ]) 21 } 22 23 def __call__(self, img, phase="train"): 24 return self.data_transform[phase](img)
というふうに、Transformのtrainとtestを辞書型にしていて、デフォルトでtrainの方を呼び出すようにしていたのが原因でした。
datasetのgetitemを変更し
Python
1✖ cropped_img = self.transform(cropped_img) 2〇 cropped_img = self.transform(cropped_img, self.phase)
とすることで、ちゃんとtest用のtransformをするようになり、最初に書いていた1枚ずつ推論するコードとまったく同じ結果になりました
seedを固定しないと結果が同じにならなかったのは、train用のtransformのRandom~等の影響だと思います
また、test時には不要なtransformの処理もなくなり、少し早くなって1秒当たり3.9フレームほど処理できるようになり、2時間20分で推論できるようになりましたが、まだ1時間を切れていないので、さらに高速化できればまた追記します
A_kirisakiさん、様々な助言を下さり、本当にありがとうございました。
・1/18
画像の読み込み等をデータローダーで並列化し、1秒で6.5フレームほど処理でき、1時間20分で終わるようになりました
Python
1キャッシュを使用してフレーム画像を取得するクラスを追加 2class Frame: 3 def __init__(self, cap): 4 self.cap = cap 5 6 @lru_cache(maxsize=1) # キャッシュ 7 def __call__(self, frame_num): 8 # self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num) 9 ret, frame = self.cap.read() 10 pil_frame = cv2pil(frame) 11 return pil_frame 12 13Frameクラスに合わせて__getitem__を変更 14class Dataset(data.Dataset): 15 def __init__(self, x, y, cap, num_frame, transform=None, phase="test"): 16 self.x = x 17 self.y = y 18 self.num_frame = num_frame 19 self.transform = transform 20 self.phase = phase 21 self.get_frame = Frame(cap) 22 23 def __len__(self): 24 return len(self.x) * len(self.y) * self.num_frame 25 26 def __getitem__(self, index): 27 frame_num = index // (len(self.x) * len(self.y)) # index // 8 * 8 28 i = (index - (frame_num * len(self.x) * len(self.y))) // len(self.x) 29 j = (index - (frame_num * len(self.x) * len(self.y))) % len(self.y) 30 self.frame = self.get_frame(frame_num) 31 cropped_img = self.frame.crop((int(self.x[i]), int(self.y[j]), int(self.x[i])+256, int(self.y[j])+256)) 32 cropped_img = self.frame.crop((int(self.x[j]), int(self.y[i]), int(self.x[j])+256, int(self.y[i])+256)) 33 cropped_img = self.transform(cropped_img, self.phase) 34 return cropped_img 35 36Frameクラスのおかげで、1フレームずつ読み込んでそのたびにデータローダーを宣言する必要が無くなったので、推論部分を変更 37 train_dataset = Dataset(x, y, cap, num_frame, transform=Transform(resize, mean, std), phase="test") 38 train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=64, num_workers=1, pin_memory=True) # num_workersは2にすると変なメッセージが出る上に、1と速度が大差ないので1 39 40 model.eval() 41 print("-------------") 42 print("推論開始") 43 44 #推論 45 with torch.no_grad(): 46 for i, inputs in enumerate(tqdm(train_dataloader)): 47 if use_cuda: 48 inputs = inputs.cuda() 49 outputs = model(inputs) 50 prob_array[i] = softmax(outputs)[:, 1].to('cpu').detach().numpy().copy().reshape(len(x), len(y))
目標の50分まではまだ届いていませんが思いつくことは大体やったので、解決にしようと思います。
ありがとうございました。
回答2件
あなたの回答
tips
プレビュー