-
Notifications
You must be signed in to change notification settings - Fork 0
faceboxesのbatch処理を追加 #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
20ee2f5
a75a25d
747aac1
4541d29
d7cb70b
4784a47
db7d1f5
58837c5
d9b0313
62a20de
c57656a
93a3b82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,11 +4,13 @@ | |
| from .data import cfg | ||
| from .layers.functions.prior_box import PriorBox | ||
| from .models.faceboxes import FaceBoxes | ||
| from .utils.box_utils import decode | ||
| from .utils.box_utils import decode, batch_decode, get_faceboxes_max_batch_size | ||
| from typing import List, Tuple | ||
|
|
||
| import cv2 | ||
|
|
||
| class FaceBoxesFaceDetector(object): | ||
| def __init__(self, use_gpu=False): | ||
| torch.set_grad_enabled(False) | ||
| # net and model | ||
| self.net = FaceBoxes(phase='test', size=None, num_classes=2) # initialize detector | ||
| weight_path = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'weights/FaceBoxes.pth')) | ||
|
|
@@ -55,6 +57,7 @@ def load_model(self, model, pretrained_path, load_to_cpu): | |
| model.load_state_dict(pretrained_dict, strict=False) | ||
| return model | ||
|
|
||
| @torch.no_grad() | ||
| def get_faceboxes(self, image, threshold=0.2): | ||
| resize = 1 | ||
|
|
||
|
|
@@ -90,3 +93,126 @@ def get_faceboxes(self, image, threshold=0.2): | |
| scores, boxes = zip(*scores_and_boxes) | ||
|
|
||
| return scores, boxes | ||
|
|
||
| @torch.no_grad() | ||
| def get_batch_faceboxes(self, image_list:List[np.ndarray], *, batch_size=-1, threshold=0.2)->List[Tuple[List[float],List[np.ndarray]]]: | ||
| """ | ||
| image_list: List[np.ndarray] 同じサイズの画像のリスト | ||
| batch_size: image_listの中から何個処理するかを指定する. デフォルトの場合はgpuの空きメモリから算出する. 値は2以上にすること | ||
| threshold: faceboxesのconfの閾値 | ||
|
|
||
| return: List[Tuple[confのリスト],[BoundingBoxのリスト]] | ||
| """ | ||
|
|
||
| if len(image_list) == 0: | ||
| return [([],[])] | ||
|
|
||
| if (batch_size == 0) or (batch_size == 1): | ||
| raise ValueError("batch_size must be greater than or equal to 2.") | ||
|
|
||
| im_height, im_width, im_ch = image_list[0].shape | ||
|
|
||
| if (batch_size == -1) and (torch.cuda.is_available()): | ||
| # batch_sizeが未指定の場合はgpuの空きメモリと画像1枚あたりの容量から許容枚数を算出し、安全マージンの7掛けした値をbatch_sizeとして使用する | ||
| torch.cuda.empty_cache() | ||
| batch_size = get_faceboxes_max_batch_size(width=im_width, height=im_height, ch=im_ch) | ||
| batch_size = int(batch_size * 0.7) | ||
|
|
||
| if (batch_size == -1) and (not torch.cuda.is_available()): | ||
| batch_size = len(image_list) | ||
|
|
||
| batch_size = min(len(image_list), batch_size) | ||
| resize = 1 | ||
|
|
||
| results = [] | ||
|
|
||
| priorbox = PriorBox(cfg, image_size=(im_height, im_width)) | ||
| priors = priorbox.forward() | ||
| priors = priors.to(self.device) | ||
| prior_data = priors.data | ||
|
|
||
| for i in range(0, int(len(image_list) / batch_size) + 1): | ||
| images = image_list[batch_size * i: min(len(image_list), batch_size * (i+1))] | ||
|
|
||
| if len(images) == 0: | ||
| break | ||
|
|
||
| if len(images) == 1: | ||
| scores, boxes = self.get_faceboxes(images[0], threshold) | ||
| results.append((scores, boxes)) | ||
| break | ||
|
|
||
| im_height, im_width, im_ch = images[0].shape | ||
| scale = torch.Tensor([im_width, im_height, im_width, im_height]) | ||
| images = np.array(images).reshape(len(images), im_height, im_width, im_ch) | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. リスト内部の画像サイズが違う場合はここでValueErrorが発生する |
||
|
|
||
| imgs = torch.from_numpy(images) | ||
| imgs = imgs.to(self.device) | ||
| scale = scale.to(self.device) | ||
|
|
||
| imgs = imgs.to(torch.float32) | ||
| imgs = torch.sub(imgs, torch.tensor((104,117,123)).to(self.device)) | ||
| imgs = imgs.permute(0,3,1,2) | ||
|
|
||
| loc, conf = self.net(imgs) | ||
|
|
||
| boxes = batch_decode(loc.data.squeeze(0), prior_data, cfg['variance']) | ||
| boxes = boxes * scale / resize | ||
|
|
||
| boxes = boxes.cpu().numpy() | ||
| scores = conf.data.cpu().numpy()[:, :, 1] | ||
|
|
||
| thres_tuple = torch.nonzero(torch.where(conf[:,:,1] > threshold, 1, 0), as_tuple=True) | ||
| image_inds = thres_tuple[0].data.cpu().numpy() | ||
| boxes_inds = thres_tuple[1].data.cpu().numpy() | ||
|
|
||
| # ここgpu化しなくても処理時間が支配的ではないのでcpuでやる | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1000枚画像相手だと全体が1000ms ~ 1500msに対して30~50ms程度だった |
||
| result = [] | ||
| for i in range(0, len(images)): | ||
| inds = boxes_inds[np.where(image_inds == i)] | ||
| if len(inds) == 0: | ||
| result.append(([],[])) | ||
| continue | ||
|
|
||
| scores_and_boxes = zip(scores[i][inds], boxes[i][inds]) | ||
| scores_and_boxes = sorted(scores_and_boxes, key=lambda x: -x[0]) | ||
| s, b = zip(*scores_and_boxes) | ||
| result.append((s,b)) | ||
|
|
||
| results += result | ||
|
|
||
| # 以降gpu上の画像は使わないので解放する | ||
| del imgs | ||
|
|
||
| return results | ||
|
|
||
| @torch.no_grad() | ||
| def get_batch_faceboxes_with_resize(self, image_list:List[np.ndarray], *, resize_target_width=360, resize_target_height=640, | ||
| batch_size=-1, threshold=0.2)->List[Tuple[List[float],List[np.ndarray]]]: | ||
| """ | ||
| image_list: List[np.ndarray] 画像のリスト 内部でresizeするため、同じ大きさである必要はない | ||
| resize_target_width: 内部でresizeするときのwidth | ||
| resize_target_height: 内部でresizeするときのheight | ||
| 上二つのデフォルト値はアスペクト比9:16にしている | ||
| batch_size: image_listの中から何個処理するかを指定する. デフォルトの場合はgpuの空きメモリから算出する.値は2以上にすること | ||
| threshold: faceboxesのconfの閾値 | ||
|
|
||
| return: List[Tuple[confのリスト],[BoundingBoxのリスト]] | ||
| """ | ||
|
|
||
| resize_target_resize_size = (resize_target_width, resize_target_height) | ||
| shapes = [i.shape for i in image_list] | ||
| resized_images = [cv2.resize(i, resize_target_resize_size) for i in image_list] | ||
|
|
||
| results = self.get_batch_faceboxes(resized_images, batch_size=batch_size, threshold=threshold) | ||
|
|
||
| new_results = [] | ||
| for shape, (confs, boxes) in zip(shapes, results): | ||
| h, w = shape[:2] | ||
| rh = h / resize_target_height | ||
| rw = w / resize_target_width | ||
| ratios = [rw, rh, rw, rh] | ||
| new_result = (confs, [b * ratios for b in boxes]) | ||
| new_results.append(new_result) | ||
|
|
||
| return new_results | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 実行時にリサイズし、結果をオリジナルスケールに直す処理を追加
この2パターンを比較すると、最大50ピクセルほどずれる(実装ミスあるかも
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://pytorch.org/docs/stable/generated/torch.no_grad.html
with句 または デコレータをつけることで内部の勾配計算をなくしてメモリ節約