Tensorflow Object Detection API를 사용해서 모델 재학습을 하려면 TFRecord 형태의 input data를 만들어줘야 한다. TFRecord는 "Tensorflow 전용 학습 데이터 저장 형태" 정도로 알아두면 될 것 같다. 제대로 알아보지는 않았지만 Tensorflow로 설계한 모델 학습에 TFRecord를 사용하면 학습 속도가 개선되는 장점이 있다고 한다. 자세하게 알고 싶다면 여기1와 여기2에 들어가서 관련 설명을 읽어보면 될 것 같다.
그리고 이미지를 byte 단위로 읽고 쓰는 법에 대해 모르는 경우라면, [Python] - 이미지 읽는 방법 / cv.imdecode( ), io.BytesIO( ) 을 읽으면 도움이 될 거다.
이제 본격적으로 Custom Dataset을 TFRecord 파일로 만드는 방법에 대해 알아보자. 코드는 여기1, 여기2 그리고 './TF_Object_Detection_API/object_detection/dataset_tools/create_coco_tf_record.py 를 참고해서 작성했다.
핵심부터 설명하자면,
다음 사진과 같이 feature_dict에서 요구하는 데이터들에 맞춰 Custom Dataset에서 뽑아내 넣어주면 된다.
1. Dataset 다운로드
여기1에서 다운받아 정리하거나 여기2에서 다운받아 사용하면 된다. Dataset 폴더는 ./TF_Object_Detection_API 밑에 위치시켜주도록 하자. 이 Dataset은 내시경 영상에서 수술 도구(Surgical Instrument)를 Segmentation하기 위해 만들어진 Dataset이다.
2. image_to_tf_data( ) 함수 설계
import cv2, os, io
import numpy as np
import tensorflow as tf
from PIL import Image
from object_detection.utils import dataset_util
def image_to_tf_data(img_path, mask_path, class_name):
filename = img_path.split('/')[-1]
# image
with tf.gfile.GFile(img_path, 'rb') as fid: # 이미지를 binary mode로 읽음
encoded_img = fid.read()
encoded_img_np = np.fromstring(encoded_img, dtype = np.uint8)
image = cv2.imdecode(encoded_img_np, cv2.IMREAD_COLOR)
height, width, _ = image.shape
# mask
with tf.gfile.GFile(mask_path, 'rb') as fid:
encoded_mask = fid.read()
encoded_mask_np = np.fromstring(encoded_mask, dtype = np.uint8)
mask = cv2.imdecode(encoded_mask_np, cv2.IMREAD_GRAYSCALE) # mask는 1차원으로 사용함
mask_pixel_vals = []
for val in range(1, 256):
if val in mask:
mask_pixel_vals.append(val)
# 필요 데이터들 추출 뒤 저장할 변수들
classes, classes_text = [], []
xmins, ymins, xmaxs, ymaxs = [], [], [], []
encoded_mask_png_list = []
for pixel_val in mask_pixel_vals:
class_idx = 1
# xmin, ymin, xmax, ymax 찾는 과정
check_x_coordi = np.any(mask == pixel_val, axis = 0)
check_y_coordi = np.any(mask == pixel_val, axis = 1)
object_x_coordi = np.where(check_x_coordi)[0]
object_y_coordi = np.where(check_y_coordi)[0]
xmin = min(object_x_coordi)
xmax = max(object_x_coordi)
ymin = min(object_y_coordi)
ymax = max(object_y_coordi)
object_mask = np.uint8(mask == pixel_val) # mask의 최대값은 1 이다
encoded_mask_png = cv2.imencode('.PNG', object_mask)[1].tobytes() # mask는 PNG 확장자로
classes.append(class_idx)
classes_text.append(class_name.encode('utf8'))
xmins.append(xmin / width)
ymins.append(ymin / height)
xmaxs.append(xmax / width)
ymaxs.append(ymax / height)
encoded_mask_png_list.append(encoded_mask_png)
# TFRecord protocol 같은 부분임
# 여기에 맞춰서 데이터 만들어서 넣어주면 됨
feature_dict = {
'image/height': dataset_util.int64_feature(height),
'image/width': dataset_util.int64_feature(width),
'image/filename': dataset_util.bytes_feature(filename.encode('utf8')),
'image/source_id': dataset_util.bytes_feature(filename.encode('utf8')),
'image/key/sha256': dataset_util.bytes_feature(class_name.encode('utf8')),
'image/encoded': dataset_util.bytes_feature(encoded_img),
'image/format': dataset_util.bytes_feature('png'.encode('utf8')),
'image/object/bbox/xmin': dataset_util.float_list_feature(xmins),
'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs),
'image/object/bbox/ymin': dataset_util.float_list_feature(ymins),
'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs),
'image/object/class/text': dataset_util.bytes_list_feature(classes_text),
'image/object/class/label': dataset_util.int64_list_feature(classes),
'image/object/mask': dataset_util.bytes_list_feature(encoded_mask_png_list)}
tf_data = tf.train.Example(features=tf.train.Features(feature=feature_dict))
return tf_data
- Line 10 ~ 21
: 잘 모르겠다면 [Python] - 이미지 읽는 방법 / cv.imdecode( ), io.BytesIO( )을 읽길 바란다. - Line 22 ~ 25
: mask image 내에 객체가 최대 3개까지 있는데 각 객체는 다른 pixel 값으로 표현되어 있다.
객체 별로 데이터를 만들어주기 위해서 작성한 부분이다.
mask_pixel_vals는 Line 31에서 사용된다. - Line 35 ~ 36
: np.any(mask == pixel_val, axis = 0)은 axis = 0 으로 설정됐으므로
123456res = []for i in range(width):if pixel_val in mask[:, i]:res.append(True)else:res.append(False)cs
의 흐름으로 mask == pixel_val 여부를 판단해 결과를 반환해준다.
※ axis = 0은 [ height, width ]로 구성된 mask의 height 차원을 1로 만들어 반환하라는 뜻이기 때문에
1열 벡터로 판단 후 결과 반환, 2열 벡터로 판단 후 결과 반환 … 의 흐름을 갖게 된다. - Line 37 ~ 38
: np.where( )에 bool array가 들어가면 True를 갖는 위치의 index들을 반환해준다.
따라서 np.where(check_x_coordi) 는 check_x_coordi 에 포함된 True의 index를 찾아 반환해준다. - Line 46
: 이 부분 또한 잘 모르겠다면 [Python] - 이미지 읽는 방법 / cv.imdecode( ), io.BytesIO( )을 읽길 바란다. - Line 48 ~ 75
: feature_dict에서 요구하는 형태에 맞춰서 우리가 Custom Dataset에서 뽑아낸 정보들을 넣어주면 된다.
(1) Line 50 ~ 53의 경우, 좌표를 이미지 크기에 상대적으로 넘겨줘야해서 width와 height으로 나눠줬다.
(2) Line 65의 경우, Custom Dataset의 image 형식이 png라서 변경해줬다.
3. TFRecord 생성
image_to_tf_data( ) 함수를 사용해서 뽑은 정보들을 TFRecrodWriter( )를 사용해서 TFRecord 파일로 저장해주면 된다.
base_dir = './Dataset/train/images'
img_names = os.listdir(base_dir)
img_paths = ['{}/{}' .format(base_dir, name) for name in img_names]
tf_writer = tf.python_io.TFRecordWriter('./Dataset/train.record')
for img_path in img_paths:
print(img_path)
mask_path = img_path.replace('/images', '/masks')
mask_path = mask_path.replace('_image', '_mask')
tf_example = image_to_tf_data(img_path, mask_path, 'instrument')
tf_writer.write(tf_example.SerializeToString())
tf_writer.close()
다음은 전체 코드이다.
[ create_tf_record.py ]
import cv2, os, io
import numpy as np
import tensorflow as tf
from PIL import Image
from object_detection.utils import dataset_util
def image_to_tf_data(img_path, mask_path, class_name):
filename = img_path.split('/')[-1]
# image
with tf.gfile.GFile(img_path, 'rb') as fid: # 이미지를 binary mode로 읽음
encoded_img = fid.read()
encoded_img_np = np.fromstring(encoded_img, dtype = np.uint8)
image = cv2.imdecode(encoded_img_np, cv2.IMREAD_COLOR)
height, width, _ = image.shape
# mask
with tf.gfile.GFile(mask_path, 'rb') as fid:
encoded_mask = fid.read()
encoded_mask_np = np.fromstring(encoded_mask, dtype = np.uint8)
mask = cv2.imdecode(encoded_mask_np, cv2.IMREAD_GRAYSCALE) # mask는 1차원으로 사용함
mask_pixel_vals = []
for val in range(1, 256):
if val in mask:
mask_pixel_vals.append(val)
# 필요 데이터들 추출 뒤 저장할 변수들
classes, classes_text = [], []
xmins, ymins, xmaxs, ymaxs = [], [], [], []
encoded_mask_png_list = []
for pixel_val in mask_pixel_vals:
class_idx = 1
# xmin, ymin, xmax, ymax 찾는 과정
check_x_coordi = np.any(mask == pixel_val, axis = 0)
check_y_coordi = np.any(mask == pixel_val, axis = 1)
object_x_coordi = np.where(check_x_coordi)[0]
object_y_coordi = np.where(check_y_coordi)[0]
xmin = min(object_x_coordi)
xmax = max(object_x_coordi)
ymin = min(object_y_coordi)
ymax = max(object_y_coordi)
object_mask = np.uint8(mask == pixel_val) # mask의 최대값은 1 이다
encoded_mask_png = cv2.imencode('.PNG', object_mask)[1].tobytes() # mask는 PNG 확장자로 저장해줘야하는 듯
classes.append(class_idx)
classes_text.append(class_name.encode('utf8'))
xmins.append(xmin / width)
ymins.append(ymin / height)
xmaxs.append(xmax / width)
ymaxs.append(ymax / height)
encoded_mask_png_list.append(encoded_mask_png)
# TFRecord protocol 같은 부분임
# 여기에 맞춰서 데이터 만들어서 넣어주면 됨
feature_dict = {
'image/height': dataset_util.int64_feature(height),
'image/width': dataset_util.int64_feature(width),
'image/filename': dataset_util.bytes_feature(filename.encode('utf8')),
'image/source_id': dataset_util.bytes_feature(filename.encode('utf8')),
'image/key/sha256': dataset_util.bytes_feature(class_name.encode('utf8')),
'image/encoded': dataset_util.bytes_feature(encoded_img),
'image/format': dataset_util.bytes_feature('png'.encode('utf8')),
'image/object/bbox/xmin': dataset_util.float_list_feature(xmins),
'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs),
'image/object/bbox/ymin': dataset_util.float_list_feature(ymins),
'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs),
'image/object/class/text': dataset_util.bytes_list_feature(classes_text),
'image/object/class/label': dataset_util.int64_list_feature(classes),
'image/object/mask': dataset_util.bytes_list_feature(encoded_mask_png_list)}
tf_data = tf.train.Example(features=tf.train.Features(feature=feature_dict))
return tf_data
base_dir = './Dataset/train/images'
img_names = os.listdir(base_dir)
img_paths = ['{}/{}' .format(base_dir, name) for name in img_names]
tf_writer = tf.python_io.TFRecordWriter('./Dataset/train.record')
for img_path in img_paths:
print(img_path)
mask_path = img_path.replace('/images', '/masks')
mask_path = mask_path.replace('_image', '_mask')
tf_example = image_to_tf_data(img_path, mask_path, 'instrument')
tf_writer.write(tf_example.SerializeToString())
tf_writer.close()
'Deep Learning > TF Object Detection API' 카테고리의 다른 글
2.2. Mask R-CNN을 Retrain 시켜보자 (6) | 2020.03.30 |
---|---|
2. Tensorflow Object Detection Model Retrain 방법에 대해 알아보자 (0) | 2020.03.13 |
1.5. draw_bounding_boxes 함수 설계 및 inference 결과 확인 (5) | 2019.07.30 |
1.4. run_inference_for_single_image 함수 설계 (0) | 2019.07.05 |
1.3. Pretrained model의 계산 그래프 로드 (0) | 2019.07.03 |