R-CNN#

Rich feature hierarchies for accurate object detection and semantic segmentation

Intersection of union#

Intersection of union(IoU)는 교집합 영역 넓이 / 합집합 영역 넓이

Linear support vector machines#

Linear support vector machines(SVMs)은 2000장의 region proposals에서 fine-tuning때와는 다르게 ground truth box만을 positive sample, IoU 값이 0.3보다 작은 것은 negative sample로 지정한다. 이때, IoU값이 0.3보다 큰 경우 무시한다. 이때 0.3은 gird search를 통해 찾은 값이다. 이후는 fine-tuning과 마찬가지로 positive sample 32개 + negative sample 96개 = 128개의 미니배치를 구성한 후 fine-tuning된 AlexNet에 입력하여 4096 dimensional feature vector를 추출한다. 추출된 벡터를 이용해 linear SVMs를 학습한다. SVM은 2진 분류를 수행하므로 분류하려는 객체의 종류만큼 SVM이 필요하다. 학습이 한 차례 끝난 후, hard negative mining 기법을 적용하여 재학습을 수행한다.

R-CNN에서는 단순히 N-way softmax layer를 통해 분류를 진행하지 않고, SVMs를 이용해 분류를 한다. 이는 SVM을 사용했을 때 성능이 더 좋기 때문이다. 성능 차이의 이유를 논문의 저자들은 positive sample을 정의할 때 SVM을 학습시킬 때 더 엄밀하게 정의한다는 점과 SVM이 hard negative를 이용해 학습하기 때문이라고 서술했다.

linear SVM에서는 output으로 class와 confidence score를 반환한다.

Hard negative mining#

이미지에서 사람을 탐지하는 경우 사람은 positive sample이 되고, 그 외의 배경은 negative sample이 된다. 이때, 모델이 bounding box를 배경이라고 예측하고 실제로 배경인 경우 true negative sample라고 한다. 반면에 모델이 사람이라고 예측했지만, 실제로 배경인 경우 false positive sample에 해당한다.

객체 탐지 시, positive sample보다 negative sample이 더 많은 클래스 불균형 때문에 모델은 주로 false positive 오류를 주로 범하게 된다. 이러한 문제를 해결하기 위해 처음 linear SVMs를 학습시킬 때의 false positive sample들을 epoch마다 학습 데이터에 추가하여 학습을 진행한다. 이를 통해 모델이 강건해지고, false positive 오류가 줄어든다.

Bounding box regressor#

selective search 알고리즘을 통해 얻은 객체의 위치는 부정확할 수 있다. 이런 문제를 해결하기 위해 객체의 위치를 조절해주는 Bounding box regressor가 있다.

\(N\)개의 학습데이터쌍(training pair)인 \(\{(P^{i}, G^{i})\}_{i=1,...,N}\)에 대해 \(P^{i}=(P^{i}_{x}, P^{i}_{y}, P^{i}_{w}, P^{i}_{h})\)는 해당 지역(region)에 대한 추정값으로 각 지역 중심의 좌표 \(x\), \(y\)와 너비(width, \(w\)), 높이(height, \(h\))를 나타내고, 이에 대응되게 \(G^{i}=(G^{i}_{x},G^{i}_{y},G^{i}_{w},G^{i}_{h})\)은 해당 지역(region)에 대한 정답(ground truth)이다.

\[\mathbf{w}_{\star}=\arg \min_{\hat{\mathbf{w}}_{\star}}\sum_{i}^{N}(t_{\star}^{i}-\hat{\mathbf{w}}_{\star}^{T}\theta_{5}(P^{i})^{2}+\lambda )\]
\[ t_{x}=(G_{x}-P_{x})/P_{w} \]

Non maximum suppression#

R-CNN을 통해 얻게 되는 2000개의 bounding box를 전부 다 표시할 경우우 하나의 객체에 대해 지나치게 많은 bounding box가 겹칠 수 있다. 따라서 가장 적합한 bounding box를 선택하는 Non maximum supression 알고리즘을 적용한다.

bounding box별로 지정한 confidence scroe threshold 이하의 box를 제거한다. 남은 bounding box를 confidence score에 따라 내림차순으로 정렬한다. 그 다음 confidence score가 높은 순의 bounding box부터 다른 box와의 IoU값을 조사하여 IoU threshold 이상인 box를 모두 제거한다. 2의 과정을 반복하여 남아있는 box만 선택한다.

import torch
from torchvision.ops import nms, box_iou
from sklearn.svm import LinearSVC


def _xywh_to_xyxy(box):
    x, y, w, h = box
    return x, y, x + w, y + h


class RegionsCNN(nn.Module):
    def __init__(self, num_classes=20, max_size=227):
        super().__init__()
        self.max_size = max_size
        self.region_proposal = SelectiveSearch(min_size=50)
        feature_extractor = models.alexnet(pretrained=True)
        feature_extractor.classifier[-1] = nn.Linear(feature_extractor.classifier[-1].in_features, 2)
        self.feature_extractor = feature_extractor

        self.classifier = LinearSVC()
        self.regressor = nn.Linear(256 * 6 * 6, 4)
        
    def forward(self, images):
        _, regions = self.region_proposal(images)
        
        
        if self.training:
            positives = []
            negatives = []
            # compute ious
            # dict to list modify selective search returns
            regions = torch.tensor(list(regions), dtype=torch.int64)
            gt = torch.tensor([[50, 50, 150, 150]], dtype=torch.int64)
            
            ious = box_iou(regions, gt)
            
            for i in range(len(ious)):
                xmin, ymin, xmax, ymax = regions[i]
                iou_score = ious[i]
                if 0 < iou_score <= 0.3:
                    negatives.append(regions[i])
                elif iou_score >= 0.5:
                    positives.append(regions[i])
                    
            print(negatives)
            print()
            print(positives)
            
            for positive in positives:
                xmin, ymin, xmax, ymax = _xywh_to_xyxy(positive)  # return x, y, w, h
                warp_image = images[ymin:ymax, xmin:xmax]  # H, W, C
                warp_image = transform(warp_image)  # C, H, W
                feature = self.feature_extractor.features(warp_image.unsqueeze(0)) # [?, ?]
                feature = torch.flatten(feature, 1)

                self.classifier.fit(feature, targets)
                outputs = self.regressor(feature)
                print(outputs)
                            
        else:
            scores = []
            positives = []
            for region in regions:
                xmin, ymin, xmax, ymax = _xywh_to_xyxy(region)  # return x, y, w, h
                warp_image = images[ymin:ymax, xmin:xmax]  # H, W, C
                warp_image = transform(warp_image)  # C, H, W
                output = self.feature_extractor(warp_image.unsqueeze(0))[0] # [?, ?]
                
                boxes = self.regressor(feature)
                
                if torch.argmax(output).item() == 1:
                    probs = torch.softmax(output, dim=0)#.cpu().numpy()
                    if probs[1] >= 0.3:
                        scores.append(probs[1])
                        positives.append([xmin, ymin, xmax, ymax])

            positives = torch.tensor(positives, dtype=torch.float32)
            print(positives.size())
            scores = torch.FloatTensor(scores)
            results = nms(positives, scores, iou_threshold=0.3)
            print(results.size())
            print(positives[results])
                

rcnn = RegionsCNN().train()
img = data.astronaut()
print(img.shape)  # skimnage h, w, depth
rcnn(img)
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Input In [3], in <cell line: 3>()
      1 import torch
      2 from torchvision.ops import nms, box_iou
----> 3 from sklearn.svm import LinearSVC
      6 def _xywh_to_xyxy(box):
      7     x, y, w, h = box

ModuleNotFoundError: No module named 'sklearn'
class LossFunction(nn.Module):
    def __init__(self):
        pass
    
    def forward(self, targets):
        xmin, ymin, xmax, ymax = positive
        pw = xmax - xmin
        ph = ymax - ymin
        px = xmin + p_w / 2
        py = ymin + p_h / 2

        xmin, ymin, xmax, ymax = bndbox
        gw = xmax - xmin
        gh = ymax - ymin
        gx = xmin + g_w / 2
        gy = ymin + g_h / 2

        # 计算t
        tx = (g_x - p_x) / p_w
        ty = (g_y - p_y) / p_h
        tw = np.log(g_w / p_w)
        th = np.log(g_h / p_h)