Generate Image
이탤릭 볼드 이탤릭볼드
Workflow stages
- Question or problem definition.
- Acquire training and testing data.
- Wrangle, prepare, cleanse the data.
- Analyze, identify patterns, and explore the data.
- Model, predict and solve the problem.
- Visualize, report, and present the problem solving steps and final solution.
- Supply or submit the results.
기본적으로 설치되어 있어야하는 패키지는 아래 코드
를 사용한다.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import keras.layers as layers
import keras.optimizers as optimizers
from keras.models import Model, load_model
from keras.utils import to_categorical
from keras.callbacks import LambdaCallback, ModelCheckpoint, ReduceLROnPlateau
import seaborn as sns
from PIL import Image
from skimage.transform import resize
import threading, random, os
data 가져오기
imgs = np.load('dataset/imgs_uint8.npy').astype(np.float32) / 255.
labels = np.load('dataset/labels_uint8.npy').astype(np.float32) / 255.
waldo_sub_imgs = np.load('dataset/waldo_sub_imgs_uint8.npy') / 255.
waldo_sub_labels = np.load('dataset/waldo_sub_labels_uint8.npy') / 255.
Data Generator
랜덤으로 이미지를 생성해서 동적으로 배치를 생성
PANNEL_SIZE = 224
class BatchIndices(object):
"""
Generates batches of shuffled indices.
# Arguments
n: number of indices
bs: batch size
shuffle: whether to shuffle indices, default False
"""
def __init__(self, n, bs, shuffle=False):
self.n,self.bs,self.shuffle = n,bs,shuffle
self.lock = threading.Lock()
self.reset()
def reset(self):
self.idxs = (np.random.permutation(self.n)
if self.shuffle else np.arange(0, self.n))
self.curr = 0
def __next__(self):
with self.lock:
if self.curr >= self.n: self.reset()
ni = min(self.bs, self.n-self.curr)
res = self.idxs[self.curr:self.curr+ni]
self.curr += ni
return res
class segm_generator(object):
"""
Generates batches of sub-images.
# Arguments
x: array of inputs
y: array of targets
bs: batch size
out_sz: dimension of sub-image
train: If true, will shuffle/randomize sub-images
waldo: If true, allow sub-images to contain targets.
"""
def __init__(self, x, y, bs=64, out_sz=(224,224), train=True, waldo=True):
self.x, self.y, self.bs, self.train = x,y,bs,train
self.waldo = waldo
self.n = x.shape[0]
self.ri, self.ci = [], []
for i in range(self.n):
ri, ci, _ = x[i].shape
self.ri.append(ri), self.ci.append(ci)
self.idx_gen = BatchIndices(self.n, bs, train)
self.ro, self.co = out_sz
self.ych = self.y.shape[-1] if len(y.shape)==4 else 1
def get_slice(self, i,o): # 랜덤으로 이미지를 자름
start = random.randint(0, i-o) if self.train else (i-o)
return slice(start, start+o)
def get_item(self, idx): # 이미지를 랜덤으로 자르고 확률적으로 좌우를 뒤집음
slice_r = self.get_slice(self.ri[idx], self.ro)
slice_c = self.get_slice(self.ci[idx], self.co)
x = self.x[idx][slice_r, slice_c]
y = self.y[idx][slice_r, slice_c]
if self.train and (random.random()>0.5): # 50 % 로 좌우로 뒤집음
y = y[:,::-1]
x = x[:,::-1]
if not self.waldo and np.sum(y)!=0:
return None
return x, to_categorical(y, num_classes=2).reshape((y.shape[0] * y.shape[1], 2))
def __next__(self):
idxs = self.idx_gen.__next__()
items = []
for idx in idxs:
item = self.get_item(idx)
if item is not None:
items.append(item)
if not items:
return None
xs,ys = zip(*tuple(items))
return np.stack(xs), np.stack(ys)
def seg_gen_mix(x1, y1, x2, y2, tot_bs=4, prop=0.34, out_sz=(224,224), train=True):
"""
Mixes generator output. The second generator is set to skip images that contain any positive targets.
# Arguments
x1, y1: input/targets for waldo sub-images
x2, y2: input/targets for whole images
tot_bs: total batch size
prop: proportion of total batch size consisting of first generator output
"""
n1 = int(tot_bs*prop)
n2 = tot_bs - n1
sg1 = segm_generator(x1, y1, n1, out_sz = out_sz ,train=train)
sg2 = segm_generator(x2, y2, n2, out_sz = out_sz ,train=train, waldo=False)
while True:
out1 = sg1.__next__()
out2 = sg2.__next__()
if out2 is None:
yield out1
else:
yield np.concatenate((out1[0], out2[0])), np.concatenate((out1[1], out2[1]))
Preview Sample Pannel Images
# waldo : not_waldo = 1 : 2 (0.34)
gen_mix = seg_gen_mix(waldo_sub_imgs, waldo_sub_labels, imgs, labels, tot_bs=4, prop=0.34, out_sz=(PANNEL_SIZE, PANNEL_SIZE))
X, y = next(gen_mix)
plt.figure(figsize=(5, 10))
for i, img in enumerate(X):
plt.subplot(X.shape[0], 2, 2*i+1)
plt.imshow(X[i])
plt.subplot(X.shape[0], 2, 2*i+2)
plt.colorbar()
plt.imshow(y[i][:,1].reshape((PANNEL_SIZE, PANNEL_SIZE)))
Plot Y-Data Distribution
Too many 0 values, so we make class weight to control biased(skewed) sample. 편향된 데이터 셋으로 학습을 시켜야함 See https://keras.io/models/sequential/ class_weight in fit_generator() section
freq0 = np.sum(labels==0)
freq1 = np.sum(labels==1)
print(freq0, freq1)
sns.distplot(labels.flatten(), kde=False, hist_kws={'log':True})
Make Class Weights (0 and 1)
위의 불균형 문제를 해결하기위해 만듬.
sample_weights = np.zeros((6, PANNEL_SIZE * PANNEL_SIZE, 2))
sample_weights[:,:,0] = 1. / freq0
sample_weights[:,:,1] = 1.
plt.subplot(1,2,1)
plt.imshow(sample_weights[0,:,0].reshape((224, 224)))
plt.colorbar()
plt.subplot(1,2,2)
plt.imshow(sample_weights[0,:,1].reshape((224, 224)))
plt.colorbar()
모델 생성
inputs = layers.Input(shape=(PANNEL_SIZE, PANNEL_SIZE, 3)) # 224 , 224 , 3
net = layers.Conv2D(64, kernel_size=3, padding='same')(inputs)
# net = layers.Activation('relu')(net)
net = layers.LeakyReLU()(net)
net = layers.MaxPool2D(pool_size=2)(net) # 차원 축소
shortcut_1 = net
net = layers.Conv2D(128, kernel_size=3, padding='same')(net)
# net = layers.Activation('relu')(net)
net = layers.LeakyReLU()(net)
net = layers.MaxPool2D(pool_size=2)(net) # 차원 축소
shortcut_2 = net
net = layers.Conv2D(256, kernel_size=3, padding='same')(net)
# net = layers.Activation('relu')(net)
net = layers.LeakyReLU()(net)
net = layers.MaxPool2D(pool_size=2)(net) # 차원 축소
shortcut_3 = net
net = layers.Conv2D(256, kernel_size=1, padding='same')(net)
# net = layers.Activation('relu')(net)
net = layers.LeakyReLU()(net)
net = layers.MaxPool2D(pool_size=2)(net) # 차원 축소
net = layers.UpSampling2D(size=2)(net) # 차원 증가
net = layers.Conv2D(256, kernel_size=3, padding='same')(net)
net = layers.Activation('relu')(net)
net = layers.Add()([net, shortcut_3])
net = layers.UpSampling2D(size=2)(net) # 차원 증가
net = layers.Conv2D(128, kernel_size=3, padding='same')(net)
net = layers.Activation('relu')(net)
net = layers.Add()([net, shortcut_2])
net = layers.UpSampling2D(size=2)(net) # 차원 증가
net = layers.Conv2D(64, kernel_size=3, padding='same')(net)
net = layers.Activation('relu')(net)
net = layers.Add()([net, shortcut_1])
net = layers.UpSampling2D(size=2)(net) # 차원 증가
net = layers.Conv2D(2, kernel_size=1, padding='same')(net) # 2채널로 변경
net = layers.Reshape((-1, 2))(net)
net = layers.Activation('softmax')(net) # 2개의 차원을 하나로 합침
model = Model(inputs=inputs, outputs=net)
model.compile(
loss='categorical_crossentropy',
optimizer=optimizers.Adam(),
metrics=['acc'],
sample_weight_mode='temporal' # class weight를 사용하기 위해 temporal로 지정
)
model.summary()
Train
gen_mix = seg_gen_mix(waldo_sub_imgs, waldo_sub_labels, imgs, labels, tot_bs=6, prop=0.34, out_sz=(PANNEL_SIZE, PANNEL_SIZE))
def on_epoch_end(epoch, logs):
print('\r', 'Epoch:%5d - loss: %.4f - acc: %.4f' % (epoch, logs['loss'], logs['acc']), end='')
print_callback = LambdaCallback(on_epoch_end=on_epoch_end) # 알림이 5줄 이내로 나오게끔함
history = model.fit_generator(
gen_mix, steps_per_epoch=6, epochs=500,
class_weight=sample_weights,
verbose=0,
callbacks=[
print_callback,
ReduceLROnPlateau(monitor='loss', factor=0.2, patience=100, verbose=1, mode='auto', min_lr=1e-05)
]
)
model.save('model.h5')
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.title('loss')
plt.plot(history.history['loss'])
plt.subplot(1, 2, 2)
plt.title('accuracy')
plt.plot(history.history['acc'])
Evaluation
img_filename = '02.jpg'
test_img = np.array(Image.open(os.path.join('test_imgs', img_filename)).resize((2800, 1760), Image.NEAREST)).astype(np.float32) / 255.
plt.figure(figsize=(20, 10))
plt.imshow(test_img)
Helper Functions (Resize, Split, Combine Pannels)
def img_resize(img): # 이미지를 균등하게 자를 수 있도록 리사이징
h, w, _ = img.shape
nvpanels = int(h/PANNEL_SIZE)
nhpanels = int(w/PANNEL_SIZE)
new_h, new_w = h, w
if nvpanels*PANNEL_SIZE != h:
new_h = (nvpanels+1)*PANNEL_SIZE
if nhpanels*PANNEL_SIZE != w:
new_w = (nhpanels+1)*PANNEL_SIZE
if new_h == h and new_w == w:
return img
else:
return resize(img, output_shape=(new_h, new_w), preserve_range=True)
def split_panels(img): # 자른 이미지를 배치로 넣는 함수
h, w, _ = img.shape
num_vert_panels = int(h/PANNEL_SIZE)
num_hor_panels = int(w/PANNEL_SIZE)
panels = []
for i in range(num_vert_panels):
for j in range(num_hor_panels):
panels.append(img[i*PANNEL_SIZE:(i+1)*PANNEL_SIZE,j*PANNEL_SIZE:(j+1)*PANNEL_SIZE])
return np.stack(panels)
def combine_panels(img, panels): # 쪼개진 이미지를 합치는 함수
h, w, _ = img.shape
num_vert_panels = int(h/PANNEL_SIZE)
num_hor_panels = int(w/PANNEL_SIZE)
total = []
p = 0
for i in range(num_vert_panels):
row = []
for j in range(num_hor_panels):
row.append(panels[p])
p += 1
total.append(np.concatenate(row, axis=1))
return np.concatenate(total, axis=0)
Preprocess Image
test_img = img_resize(test_img)
panels = split_panels(test_img)
out = combine_panels(test_img, panels)
print(panels.shape, test_img.shape, out.shape)
Predict
model = load_model('model.h5')
pred_panels = model.predict(panels).reshape((-1, PANNEL_SIZE, PANNEL_SIZE, 2))[:, :, :, 1] # 1번 채널만 사용
pred_out = combine_panels(test_img, pred_panels)
# compute coordinates and confidence
argmax_x = np.argmax(np.max(pred_out, axis=0), axis=0)
argmax_y = np.argmax(np.max(pred_out, axis=1), axis=0)
confidence = np.amax(pred_out) * 100
print('(%s, %s) %.2f%%' % (argmax_x, argmax_y, confidence))
plt.figure(figsize=(20, 10))
plt.imshow(pred_out)
plt.colorbar()
Make Overlay for Result
def bbox_from_mask(img):
rows = np.any(img, axis=1)
cols = np.any(img, axis=0)
y1, y2 = np.where(rows)[0][[0, -1]]
x1, x2 = np.where(cols)[0][[0, -1]]
return x1, y1, x2, y2
x1, y1, x2, y2 = bbox_from_mask((pred_out > 0.8).astype(np.uint8))
print(x1, y1, x2, y2)
# make overlay
overlay = np.repeat(np.expand_dims(np.zeros_like(pred_out, dtype=np.uint8), axis=-1), 3, axis=-1)
alpha = np.expand_dims(np.full_like(pred_out, 255, dtype=np.uint8), axis=-1)
overlay = np.concatenate([overlay, alpha], axis=-1)
overlay[y1:y2, x1:x2, 3] = 0
plt.figure(figsize=(20, 10))
plt.imshow(overlay)
Final Result
fig, ax = plt.subplots(figsize=(20, 10))
ax.imshow(test_img)
ax.imshow(overlay, alpha=0.5)
rect = patches.Rectangle((x1, y1), width=x2-x1, height=y2-y1, linewidth=1.5, edgecolor='r', facecolor='none')
ax.add_patch(rect)
ax.set_axis_off()
fig.savefig(os.path.join('test_result', img_filename), bbox_inches='tight')