R-CNN 논문 리뷰는 지난 포스팅을 참고하길 바란다: https://hyelimkungkung.tistory.com/25
프로젝트 개요
(0) 서론, 데이터 셋 소개: https://hyelimkungkung.tistory.com/27
(1) selective serarch: https://hyelimkungkung.tistory.com/28?category=935193
(2) fine tuning: https://hyelimkungkung.tistory.com/29
(3) linear svm, (4) 결론: https://hyelimkungkung.tistory.com/30
(1) Selective Search
개요는 위와 같다.
(1) 하나의 이미지를 받으면 selective search를 통해 2000개의 후보 영역을 추출한다.
(2) 각각의 후보에 대해 ground truth과의 iou가 0.5 이상이면 positive, 아니라면 negative 샘플로 분류한다.
(3) 배치를 구성한다.
(4) 동일한 input으로 resize 해준다.
이 (1) ~ (4) 는 AlexNet을 finetuning할 때, 그리고 linear SVM을 학습할 때, 그리고 마지막으로 테스트 할때 최종적으로 사용하게 된다. 각각,
finetuning 할때는 iou가 0.5
SVM을 학습할 때는 iou가 0.3
테스트 할때는 iou를 계산하지 않음(정확히 말하면 할 수 없는거다 ground_truth box를 모르니까)
이런 차이가 발생한다.
그러나 여기서 (3)은 일단 제하고 구현한다. 추후에 보완하면 게시글을 수정한다.
(1) region_proposal, (2) resize, (3) pos_neg_region 함수 순으로 보도록 한다.
사용한 라이브러리
from iou import iou
import xml.etree.ElementTree as ET ##xml 파일 읽어주는 라이브러리
import numpy as np
import os
import cv2
# 글로벌 변수로 class dict 생성
classes = {'RBC': '1', "WBC": '2', "Platelets": '3'}
이때, iou는 직접 만든 함수며 실제로 iou.py로 존재한다.
iou.py
## iou 계산
def iou(bb1, bb2):
## 오른쪽 좌표가 왼쪽 좌표보다 커야 하고, 위 좌표가 아래 좌표보다 커야 함 그렇지 않을 경우 asserterror
assert bb1['x1'] < bb1['x2']
assert bb1['y1'] < bb1['y2']
assert bb2['x1'] < bb2['x2']
assert bb2['y1'] < bb2['y2']
## 두개의 bounding box가 겹치는 영역의 좌표
x_left = max(bb1['x1'], bb2['x1'])
x_right = min(bb1['x2'], bb2['x2'])
y_bottom = max(bb1['y1'], bb2['y1'])
y_top = min(bb1['y2'], bb2['y2'])
if x_right < x_left or y_top < y_bottom: return 0
intersection_area = (x_right - x_left) * (y_top - y_bottom)
bb1_area = (bb1['x2'] - bb1['x1']) * (bb1['y2'] - bb1['y1'])
bb2_area = (bb2['x2'] - bb2['x1']) * (bb2['y2'] - bb2['y1'])
iou = intersection_area / (bb1_area + bb2_area - intersection_area)
assert iou <= 1
assert iou >= 0
return iou
@ assert 함수는 사용자가 지정하는 에러 메시지다.
assert (조건문), (조건문이 거짓일 경우 출력할 메세지)
@ x1, y1, x2, y2는 다음과 같이 정의한다.
그렇기에 x2는 x1보다 작아야하며, y2 역시 y1보다 작아야한다. 그렇지 않을 경우 에러를 일으킨다.
@ input인 bb1과 bb2는 딕서녀리의 형태로 {x1: 키값, x2: 키값, y1:키값, y2:키값} 으로 정의한다.
@ 계산은 두 bounding box의 교집합 영역 / bounding box의 합집합 영역 으로 계산한다.
이거는 크게 어려울 것 없을 것 같다.
이제 본격적인 selecrivesearch.py다
노란색 하이라이트는 각각의 함수다
region_proposal
def region_proposal(mode):
ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation() ## selective search initialize
train_images = []
train_labels = []
if mode == 'finetune':
threshold = 0.5
elif mode == 'classify':
threshold = 0.3
elif mode == 'test':
threshold = 0
## xml 파싱해서 정보 읽어들이기
## xml 파싱을 통해서 이미지 당 ground_truth 상자 만듦
for e, i in enumerate(os.listdir('data/content/BCCD/train/annos')):
xml = open("data/content/BCCD/train/annos/" + i, "r")
tree = ET.parse(xml)
root = tree.getroot()
ground_truth = []
size = root.find("size")
objects = root.findall("object")
for object_ in objects:
class_ = object_.find("name").text
bndbox = object_.find("bndbox")
xmin = bndbox.find("xmin").text
xmax = bndbox.find("xmax").text
ymin = bndbox.find("ymin").text
ymax = bndbox.find("ymax").text
ground_truth.append(
({"x1": float(xmin), 'x2': float(xmax), 'y1': float(ymin), 'y2': float(ymax)}, classes[class_]))
path_img = 'data/content/BCCD/train/images/' + i.split('xml')[0] + 'jpg'
image = cv2.imread(path_img)
ss.setBaseImage(image)
ss.switchToSelectiveSearchFast()
ssresults = ss.process()
imgs, labels = pos_neg_region(image, ssresults, ground_truth, threshold)
train_images += imgs
train_labels += labels
return train_images, train_labels
@ 이 함수의 역할
selective search를 통해서 2000개의 영역을 추출한 후 iou 계산을 통해서 labeling을 하고, 마지막으로 resize까지 할 수 있도록 한다. labeling 하는 건 pos_neg_region 함수고, resize는 pos_neg_region내의 resize 함수다. 이렇게 쓰고 보니 진짜 엉성하게 만들었구나 하는 생각이든다. 실은 이렇게 뭐 안에 이 함수가 있고 뭐 안에 저 함수가 있고 이런식으로 파고드는 건 별로 구조상 안 좋은 것 같은데.. 원래 쥬피터로 하던 거를 이렇게 py로 옮기니까 원활하게 되지 않는 것 같다. 추후에 수정할 예정이다.
@ pos_neg_region
보면 pos_neg_region 함수를 볼 수 있는데, 추후에 다시 만나게 된다. iou를 계산하여 positive 혹은 negative 영역인지 판단하는 함수다. 반환하는 값은 resize한 이미지와 그 영역의 클래스 값이다. iou의 값에 따라 negative는 class 0, 나머지는 각각 1,2,3 에 배정된다.
@selective search
자체는 cv의 함수에 있어서 쉽게 할 수 있다. 여기까지 깊게 파고드는거? 그건 너무 무겁다.
다만 이게 매번 다른 결과물을 내는 것 같기에 랜덤 시드를 고정할 수 있다면 하는게 좋을 것 같다.
@ threshold
selective search는 파인튜닝할때, classificatino을 할 때, 그리고 마지막으로 test 할 때 쓰이게 된다. 앞에서 언급했던 것처럼 iou에 차이가 있으므로 위와 같이 mode를 인자로 받아서 다르게 설정하도록 했다.
pos_neg_region
def pos_neg_region(image, ssresults, ground_truth, threshold):
train_images = []
train_labels = []
p_num = 0
n_num = 0
for k, candidates in enumerate(ssresults):
if p_num >30 and n_num >30: break
for box, title in ground_truth:
x, y, w, h = candidates
## cv.imread의 경우 (y, x, 채널) 순으로 차원 구성됨
img = resize(image, x, y, w, h)
# 30 개씩만 일단 하기(원래대로라면 제한 없어야 함)
if iou(box, ({'x1': x, 'x2': x + w, 'y1': y, 'y2': y + h})) > threshold:
if p_num <= 30:
train_images.append(img)
train_labels.append(int(title))
p_num += 1
else:
if n_num <= 30:
train_images.append(img)
train_labels.append(0)
n_num += 1
return train_images, train_labels
@이 함수의 목적
이 함수는 iou를 계산해서 positive인지 negative인지 판단한다.
위에 보면 p_num, n_num이 있는데 총 2000개의 영역을 모두 하는 건 아무래도 어려워서 끼워넣은 부분이다. GPU 사용량에 한계 없는 사람들은 저 부분을 빼면 된다.
resize
## 원본 image에서 iou가 0.5이상인 경우 positive로 판단하고 해당 영역을 cnn의 input 사이즈에 맞게 변형하기
## Appendix A를 참고하여 image context를 고려하지 않고 16pixel씩 패딩하기 16픽셀시 패딩할 수 없을 경우, zerro 패딩
def resize(image, x, y, w, h):
img = image.copy()[max(y - 16, 0):min(image.shape[0], y + h + 16),
max(x - 16, 0):min(x + w + 16, image.shape[1])]
np.pad(img, (
(max(0, 16 - y), max(0, image.shape[1] - y - h)), (max(0, 16 - x), max(0, image.shape[1] - x - w)),
(0, 0)),
mode='constant', constant_values=(0, 0))
img = cv2.resize(img, (224, 224), interpolation=cv2.INTER_CUBIC) # padding 후 resize
return img
@ 이 함수의 목적
Appendix A에 따라 padding하여 resize하기 위해 수정했다.
패딩하려다보니 어쩔 수 없이 image context가 필요해 image를 인자로 받았다. 그 탓에 좀 효율이 별로일 것 같다.
나중에 고민해봐야징
이렇게 이번 selectivesearch.py는 마무리다.
여기까지 궁금해서 한 번 돌려봤다.
원본 및 정답 레이블은 이렇게 생겼다.
예측한 결과를 시각화 하면 다음과 같다.
## positive sample 중 하나
print(train_labels[30147])
plt.imshow(cv2.cvtColor(train_images[30147],cv2.COLOR_BGR2RGB))
다행히도 잘 하는 것 같지 않은가? 일단 한 시름 놨다..
여러 함수로 안 쪼개고 원래대로 내가 했던 코드는 이렇다
import xml.etree.ElementTree as ET ##xml 파일 읽어주는 라이브러리
import numpy as np
import os
import cv2
train_images = []
train_labels = []
## xml 파싱해서 정보 읽어들이기
## xml 파싱을 통해서 이미지 당 ground_truth 상자 만듦
classes = {'RBC':'1', "WBC":'2',"Platelets":'3'}
for e, i in enumerate(os.listdir('/content/BCCD/train/annos')):
if e > 0: break
xml = open("/content/BCCD/train/annos/"+i,"r")
tree = ET.parse(xml)
root = tree.getroot()
ground_truth = []
size = root.find("size")
width = size.find("width").text
height = size.find("height").text
depth = size.find("depth").text
objects = root.findall("object")
for object_ in objects:
class_ = object_.find("name").text
bndbox = object_.find("bndbox")
xmin = bndbox.find("xmin").text
xmax = bndbox.find("xmax").text
ymin = bndbox.find("ymin").text
ymax = bndbox.find("ymax").text
ground_truth.append(({"x1":float(xmin),'x2':float(xmax),'y1':float(ymin),'y2':float(ymax)},classes[class_]))
path_img = '/content/BCCD/train/images/' + i.split('xml')[0] + 'jpg'
image = cv2.imread(path_img)
ss.setBaseImage(image)
ss.switchToSelectiveSearchFast()
ssresults = ss.process()
for e, candidates in enumerate(ssresults):
if e>2000: break
p_num = 0
n_num = 0
for box,title in ground_truth:
x, y, w, h = candidates
## cv.imread의 경우 (y, x, 채널) 순으로 차원 구성됨
## 원본 image에서 iou가 0.5이상인 경우 positive로 판단하고 해당 영역을 cnn의 input 사이즈에 맞게 변형하기
## Appendix A를 참고하여 image context를 고려하지 않고 16pixel씩 패딩하기 16픽셀시 패딩할 수 없을 경우, zerro 패딩
img = image.copy()[max(y-16,0):min(image.shape[0],y+h+16), max(x-16,0):min(x+w+16,image.shape[1])]
np.pad(img,((max(0,16-y),max(0,image.shape[1]-y-h)),(max(0,16-x),max(0,image.shape[1]-x-w)),(0,0)),mode='constant', constant_values=(0,0))
img = cv2.resize(img,(224,224),interpolation = cv2.INTER_CUBIC) #padding 후 resize
#positive sample 32, negative sample은 96개로 해서 샘플링
if iou(box, ({'x1':x, 'x2':x+w, 'y1':y, 'y2':y+h}))>0.5:
if p_num <= 30:
train_images.append(img)
train_labels.append(int(title))
p_num += 1
else:
if n_num <= 30:
train_images.append(img)
train_labels.append(0)
n_num += 1
'딥러닝 > 프로젝트' 카테고리의 다른 글
[Kaggle] CT Medical Image - (0) 서론, 액션 플랜 (0) | 2021.09.25 |
---|---|
[개인]R-CNN 을 이용한 BCCD type 분류 -(3)linear svm, (4)결론 (0) | 2021.06.29 |
[개인]R-CNN 을 이용한 BCCD type 구분 -(2) fine tuning (0) | 2021.06.29 |
[개인]R-CNN 을 이용한 BCCD type 구분 -(0) 서론, 데이터 셋 소개 (0) | 2021.06.22 |
[개인]합성곱 신경망을 통한 이미지 분류 (0) | 2021.04.26 |
댓글