[Yolo V3] 편의점 물품 객체 인식

GPU : GeForce RTX 2080 Ti SUPER

총 6개의 Class(chip, crispy, almond, gum, coffee, pepero)를 위에서 내려다보는 각도에 고정시킨 카메라로 볼 때 Object detection 결과를 확인하는 프로젝트를 수행했습니다.

 

1차 (학습 : 12000번)

물체의 옆 면과 윗면을 돌리면서 물체 자체를 회전시키는 데이터셋을 만들었습니다.

Class 1 (원본 11개, rotate 396개, 총 407개)
Class 2 (원본 9개, rotate 346개, 총 355개)
Class 3 (원본 7개, rotate 504개, 총 511개)
Class 4 (원본 18개, rotate 630개, 총 648개)
Class 5 (원본 10개, rotate 489개, 총 499개)
Class 6 (원본 10개, rotate 539개, 총 549개)

★ 문제점

-> 두 개 이상의 object를 붙여 놓았을 때 bounding box가 잘 잡히지 않고 인식을 제대로 못 함 -> 두 개 이상의 object를 붙여 놓은 dataset 만들기 
-> 학습시킨 object들 이외의 물건을 인식하는 경우가 있었습니다.
-> 몇 개의 object는 scale이 작아졌을 때 인식을 제대로 못했습니다.
-> 2차에서는 2, 3 object dataset과 4, 5 object dataset 학습 / 1 object 배경이 다른 dataset 학습 / 모든 dataset을 학습 / 1, 6 object dataset 학습

 

2차

2, 3 object(학습 : 16000번) vs. 4, 5 object (학습 : 17000번)

rotation을 많이 시키면 bounding box가 필요 이상으로 커져 회전을 많이 시키지 않은 dataset 사용했습니다.

Class 2, 3 (원본 263개, rotate 790개, 총 1053개)
Class 4, 5 (원본 102개, rotate 503개, 총 605개)

★ 문제점

-> pepero, coffee, gum, chip을 붙여 놓았을 때 coffee와 gum이 잘 인식이 안됐습니다.
-> coffee, almond, chip을 붙여 놓았을 때 coffee나 almond가 잘 인식이 안됐습니다.
-> crispy와 almond를 붙여 놓았을 때 label은 잡았으나 crispy의 영역이 넓게 잡히는 등 여전히 object들을 붙여 놓았을 때 인식이 잘 되지 않음을 확인했습니다.

 

배경을 다르게 한 1 object(학습 : 10000번)

Class 1 (원본 21개, rotate 63개, 총 84개)
Class 2 (원본 33개, rotate 99개, 총 132개)
Class 3 (원본 44개, rotate 132개, 총 176개)
Class 4 (원본 17개, rotate 51개, 총 68개)
Class 5 (원본 28개, rotate 84개, 총 112개)
Class 6 (원본 47개, rotate 138개, 총 185개)

★ 문제점

-> 카메라를 고정시키고 test를 해봤으나 많이 가려진 object는 잘 인식하지 못했습니다.
-> json파일을 분석해 본 결과 Precision이 매우 높게 나와 대부분의 object들을 잘 인식하는 것으로 확인했습니다.

모든 dataset(학습 : 15000번)

-> webcam으로 확인해 본 결과 육안으로 object들을 놓았을 때 잘 인식하는 것을 확인했습니다.
-> 그러나 mAP를 확인해 본 결과 FN, FP의 개수가 있는 것으로 보아 잘 인식하지 못하는 image가 있음을 확인했습니다.

1, 6 object dataset(학습 : 20000번)

Class 6 (원본 7개, rotate 280개, 총 287개)

★ 문제점

-> 검은색 물체 edge 부분이 흐려지면 잘 인식하지 못했습니다.
-> 흰색 배경이 아닌 곳에서 webcam으로 test, object가 없는 부분을 object로 인식하는 경우가 있었습니다.
-> crispy와 pepero의 특징을 잘 잡지 못한 것으로 보입니다.

<pepero를 crispy 로 잡은 사진>

 

MAP(Mean Average Precision)

대표적으로 Object 4, 5 학습 MAP을 가져왔습니다.

<Result.json 파일 분석 결과>

Test data : object 3, 5(train시키지 않은), 6개 img

<FN example>

정답 class = [0, 2, 3, 4, 5]
검출 class = [4, 3, 2]

-> TP : 4, 3, 2  총 3개
-> FP : x
-> FN : 0, 5  총 2개

<FP example>

★ 문제점 : class 개수가 같으면 TP 로 처리했기 때문에 AlexeyAB의 mAP과 차이가 납니다.

IoU(Intersection over Union)을 고려하면 더 정확한 결과가 나올 것이라고 생각됩니다.

정답 class = [0, 2, 3, 4, 5]
검출 class = [4, 5, 3, 2, 0]

-> TP : 0, 2, 3, 4  총 4개          -> 실제 TP : 2, 3, 4
-> FP : 4  총 1개 -> 실제 FP : 0, 4
-> FN : x

<TP example>

정답 class = [0, 1, 2, 3, 4]
검출 class = [4, 3, 2, 1, 0]

-> TP : 0, 1, 2, 3, 4    총 5개

 

결론

object들을 붙여 놓았을 때 edge검출이 잘 되지 않음을 확인했습니다. 배경을 다르게 해서 찍은 dataset이나 object들을 붙여 놓은 dataset을 많이 train한 시도들에서는 test에서나 mAP를 확인해본 결과 잘 인식하는 것을 확인했습니다. 이 문제 이외에 crispy와 pepero object에서 특징을 잘 뽑아내지 못하는 것을 확인했습니다.  이 문제는 배경이 다른 1 object train과 1~6object train 모두에서 발견된 문제점으로 더 개선되어야 할 점으로 생각됩니다. 

3차로 더 train시킨다면

-> crispy와 pepero의 train dataset을 좀 더 넣어보기.(좀 더 여러 방향에서 찍은 dataset)
-> object가 많이 가려진 dataset도 넣어보기.

 

Yolo V3 데이터 셋 박스 코드 (객체가 여러 개일 때)

import cv2
import math

## ! 수정하는 부분


def read_file(txt_file):
    lst = []
    original_rectangle = [0]*6
    class_num = []
    center = []
    rectangle = [0]*6
    # print(original_rectangle)
    for line in txt_file:
        lst = list(map(float, line.strip().split()))
        # lst[0] = class 번호
        # lst[1] = x_center / width
        # lst[2] = y_center / height
        # lst[3] = w / width
        # lst[4] = h / height
        class_num.append(int(lst[0]))
        x_center = width * lst[1]
        y_center = height * lst[2]
        center.append((x_center, y_center))
        w = width * lst[3]
        h = height * lst[4]
        rectangle[int(lst[0])] = (w, h)
        # print(x_center, y_center, w, h)
        # class 번호 index에 좌표값 저장
        left_top_x = int(x_center - w / 2)
        left_top_y = int(y_center - h / 2)
        right_top_x = int(x_center + w / 2)
        right_top_y = int(y_center - h / 2)
        right_bottom_x = int(x_center + w / 2)
        right_bottom_y = int(y_center + h / 2)
        left_bottom_x = int(x_center - w / 2)
        left_bottom_y = int(y_center + h / 2)
        original_rectangle[int(lst[0])] = [(left_top_x, left_top_y), (right_top_x, right_top_y),
                                           (right_bottom_x, right_bottom_y), (left_bottom_x, left_bottom_y)]
    return original_rectangle, class_num, center, rectangle


def rotate_img(image, th):
    matrix = cv2.getRotationMatrix2D((width / 2, height / 2), th, 1)
    dst = cv2.warpAffine(image, matrix, (width, height))
    return dst


def draw_rectangle(image, o, class_num):
    for j in class_num:
        cv2.rectangle(image, o[j][0], o[j][2], (0, 30*j, 0), 2)


def rotation(image, c_num, center, rect, count):
    class_num = 0
    _count = 0
    flag = 0
    for cx, cy in center:
        c = c_num[class_num]
        _count = count
        for i in range(10, 360, 10):
            img_theta = i
            rot_img = rotate_img(image, img_theta)
            theta = math.radians(360 - img_theta)
            a = int((cx - width / 2) * math.cos(theta) - (cy - height / 2) * math.sin(theta) + width / 2)
            b = int((cx - width / 2) * math.sin(theta) + (cy - height / 2) * math.cos(theta) + height / 2)
            rct = ((a, b), (rect[c][0], rect[c][1]), -img_theta)
            box = cv2.boxPoints(rct).astype(int)
            # cv2.polylines(rot_img, [box], True, (255, 0, 0), 2)
            l_b = list(box)
            x = []
            y = []
            for k in range(len(l_b)):
                x.append(l_b[k][0])
                y.append(l_b[k][1])
            # cv2.rectangle(rot_img, (min(x), min(y)), (max(x), max(y)), (0, 0, 255), 2)
            rw = max(x) - min(x)
            rh = max(y) - min(y)
            d3 = rw / width
            d4 = rh / height
            d1 = (min(x) + (rw/2)) / width
            d2 = (min(y) + (rh/2)) / height
            # print(c, d1, d2, d3, d4)
            direct = 'C:\\Users\\cosge\\Desktop\\image\\train{}.txt'.format(_count)  ## ! 파일 이름, 경로 수정
            i_dir = 'C:\\Users\\cosge\\Desktop\\image\\train{}.jpg'.format(_count)  ## ! 파일 이름, 경로 수정
            # print(_count)
            cv2.imwrite(i_dir, rot_img)
            data = '{} {} {} {} {}\n'.format(c, d1, d2, d3, d4)
            f = open(direct, mode='a', encoding='utf-8')
            f.write(data)
            _count += 1
            cv2.imshow('img2', rot_img)
            key = cv2.waitKey(1)
            if key == 27:
                flag = 1
                break
        class_num += 1
        if flag == 1:
            break
    return _count
    #
    # key = cv2.waitKey(0)
    # cv2.destroyAllWindows()

############MAIN################


count = 28  # train image 원본 개수 + 1   ## ! 원본 이미지 파일 번호 +1로 수정

for number in range(28):  ## 원본 이미지 개수 = 28개, 번호 0~27   ## ! count와 같은 수로 수정
    filename = 'C:\\Users\\cosge\\Desktop\\image\\train{}.jpg'.format(number)  ## ! 파일 이름, 경로 수정
    txt = 'C:\\Users\\cosge\\Desktop\\image\\train{}.txt'.format(number)  ## ! 파일 이름, 경로 수정
    img = cv2.imread(filename)
    height, width, channel = img.shape
    # print(width, height)

    t = open(txt, mode='rt', encoding='utf-8')
    origin, c_num, center, rect = read_file(t)
    print(c_num)
    # draw_rectangle(img, origin, c_num)
    # print(origin)

    count = rotation(img, c_num, center, rect, count)

    # cv2.imshow('img', img)

 

MAP 비교 코드

import json

#### json 파일 열기 ####
with open('/home/moon/Alexey/darknet/result.json', 'r') as f:
    json_data = json.load(f)

#### test.txt파일 읽기 ####
jpg = []
with open('/home/moon/darknet/data/test.txt', 'r') as t:
    for line in t:
        jpg.append(line.strip().replace('jpg', 'txt'))
print(jpg)

class_id = [[] for _ in range(len(json_data))]
# print(class_id)


#### json 파일 읽기 ####
for test_number in range(len(json_data)):
    # print("test_number", test_number)
    # print("objects", json_data[test_number]["objects"])
    # for object_number in range(len(json_data[test_number]["objects"])):
    #     print(json_data[test_number]["objects"][object_number])
    for class_id_number in range(len(json_data[test_number]["objects"])):
        class_id[test_number].append(json_data[test_number]["objects"][class_id_number]["class_id"])
        # print(json_data[test_number]["objects"][class_id_number]["class_id"])

answer = [[] for _ in range(len(json_data))]
count = 0
for file in jpg:
    with open(file, 'r') as a:
        for line in a:
            answer[count].append(int(line[0]))
    count += 1
# print(json_data[0]["objects"][0])
# print(len(json_data[0]["objects"]))
# print(json.dumps(json_data, indent="\t") )


print("answer = ", answer)
print("prediction = ", class_id)


total_prediction = 0  # 검출된 class 개수
total_answer = 0  # 실제 class 개수

TP = 0  # 예측 개수 중 맞은 것
FN = 0  # 예측에 포함되지 않은 것
FP = 0  # 예측에는 있으나 실제는 없는 것

Precision = 0  # 검출된 것 중 맞는 것(class / 검출된 것들(json에서 나온 class 개수, number)   =   TP / total_prediction
Recall = 0  # 검출된 것 중 맞는 것 / 실제 개수(jpg.txt파일에 있는 class 개수, number)   =   TP / total_answer


for i in range(len(json_data)):
    print("=======================")
    print(json_data[i]["filename"])
    print(answer[i])
    print(class_id[i])
    total_answer += len(answer[i])
    total_prediction += len(class_id[i])
    tp = 0
    fp = 0
    fn = 0
    for _answer in answer[i]:
        fp = 0
        for _predict in class_id[i]:
            fp = 0
            if _answer == _predict:
                TP += 1  # 총 TP 수
                tp += 1  # 한 개의 img 당 검출된 tp 개수
                fp = len(class_id[i]) - tp  # 틀린 검출
                if len(answer[i]) > len(class_id[i]):
                    fn = len(answer[i]) - (tp + fp)  # 검출되어야 할 것이 검출되지 않았음
                else:
                    fn = 0  # 검출되어야 할 것이 검출되지 않았음
                break
            fp = len(class_id[i]) - tp  # 틀린 검출
        # fn = len(answer[i]) - (tp + fp)  # 검출되어야 할 것이 검출되지 않았음
    print("fp = ", fp)
    FP += fp
    print("tp = ", tp)
    FN += fn
    print("fn = ", fn)

print("=====================")
print("TP = ", TP)
print("FP = ", FP)
print("FN = ", FN)
print("Precision = ", TP/total_prediction)
print("Recall = ", TP/total_answer)

'Project > Project' 카테고리의 다른 글

[Yolo V3] BDD, COCO, KITTI dataset to Yolo  (2) 2020.06.04