yolov1 구현 ] 오브젝트간의 상대적 위치에 따른 디텍션 성능 하락

안녕하세요. YOLOv1 구현 중에 어려움을 겪고 있어 조언을 부탁드립니다.

0. 문제 소개

문제: 오브젝트의 오른쪽 위 대각선 방향에 오브젝트가 위치하면 디텍션 성능이 크게 감소합니다.

  • 컨피던스 스코어, 박스 크기, 오브젝트 클래스 예측이 훨씬 부정확해집니다.

    • 박스 중심은 어느정도 잡아내고 있습니다. 오브젝트의 중심은 찾되 박스 크기나 클래스를 다른 오브젝트를 보고 예측하는 느낌입니다.
  • 아래 예시들은 모두 학습에 사용한 데이터들로 데이터 어그멘테이션에 좌우 플립을 포함하였으므로 모두 모델이 학습한 사진들입니다. 하지만 오브젝트간의 상대적인 위치가 좌하단-우상단인 경우 디텍션 성능이 크게 감소하는 것을 보실 수 있습니다.

  • 아래의 이미지들 외에도 여러 데이터 샘플에서 공통적으로 문제가 발생하고 있습니다.

  • NMS를 수행하지 않은 상태입니다.

  • 예시: 차례로 (정상 / 에러 발생) 입니다.

    • 0: 클래스, 박스 크기 예측에 오류 발생 - 모두 horse로 예측, 박스 크기 부정확
      • 000235
      • 000235_error
    • 1: 컨피던스 스코어, 박스 크기, 클래스 예측에 오류 발생 - 박스 크기 부정확, dog 박스에 dog, person으로 예측?
      • 000278
      • 000278_error
    • 2: YOLO의 대표적인 예시에서도 발생.
      • example_flip
      • example_original
    • 3: 박스 크기, 클래스 예측에 오류 발생 - 사람에 가까운 박스는 horse, 말에 가까운 박스는 person 예측, 박스 중심, 크기 부정확
      • 000799
      • 000799_error

1. 모델

  • github
  • YOLOv1 구조에서 백본을 resnet18로 교체하였고 나머지는 모두 논문과 동일합니다.
  • 모델 구조 문제인지 확인을 위해 conv, dropout, fully connected 레이어 구조를 conv+bn+relu+1x1conv 구조로 변경하여도 같은 문제가 발생했습니다.

2. 학습

lr warmup 방식이 다르고 나머지는 다크넷으로 구현된 yolov1과 거의 같은 설정을 사용합니다.

  • 배치 64, subdivision 2 (32 * 2 누적하여 가중치 갱신)
  • 총 40000 배치 학습
  • SGD + momentum + decay
  • lr
    • 1e-3으로 시작 1100 스텝동안 1e-2로 웜업
    • 20000, 30000 스텝에서 1/10
  • 시드를 바꿔 수차례 학습하여도 같은 결과를 보였습니다.

3. 데이터

처음에는 데이터를 의심하였으나 코드를 다시 점검하고 어그멘테이션 결과를 시각화해보아도 문제를 찾지 못하였습니다.

  • VOC 2012 + 2007
    • 좌우 플립을 포함하여 논문에 나온 crop & scaling , HSV 컬러에서의 saturation, exposure scaling
  • 데이터로더에서는 이미지 & 어노테이션 배치를 출력
  • 어그멘테이션 예시
    • 원본
      original
    • 어그멘테이션
      aug

4. loss 계산

  • github
  • prediction: (64, 7, 7, 30) shape
  • target: ({num_object}, 8) shape
    • 8: (batch index, grid y index, grid x index, x, y, w, h, class index)
    • target에 포함된 오브젝트별로 배치의 몇번째 이미지의 어느 그리드에 위치하는지를 (batch index, grid y index, grid x index)로 표현했고, 이를 인덱스로 삼아 prediction에서 오브젝트가 위치한 그리드의 예측을 가져와 loss를 계산합니다.
  • 좌우 반전할때 loss 값이 차이가 있나 싶어 타겟과 prediction을 좌우반전하여 계산하여도 좌우반전하지 않은 샘플과 loss 값이 같았습니다.

계속 코드를 점검해보는데 문제점을 발견하지 못하고 있습니다.
여유가 있으시다면 조언 부탁드립니다.
긴 글 읽어주셔서 감사합니다.

안녕하세요? 제가 생각한 의견 공유 드립니다.

현재 좌우플립(Horizontal Flip) 수행시에 특히 이슈가 되는 것 같은데요. 보통 학습한 데이터면 bbox 결과가 잘 나와야 하는게 맞습니다.

공개해주신 코드를 보니 좌우플립 하실때 bbox xywh 값 중 x = 1.0 -x로만 변경을 하시던데요. 이 부분이 잘못 적용된 것 같아 보이는데 확인 해 보시면 어떨까 합니다.(좌우플립 후에 bbox를 이미지에 그려서 잘못 되진 않았는지 확인해보세요)
감사합니다.

1개의 좋아요

안녕하세요.

저도 플립이 문제인가 싶어 다시 점검을 해보았습니다.
좌우 플립만 적용하였기에 YOLO 포맷의 x좌표만 조절하였고
어그멘테이션 결과를 시각화하면
3.데이터 항목의 어그멘테이션 예시처럼 의도한대로 출력이 되었습니다.
아직 어떤 점이 문제인지 찾지 못하고 있습니다.

답변 감사드립니다.

간단한 예를 들면, xywh 가 [0.1, 0.1, 0.1, 0.1] 이라고 가정해 보겠습니다.
그러면 hflip 했을시에 [0.9, 0.1, 0.1, 0.1] 이 아니라 [0.8, 0.1, 0.1, 0.1] 이 되야 하는게 맞습니다.

그래서 flip 시 이슈 인것 같다고 얘기 드린 부분이구요. 이게 이슈가 아니라면 좀 더 코드를 봐야 알 것 같네요.
감사합니다.

1개의 좋아요

계속 도움을 주셔서 감사합니다.

보여주신 예제에서 xywh가 [0.9, 0.1, 0.1, 0.1] 대신 [0.8, 0.1, 0.1, 0.1]이 되는 과정을
제가 이해하지 못하고 있는데 조금 더 설명을 부탁드리고 싶습니다.

감사합니다.

앗, @bongmo 님께서 바쁘신가 보네요 ㅎㅎ
먼저, 저는 CV 분야를 잘 알지는 못해서 틀릴 수 있음을 양해 부탁드립니다 :bowing_man:

추측하기로는 @bongmo 님께서 예시로 드신 xywh[0.1, 0.1, 0.1, 0.1]을 고려해보면,
(x_0, y_0) 의 좌표가 (0.1, 0.1)로 box가 이미지의 끝에 붙어있지 않고, 0.1 (= x_0)만큼 떨어져있습니다.

하지만 @canlion 님께서 하셨던 것처럼 x'_0 = 1.0 - x_0 로 hflip을 하는 경우에는
(x'_0, \ y'_0) 의 좌표가 (0.9, 0.1)로 해당 box는 이미지 끝에 딱 붙게 됩니다.
(box의 오른쪽 끝 좌표 x'_1 = x'_0 + w = 0.9 + 0.1 = 1.0)

즉, 좌표 변환 과정에서 박스의 넓이(w)를 고려하지 않아서 생긴 이슈로 이해했고,
아마도 그런 이유에서 그려서 확인이 필요하다고 하신 것으로 이해하였습니다.

hflip의 경우 중심 선(0.5)을 기준으로 x_1 \ -> \ x'_0 이 되고, x_0 \ -> \ x'_1 이 되어야 할 것 같습다.
한 번 x'_0 = 1 - x_1 = 1 - (x_0 + w) 와 같은 방식으로 hflip을 해보시면 어떠실까요?

(CV 분야를 잘 알지 못해서 이 설명이 틀릴 수도 있으니 참고만 부탁드립니다. :sweat_smile: )

1개의 좋아요

답변 감사합니다.

추가 설명을 붙여주셔서 두 분께서 말씀하신 내용을 이해할 수 있었습니다.
xy를 박스의 좌상단 좌표로 사용하신 내용으로 이해했습니다.
그런데 yolo에서 xy좌표는 박스의 좌상단 좌표 대신 박스의 중심을 의미합니다.
yolo에서만 사용되는 형식인데 제가 설명을 자세히 드리지 않아 오해가 생긴 듯 합니다.
다음부터는 좀 더 디테일하게 글을 작성토록 하겠습니다.

@bongmo 님, @9bow 님, 답변 다시 한 번 감사드립니다.

앗, 그렇군요ㅠ
제가 잘 모르고 잘못된 설명을 드렸네요 :sob:
번거롭게 해드려 죄송합니다 :bowing_man:

1개의 좋아요

아닙니다. 제가 설명이 부족했던 부분이 있었습니다.
저한테 익숙하니깐 생략했었네요. 흔히 하는 실수인데 잘 안고쳐지네요 ㅎㅎ;
오히려 설명이 부족했는데도 맞춰서 답변 작성해주시느라 고생하셨습니다.

1개의 좋아요

문제를 찾았습니다. 제가 코드를 잘못 작성했습니다.

데이터셋에서 오브젝트마다 (오브젝트가 속한 배치(이미지) 인덱스, 그리드 인덱스 y, 그리드 인덱스 x, …)를 받아
True/False 마스크를 생성해 prediction에서 오브젝트가 위치한 그리드의 값을 인덱싱하는데
마스크에서는 오브젝트의 순서를 반영하지 못해 prediction과 target의 페어가 어긋났습니다.

답변주신 분들 모두 감사드립니다.

글은 테스트해보고 닫도록 하겠습니다!

        # loss.py
        ...
        # obj grid indicator
        obj_grid_index = target[:, :3].long()
        obj_grid_mask = torch.zeros(batch, S, S, dtype=torch.bool, device=device)
        obj_grid_mask.index_put_(torch.split(obj_grid_index, 1, dim=-1), torch.tensor(True, device=device))
        ######################################################################
        # mask를 생성하면서 오브젝트의 순서가 사라짐
        ######################################################################
        assert obj_grid_mask.sum().item() == N, 'obj_grid_mask'

        # object grid - pred에서 오브젝트가 위치한 그리드의 예측값
        obj_grid_pred = pred[obj_grid_mask]

        ######################################################################
        # Ture/False mask는 오브젝트 순서를 반영하지 못함 -> prediction과 target의 페어가 어긋남
        ######################################################################
        ...
3개의 좋아요

오오… 축하드립니다!! :tada:
모쪼록 잘 동작하길 기원합니다!! :pray:

1개의 좋아요

이 글은 마지막 댓글이 달린지 오래(2일)되어 자동으로 닫혔습니다. 댓글 대신 새로운 글을 작성해주세요! :slight_smile: