Skip to content

Commit 4b0c4f4

Browse files
committed
detector関係の追加
1 parent 3a69cbb commit 4b0c4f4

File tree

10 files changed

+2045
-0
lines changed

10 files changed

+2045
-0
lines changed

.dockerignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Dockerfile
2+
README.md
3+
*.pyc
4+
*.pyo
5+
*.pyd
6+
__pycache__

mahjong_sample_web_app/detector/__init__.py

Whitespace-only changes.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from pathlib import Path
2+
import numpy as np
3+
4+
# from scipy.misc import imresize
5+
from PIL import Image as pil_image
6+
from keras.preprocessing import image
7+
from .postprocess import PostProcess
8+
from .ssd.ssd import SingleShotMultiBoxDetector
9+
10+
11+
model_file = Path(__file__).parent / "models" / "weights.25-0.05.hdf5"
12+
param_file = (
13+
Path(__file__).parent / "models" / "ssd300_params_mahjong_vgg16_train_2.json"
14+
)
15+
16+
17+
def model_build(model_file, param_file):
18+
ssd = SingleShotMultiBoxDetector(
19+
overlap_threshold=0.5, nms_threshold=0.45, max_output_size=400
20+
)
21+
ssd.load_parameters(param_file)
22+
ssd.build(init_weight=model_file)
23+
return ssd
24+
25+
26+
def _add_margin(img):
27+
img_shape = list(img.shape)
28+
if img_shape[0] == img_shape[1]:
29+
return img
30+
if img_shape[0] < img_shape[1]:
31+
min_arg = 0
32+
max_arg = 1
33+
else:
34+
min_arg = 1
35+
max_arg = 0
36+
margin_shape = img_shape
37+
margin_shape[min_arg] = int((img_shape[max_arg] - img_shape[min_arg]) / 2.0)
38+
margin = np.tile([0.0], margin_shape)
39+
new_img = np.concatenate([margin, img], axis=min_arg)
40+
new_img = np.concatenate([new_img, margin], axis=min_arg)
41+
return new_img
42+
43+
44+
def pred(ssd, img):
45+
inputs = np.array([img.copy()])
46+
results = ssd.detect(inputs, batch_size=1, verbose=1, do_preprocess=True)
47+
return results
48+
49+
50+
def load_image(img_obj, input_shape=(512, 512)):
51+
# img = image.load_img(img_path)
52+
img = pil_image.open(img_obj)
53+
if img.mode != "RGB":
54+
img = img.convert("RGB")
55+
56+
img_array = image.img_to_array(img)
57+
new_img = _add_margin(img_array)
58+
new_img_float = np.array(
59+
pil_image.fromarray(new_img.astype("uint8")).resize(
60+
size=input_shape # , resample=pil_image.BICUBIC
61+
)
62+
).astype("float32")
63+
# new_img_float = imresize(new_img, input_shape).astype("float32")
64+
return new_img_float
65+
66+
67+
def detect(img_obj):
68+
img = load_image(img_obj)
69+
ssd = model_build(model_file, param_file)
70+
pred_result = pred(ssd, img)
71+
72+
pp = PostProcess(ssd.class_names, pred_threshold=0.9)
73+
pp.set_top_score(pred_result)
74+
list_label = pp.get_list_pi()
75+
# pp.save_image(img, pred_result, savepath)
76+
# print(list_label)
77+
return list_label
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# import matplotlib.pyplot as plt
2+
import numpy as np
3+
4+
5+
class PostProcess:
6+
def __init__(self, class_names, pred_threshold=0.9):
7+
self.pred_threshold = pred_threshold
8+
self.class_names = class_names
9+
10+
def set_top_score(self, pred_result):
11+
# Parse the outputs.
12+
det_label = pred_result[0][:, 0]
13+
det_conf = pred_result[0][:, 1]
14+
15+
# get top score result
16+
self.top_indices = [
17+
i for i, conf in enumerate(det_conf) if conf >= self.pred_threshold
18+
]
19+
self.top_conf = det_conf[self.top_indices]
20+
self.top_label_indices = det_label[self.top_indices].tolist()
21+
22+
def get_list_pi(self):
23+
list_label = []
24+
for i in range(self.top_conf.shape[0]):
25+
label = int(self.top_label_indices[i])
26+
label_name = self.class_names[label]
27+
list_label.append(label_name)
28+
29+
return list_label
30+
31+
# def save_image(self, img, pred_result, savepath):
32+
# colors = plt.cm.hsv(np.linspace(0, 1, 35)).tolist()
33+
# plt.tick_params(
34+
# labelbottom=False, labelleft=False, labelright=False, labeltop=False
35+
# )
36+
# plt.tick_params(bottom=False, left=False, right=False, top=False)
37+
38+
# plt.imshow(img / 255.0)
39+
# currentAxis = plt.gca()
40+
41+
# det_xmin = pred_result[0][:, 2]
42+
# det_ymin = pred_result[0][:, 3]
43+
# det_xmax = pred_result[0][:, 4]
44+
# det_ymax = pred_result[0][:, 5]
45+
46+
# top_xmin = det_xmin[self.top_indices]
47+
# top_ymin = det_ymin[self.top_indices]
48+
# top_xmax = det_xmax[self.top_indices]
49+
# top_ymax = det_ymax[self.top_indices]
50+
51+
# for i in range(self.top_conf.shape[0]):
52+
# xmin = int(round(top_xmin[i] * img.shape[1]))
53+
# ymin = int(round(top_ymin[i] * img.shape[0]))
54+
# xmax = int(round(top_xmax[i] * img.shape[1]))
55+
# ymax = int(round(top_ymax[i] * img.shape[0]))
56+
57+
# label = int(self.top_label_indices[i])
58+
# score = self.top_conf[i]
59+
# label_name = self.class_names[label]
60+
# display_txt = "{:0.2f}, {}".format(score, label_name)
61+
# coords = (xmin, ymin), xmax - xmin + 1, ymax - ymin + 1
62+
# color = colors[label]
63+
64+
# currentAxis.add_patch(
65+
# plt.Rectangle(*coords, fill=False, edgecolor=color, linewidth=2)
66+
# )
67+
# currentAxis.text(
68+
# xmin, ymin, display_txt, bbox={"facecolor": color, "alpha": 1.0}
69+
# )
70+
71+
# plt.savefig(savepath)

mahjong_sample_web_app/detector/ssd/__init__.py

Whitespace-only changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import numpy as np
2+
import keras.backend as K
3+
from keras.engine.topology import Layer
4+
from keras.engine.topology import InputSpec
5+
6+
7+
class L2Normalization(Layer):
8+
"""
9+
"""
10+
11+
def __init__(self, scale, **kwargs):
12+
self.scale = scale
13+
self.gamma = None
14+
self.axis = None
15+
# if K.image_dim_ordering() == "tf":
16+
if K.image_data_format() == "tf":
17+
self.axis = 3
18+
else:
19+
self.axis = 1
20+
super(L2Normalization, self).__init__(**kwargs)
21+
22+
def build(self, input_shape):
23+
self.input_spec = [InputSpec(shape=input_shape)]
24+
shape = (input_shape[self.axis],)
25+
self.gamma = K.variable(
26+
self.scale * np.ones(shape), name="{}_gamma".format(self.name)
27+
)
28+
self.trainable_weights = [self.gamma]
29+
30+
def call(self, x, mask=None):
31+
output = K.l2_normalize(x, self.axis)
32+
output *= self.gamma
33+
return output
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import tensorflow as tf
2+
from keras.losses import categorical_crossentropy
3+
4+
5+
class MultiBoxLoss:
6+
"""
7+
"""
8+
def __init__(self, n_classes, alpha=1.0, neg_pos_ratio=3.0,
9+
negatives_for_hard=100):
10+
self.n_classes = n_classes
11+
self.alpha = alpha
12+
self.neg_pos_ratio = neg_pos_ratio
13+
self.negatives_for_hard = negatives_for_hard
14+
15+
def _softmax_loss(self, y_true, y_pred):
16+
"""
17+
"""
18+
softmax_loss = categorical_crossentropy(y_true, y_pred)
19+
# y_pred = tf.maximum(tf.minimum(y_pred, 1 - 1e-15), 1e-15)
20+
# softmax_loss = -tf.reduce_sum(y_true * tf.log(y_pred), axis=-1)
21+
return softmax_loss
22+
23+
def _l1_smooth_loss(self, y_true, y_pred):
24+
"""
25+
"""
26+
abs_loss = tf.abs(y_true - y_pred)
27+
sq_loss = 0.5 * (y_true - y_pred)**2
28+
l1_loss = tf.where(tf.less(abs_loss, 1.0), sq_loss, abs_loss - 0.5)
29+
return tf.reduce_sum(l1_loss, -1)
30+
31+
def compute_loss_old(self, y_true, y_pred):
32+
""" compute loss
33+
"""
34+
batch_size = tf.shape(y_true)[0]
35+
num_boxes = tf.to_float(tf.shape(y_true)[1])
36+
37+
# loss for all default boxes
38+
conf_loss = self._softmax_loss(y_true[:, :, 4:],
39+
y_pred[:, :, 4:])
40+
loc_loss = self._l1_smooth_loss(y_true[:, :, :4],
41+
y_pred[:, :, :4])
42+
43+
# positives loss
44+
num_pos = num_boxes - tf.reduce_sum(y_true[:, :, 4], axis=-1)
45+
fpmask = 1 - y_true[:, :, 4]
46+
pos_loc_loss = tf.reduce_sum(loc_loss * fpmask, axis=1)
47+
pos_conf_loss = tf.reduce_sum(conf_loss * fpmask, axis=1)
48+
49+
# negatives loss
50+
num_neg = tf.minimum(self.neg_pos_ratio * num_pos,
51+
num_boxes - num_pos)
52+
pos_num_neg_mask = tf.greater(num_neg, 0)
53+
has_min = tf.to_float(tf.reduce_any(pos_num_neg_mask))
54+
num_neg = tf.concat(axis=0,
55+
values=[num_neg,
56+
[(1 - has_min) * self.negatives_for_hard]])
57+
num_neg_batch = tf.reduce_min(tf.boolean_mask(num_neg,
58+
tf.greater(num_neg, 0)))
59+
num_neg_batch = tf.to_int32(num_neg_batch)
60+
confs_start = 4 + 1
61+
confs_end = confs_start + self.n_classes - 1
62+
max_confs = tf.reduce_max(y_pred[:, :, confs_start:confs_end],
63+
axis=2)
64+
65+
nvalues, indices = tf.nn.top_k(max_confs * y_true[:, :, 4],
66+
k=num_neg_batch)
67+
68+
batch_idx = tf.expand_dims(tf.range(0, batch_size), 1)
69+
batch_idx = tf.tile(batch_idx, (1, num_neg_batch))
70+
full_indices = (tf.reshape(batch_idx, [-1]) * tf.to_int32(num_boxes) +
71+
tf.reshape(indices, [-1]))
72+
73+
neg_conf_loss = tf.gather(tf.reshape(conf_loss, [-1]),
74+
full_indices)
75+
neg_conf_loss = tf.reshape(neg_conf_loss,
76+
[batch_size, num_neg_batch])
77+
neg_conf_loss = tf.reduce_sum(neg_conf_loss, axis=1)
78+
79+
# loss is sum of positives and negatives
80+
total_loss = pos_conf_loss + neg_conf_loss
81+
total_loss /= (num_pos + tf.to_float(num_neg_batch))
82+
num_pos = tf.where(tf.not_equal(num_pos, 0), num_pos,
83+
tf.ones_like(num_pos))
84+
total_loss += (self.alpha * pos_loc_loss) / num_pos
85+
return total_loss
86+
87+
def compute_loss(self, y_true, y_pred):
88+
""" compute loss
89+
"""
90+
batch_size = tf.shape(y_true)[0]
91+
num_boxes = tf.to_float(tf.shape(y_true)[1])
92+
93+
# loss for all default boxes
94+
conf_loss = self._softmax_loss(y_true[:, :, 4:],
95+
y_pred[:, :, 4:])
96+
loc_loss = self._l1_smooth_loss(y_true[:, :, :4],
97+
y_pred[:, :, :4])
98+
99+
# positives loss
100+
num_pos = num_boxes - tf.reduce_sum(y_true[:, :, 4], axis=-1)
101+
fpmask = 1 - y_true[:, :, 4]
102+
pos_loc_loss = tf.reduce_sum(loc_loss * fpmask, axis=1)
103+
pos_conf_loss = tf.reduce_sum(conf_loss * fpmask, axis=1)
104+
105+
# negatives loss
106+
num_neg = tf.minimum(self.neg_pos_ratio * num_pos,
107+
num_boxes - num_pos)
108+
pos_num_neg_mask = tf.greater(num_neg, 0)
109+
has_min = tf.to_float(tf.reduce_any(pos_num_neg_mask))
110+
num_neg = tf.concat(axis=0,
111+
values=[num_neg,
112+
[(1 - has_min) * self.negatives_for_hard]])
113+
num_neg_batch = tf.reduce_min(tf.boolean_mask(num_neg,
114+
tf.greater(num_neg, 0)))
115+
num_neg_batch = tf.to_int32(num_neg_batch)
116+
confs_start = 4 + 1
117+
confs_end = confs_start + self.n_classes - 1
118+
max_confs = tf.reduce_max(y_pred[:, :, confs_start:confs_end],
119+
axis=2)
120+
121+
nvalues, indices = tf.nn.top_k(max_confs * y_true[:, :, 4],
122+
k=num_neg_batch)
123+
min_nvalues = nvalues[:, -1]
124+
min_nvalues = tf.expand_dims(min_nvalues, 1)
125+
min_nvalues = tf.tile(min_nvalues, (1, tf.shape(max_confs)[1]))
126+
nmask = tf.logical_not(tf.cast(fpmask, tf.bool))
127+
nmask = tf.logical_and(nmask,
128+
tf.greater_equal(max_confs, min_nvalues))
129+
fnmask = tf.to_float(nmask)
130+
131+
neg_conf_loss = tf.reduce_sum(conf_loss * fnmask, axis=1)
132+
133+
# loss is sum of positives and negatives
134+
total_loss = pos_conf_loss + neg_conf_loss
135+
total_loss /= (num_pos + tf.to_float(num_neg_batch))
136+
num_pos = tf.where(tf.not_equal(num_pos, 0), num_pos,
137+
tf.ones_like(num_pos))
138+
total_loss += (self.alpha * pos_loc_loss) / num_pos
139+
return total_loss

0 commit comments

Comments
 (0)