CNN でランドマーク検出
前回の「CNNで輪郭の検出」 で試した手法を工夫し、ランドマーク(特徴点)検出へ適用してみました。
- Keras + Tensorflow
- Jupyter Notebook
ソースは http://github.com/fits/try_samples/tree/master/blog/20190217/
輪郭の検出では画像をピクセル単位で二値分類(輪郭以外 = 0, 輪郭 = 1)しましたが、今回はこれを多クラス分類(ランドマーク以外 = 0, ランドマーク1 = 1, ランドマーク2 = 2, ・・・)へ変更します。
ちなみに、Deeplearning でランドマーク検出を行うような場合、ランドマークの座標を直接予測するような手法が考えられますが、今回試してみた限りでは納得のいく結果(座標の精度や汎用性など)を出せなくて、代わりに思いついたのが今回の手法となっています。
はじめに
データセット
今回は、DeepFashion: In-shop Clothes Retrieval のランドマーク用データセットから以下の条件を満たすものに限定して使います。
- clothes_type の値が 1 (upper-body clothes)
- variation_type の値が 1 (normal pose)
- landmark_visibility_1 ~ 6 の値が 0(visible)
ランドマークには 6種類 (landmark_location_x_1 ~ 6、landmark_location_y_1 ~ 6) の座標を使います。
教師データ
入力データには画像を使うため、データ形状は (<バッチサイズ>, 256, 256, 3)
※ となります。
※ (<バッチサイズ>, <高さ>, <幅>, <チャンネル数>)
ラベルデータは landmark_location 1 ~ 6 の値を元に動的に生成します。
ピクセル単位でランドマーク以外(= 0)とランドマーク 1 ~ 6 の多クラス分類を行うため、データ形状は (<バッチサイズ>, 256, 256, 7)
とします。
ランドマーク毎に 1ピクセルだけランドマークへ分類しても上手く学習できないので ※、一定の大きさ(範囲)をランドマークへ分類する必要があります。
※ 全てをランドマーク以外(= 0)とするようになってしまう
そこで、ランドマーク周辺の一定範囲をランドマークへ分類するとともに、以下の図(中心がランドマーク)のようにランドマークから離れると確率値が下がるように工夫します。
学習
学習処理は Jupyter Notebook 上で実行しました。
(1) 入力データの準備
まずは、list_landmarks_inshop.txt
ファイルを読み込んで必要なデータを抜き出します。
今回は学習時間の短縮のため、先頭から 100件だけを使用しています。
データ読み込みとフィルタリング
import pandas as pd df = pd.read_table('list_landmarks_inshop.txt', sep = '\s+', skiprows = 1) s = 100 dfa = df[(df['clothes_type'] == 1) & (df['variation_type'] == 1) & (df['landmark_visibility_1'] == 0) & (df['landmark_visibility_2'] == 0) & (df['landmark_visibility_3'] == 0) & (df['landmark_visibility_4'] == 0) & (df['landmark_visibility_5'] == 0) & (df['landmark_visibility_6'] == 0)][:s]
次に、入力データとして使う画像を読み込みます。
入力データ(画像)読み込み
import numpy as np from keras.preprocessing.image import load_img, img_to_array imgs = np.array([ img_to_array(load_img(f)) for f in dfa['image_name']])
入力データの形状は以下のようになります。
imgs.shape
(100, 256, 256, 3)
(2) ラベルデータの生成
先述したように landmark_location の値から得られたランドマーク座標の周辺に確率値を設定していきます。
ここでは、確率の構成内容や確率値の設定対象とする周辺座標の取得処理を引数で指定できるようにしてみました。
また、他のランドマークの範囲と重なった場合、今回は単純に上書き(後勝ち)するようにしましたが、確率値の大きい方を選択するか確率値を分配するようにした方が望ましいと思われます。
ラベルデータ作成処理
cols = [f'landmark_location_{t}_{i + 1}' for i in range(6) for t in ['x', 'y'] ] labels_t = dfa[cols].values.astype(int) def gen_labels(prob, around_func): res = np.zeros(imgs.shape[:-1] + (int(len(cols) / 2) + 1,)) res[:, :, :, 0] = 1.0 for i in range(len(res)): r = res[i] # ランドマーク毎の設定 for j in range(0, len(labels_t[i]), 2): # ランドマークの座標 x = labels_t[i, j] y = labels_t[i, j + 1] # ランドマークの分類(1 ~ 6) c = int(j / 2) + 1 for k in range(len(prob)): p = prob[k] # (相対的な)周辺座標の取得 for a in around_func(k): ax = x + a[0] ay = y + a[1] if ax >= 0 and ax < imgs.shape[2] and ay >= 0 and ay < imgs.shape[1]: # 他のランドマークと範囲が重なった場合への対応(設定値のクリア) r[ay, ax, :] = 0.0 # ランドマーク c へ該当する確率 r[ay, ax, c] = p # ランドマーク以外へ該当する確率 r[ay, ax, 0] = 1.0 - p return res
今回は以下のような内容でラベルデータを作りました。
ラベルデータ作成
def around_square(n): return [(x, y) for x in range(-n, n + 1) for y in range(-n, n + 1) if abs(x) == n or abs(y) == n] labels = gen_labels([1.0, 1.0, 1.0, 0.8, 0.8, 0.7, 0.7, 0.6, 0.6, 0.5], around_square)
ラベルデータの形状は以下の通りです。
labels.shape
(100, 256, 256, 7)
ラベルデータの内容確認
ランドマーク周辺の値を見てみると以下のようになっており、問題無さそうです。
labels[0, 59, 105:126]
array([[1. , 0. , 0. , 0. , 0. , 0. , 0. ], [0.5, 0.5, 0. , 0. , 0. , 0. , 0. ], [0.4, 0.6, 0. , 0. , 0. , 0. , 0. ], [0.4, 0.6, 0. , 0. , 0. , 0. , 0. ], [0.3, 0.7, 0. , 0. , 0. , 0. , 0. ], [0.3, 0.7, 0. , 0. , 0. , 0. , 0. ], [0.2, 0.8, 0. , 0. , 0. , 0. , 0. ], [0.2, 0.8, 0. , 0. , 0. , 0. , 0. ], [0. , 1. , 0. , 0. , 0. , 0. , 0. ], [0. , 1. , 0. , 0. , 0. , 0. , 0. ], [0. , 1. , 0. , 0. , 0. , 0. , 0. ], [0. , 1. , 0. , 0. , 0. , 0. , 0. ], [0. , 1. , 0. , 0. , 0. , 0. , 0. ], [0.2, 0.8, 0. , 0. , 0. , 0. , 0. ], [0.2, 0.8, 0. , 0. , 0. , 0. , 0. ], [0.3, 0.7, 0. , 0. , 0. , 0. , 0. ], [0.3, 0.7, 0. , 0. , 0. , 0. , 0. ], [0.4, 0.6, 0. , 0. , 0. , 0. , 0. ], [0.4, 0.6, 0. , 0. , 0. , 0. , 0. ], [0.5, 0.5, 0. , 0. , 0. , 0. , 0. ], [1. , 0. , 0. , 0. , 0. , 0. , 0. ]])
labels[0, 50:71, 149]
array([[1. , 0. , 0. , 0. , 0. , 0. , 0. ], [0.5, 0. , 0.5, 0. , 0. , 0. , 0. ], [0.4, 0. , 0.6, 0. , 0. , 0. , 0. ], [0.4, 0. , 0.6, 0. , 0. , 0. , 0. ], [0.3, 0. , 0.7, 0. , 0. , 0. , 0. ], [0.3, 0. , 0.7, 0. , 0. , 0. , 0. ], [0.2, 0. , 0.8, 0. , 0. , 0. , 0. ], [0.2, 0. , 0.8, 0. , 0. , 0. , 0. ], [0. , 0. , 1. , 0. , 0. , 0. , 0. ], [0. , 0. , 1. , 0. , 0. , 0. , 0. ], [0. , 0. , 1. , 0. , 0. , 0. , 0. ], [0. , 0. , 1. , 0. , 0. , 0. , 0. ], [0. , 0. , 1. , 0. , 0. , 0. , 0. ], [0.2, 0. , 0.8, 0. , 0. , 0. , 0. ], [0.2, 0. , 0.8, 0. , 0. , 0. , 0. ], [0.3, 0. , 0.7, 0. , 0. , 0. , 0. ], [0.3, 0. , 0.7, 0. , 0. , 0. , 0. ], [0.4, 0. , 0.6, 0. , 0. , 0. , 0. ], [0.4, 0. , 0.6, 0. , 0. , 0. , 0. ], [0.5, 0. , 0.5, 0. , 0. , 0. , 0. ], [1. , 0. , 0. , 0. , 0. , 0. , 0. ]])
これだけだと分かり難いので、単純な可視化を行ってみます。(ランドマークの該当確率をピクセル毎に合計しているだけ)
ラベルデータの可視化処理
matplotlib inline import matplotlib.pyplot as plt def imshow_label(index): plt.imshow(labels[index, :, :, 1:].sum(axis = -1), cmap = 'gray')
imshow_label(0)
imshow_label(1)
特に問題は無さそうです。
(3) CNN モデル
前回 と同様に Encoder-Decoder の構成を採用し、Encoder・Decoder をそれぞれ 1段階深くしました。(4段階に縮小して拡大)
多クラス分類を行うために、出力層の活性化関数を softmax
にして、損失関数を categorical_crossentropy
としています。
モデル内容
from keras.models import Model from keras.layers import Input, Dense, Dropout, UpSampling2D from keras.layers.convolutional import Conv2D, Conv2DTranspose from keras.layers.pooling import MaxPool2D from keras.layers.normalization import BatchNormalization input = Input(shape = imgs.shape[1:]) x = input x = BatchNormalization()(x) x = Conv2D(16, 3, padding='same', activation = 'relu')(x) x = Conv2D(16, 3, padding='same', activation = 'relu')(x) x = MaxPool2D()(x) x = BatchNormalization()(x) x = Dropout(0.3)(x) x = Conv2D(32, 3, padding='same', activation = 'relu')(x) x = Conv2D(32, 3, padding='same', activation = 'relu')(x) x = Conv2D(32, 3, padding='same', activation = 'relu')(x) x = MaxPool2D()(x) x = BatchNormalization()(x) x = Dropout(0.3)(x) x = Conv2D(64, 3, padding='same', activation = 'relu')(x) x = Conv2D(64, 3, padding='same', activation = 'relu')(x) x = Conv2D(64, 3, padding='same', activation = 'relu')(x) x = MaxPool2D()(x) x = BatchNormalization()(x) x = Dropout(0.3)(x) x = Conv2D(128, 3, padding='same', activation = 'relu')(x) x = Conv2D(128, 3, padding='same', activation = 'relu')(x) x = Conv2D(128, 3, padding='same', activation = 'relu')(x) x = MaxPool2D()(x) x = BatchNormalization()(x) x = Dropout(0.3)(x) x = Conv2D(256, 3, padding='same', activation = 'relu')(x) x = BatchNormalization()(x) x = Dropout(0.3)(x) x = UpSampling2D()(x) x = Conv2DTranspose(128, 3, padding = 'same', activation = 'relu')(x) x = Conv2DTranspose(128, 3, padding = 'same', activation = 'relu')(x) x = Conv2DTranspose(128, 3, padding = 'same', activation = 'relu')(x) x = BatchNormalization()(x) x = Dropout(0.3)(x) x = UpSampling2D()(x) x = Conv2DTranspose(64, 3, padding = 'same', activation = 'relu')(x) x = Conv2DTranspose(64, 3, padding = 'same', activation = 'relu')(x) x = Conv2DTranspose(64, 3, padding = 'same', activation = 'relu')(x) x = BatchNormalization()(x) x = Dropout(0.3)(x) x = UpSampling2D()(x) x = Conv2DTranspose(32, 3, padding = 'same', activation = 'relu')(x) x = Conv2DTranspose(32, 3, padding = 'same', activation = 'relu')(x) x = BatchNormalization()(x) x = Dropout(0.3)(x) x = UpSampling2D()(x) x = Conv2DTranspose(16, 3, padding = 'same', activation = 'relu')(x) x = Conv2DTranspose(16, 3, padding = 'same', activation = 'relu')(x) x = Dropout(0.3)(x) output = Dense(labels.shape[-1], activation = 'softmax')(x) model = Model(inputs = input, outputs = output) model.compile(loss = 'categorical_crossentropy', optimizer = 'adam', metrics = ['acc']) model.summary()
model.summary()
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_7 (InputLayer) (None, 256, 256, 3) 0 _________________________________________________________________ batch_normalization_46 (Batc (None, 256, 256, 3) 12 _________________________________________________________________ conv2d_56 (Conv2D) (None, 256, 256, 16) 448 _________________________________________________________________ conv2d_57 (Conv2D) (None, 256, 256, 16) 2320 _________________________________________________________________ max_pooling2d_20 (MaxPooling (None, 128, 128, 16) 0 _________________________________________________________________ batch_normalization_47 (Batc (None, 128, 128, 16) 64 _________________________________________________________________ dropout_46 (Dropout) (None, 128, 128, 16) 0 _________________________________________________________________ conv2d_58 (Conv2D) (None, 128, 128, 32) 4640 _________________________________________________________________ conv2d_59 (Conv2D) (None, 128, 128, 32) 9248 _________________________________________________________________ conv2d_60 (Conv2D) (None, 128, 128, 32) 9248 _________________________________________________________________ max_pooling2d_21 (MaxPooling (None, 64, 64, 32) 0 _________________________________________________________________ batch_normalization_48 (Batc (None, 64, 64, 32) 128 _________________________________________________________________ dropout_47 (Dropout) (None, 64, 64, 32) 0 _________________________________________________________________ conv2d_61 (Conv2D) (None, 64, 64, 64) 18496 _________________________________________________________________ conv2d_62 (Conv2D) (None, 64, 64, 64) 36928 _________________________________________________________________ conv2d_63 (Conv2D) (None, 64, 64, 64) 36928 _________________________________________________________________ max_pooling2d_22 (MaxPooling (None, 32, 32, 64) 0 _________________________________________________________________ batch_normalization_49 (Batc (None, 32, 32, 64) 256 _________________________________________________________________ dropout_48 (Dropout) (None, 32, 32, 64) 0 _________________________________________________________________ conv2d_64 (Conv2D) (None, 32, 32, 128) 73856 _________________________________________________________________ conv2d_65 (Conv2D) (None, 32, 32, 128) 147584 _________________________________________________________________ conv2d_66 (Conv2D) (None, 32, 32, 128) 147584 _________________________________________________________________ max_pooling2d_23 (MaxPooling (None, 16, 16, 128) 0 _________________________________________________________________ batch_normalization_50 (Batc (None, 16, 16, 128) 512 _________________________________________________________________ dropout_49 (Dropout) (None, 16, 16, 128) 0 _________________________________________________________________ conv2d_67 (Conv2D) (None, 16, 16, 256) 295168 _________________________________________________________________ batch_normalization_51 (Batc (None, 16, 16, 256) 1024 _________________________________________________________________ dropout_50 (Dropout) (None, 16, 16, 256) 0 _________________________________________________________________ up_sampling2d_20 (UpSampling (None, 32, 32, 256) 0 _________________________________________________________________ conv2d_transpose_44 (Conv2DT (None, 32, 32, 128) 295040 _________________________________________________________________ conv2d_transpose_45 (Conv2DT (None, 32, 32, 128) 147584 _________________________________________________________________ conv2d_transpose_46 (Conv2DT (None, 32, 32, 128) 147584 _________________________________________________________________ batch_normalization_52 (Batc (None, 32, 32, 128) 512 _________________________________________________________________ dropout_51 (Dropout) (None, 32, 32, 128) 0 _________________________________________________________________ up_sampling2d_21 (UpSampling (None, 64, 64, 128) 0 _________________________________________________________________ conv2d_transpose_47 (Conv2DT (None, 64, 64, 64) 73792 _________________________________________________________________ conv2d_transpose_48 (Conv2DT (None, 64, 64, 64) 36928 _________________________________________________________________ conv2d_transpose_49 (Conv2DT (None, 64, 64, 64) 36928 _________________________________________________________________ batch_normalization_53 (Batc (None, 64, 64, 64) 256 _________________________________________________________________ dropout_52 (Dropout) (None, 64, 64, 64) 0 _________________________________________________________________ up_sampling2d_22 (UpSampling (None, 128, 128, 64) 0 _________________________________________________________________ conv2d_transpose_50 (Conv2DT (None, 128, 128, 32) 18464 _________________________________________________________________ conv2d_transpose_51 (Conv2DT (None, 128, 128, 32) 9248 _________________________________________________________________ batch_normalization_54 (Batc (None, 128, 128, 32) 128 _________________________________________________________________ dropout_53 (Dropout) (None, 128, 128, 32) 0 _________________________________________________________________ up_sampling2d_23 (UpSampling (None, 256, 256, 32) 0 _________________________________________________________________ conv2d_transpose_52 (Conv2DT (None, 256, 256, 16) 4624 _________________________________________________________________ conv2d_transpose_53 (Conv2DT (None, 256, 256, 16) 2320 _________________________________________________________________ dropout_54 (Dropout) (None, 256, 256, 16) 0 _________________________________________________________________ dense_13 (Dense) (None, 256, 256, 7) 119 ================================================================= Total params: 1,557,971 Trainable params: 1,556,525 Non-trainable params: 1,446 _________________________________________________________________
(4) 学習
教師データ 100件では少なすぎると思いますが、今回はその中の 80件のみ学習に使用して 20件を検証に使ってみます。(validation_split
で指定)
ここで、ランドマークとそれ以外でデータ数に大きな偏りがあるため(ランドマーク以外が大多数)、そのままでは上手く学習できない恐れがあります。
以下では class_weight
を使ってランドマーク分類の重みを大きくしています。
実行例(351 ~ 400 エポック)
# 分類毎の重みを定義(ランドマークは 256*256 に設定) wg = np.ones(labels.shape[-1]) * (imgs.shape[1] * imgs.shape[2]) # ランドマーク以外(= 0)の重み設定 wg[0] = 1 hist = model.fit(imgs, labels, initial_epoch = 350, epochs = 400, batch_size = 10, class_weight = wg, validation_split = 0.2)
結果例
Train on 80 samples, validate on 20 samples Epoch 351/400 80/80 [===・・・ - loss: 0.0261 - acc: 0.9924 - val_loss: 0.1644 - val_acc: 0.9782 Epoch 352/400 80/80 [===・・・ - loss: 0.0263 - acc: 0.9924 - val_loss: 0.1638 - val_acc: 0.9784 ・・・ Epoch 399/400 80/80 [===・・・ - loss: 0.0255 - acc: 0.9930 - val_loss: 0.1719 - val_acc: 0.9775 Epoch 400/400 80/80 [===・・・ - loss: 0.0253 - acc: 0.9931 - val_loss: 0.1720 - val_acc: 0.9777
(5) 確認
fit
の戻り値から学習・検証の loss
と acc
の値をそれぞれグラフ化してみます。
fit 結果表示
%matplotlib inline import matplotlib.pyplot as plt plt.rcParams['figure.figsize'] = (16, 4) plt.subplot(1, 4, 1) plt.plot(hist.history['loss']) plt.subplot(1, 4, 2) plt.plot(hist.history['acc']) plt.subplot(1, 4, 3) plt.plot(hist.history['val_loss']) plt.subplot(1, 4, 4) plt.plot(hist.history['val_acc'])
結果例(351 ~ 400 エポック)
val_loss
と val_acc
の値が良くないのは、データ量が少なすぎる点にあると考えています。
(6) ランドマーク検出
下記 4種類の画像を出力して、ランドマーク検出(predict)結果とラベルデータ(正解)を比較してみます。
- (a) ラベルデータの分類(ピクセル毎に確率値が最大の分類で色分け)
- (b) 予測結果(predict)の分類(ピクセル毎に確率値が最大の分類で色分け)
- (c) 元画像と (b) の重ね合わせ
- (d) ランドマークの描画(各ランドマークの確率値が最大の座標へ円を描画)
今回はランドマークは分類毎に 1点のみなので、確率が最大値の座標がランドマークと判断できます。
ランドマーク検出と結果出力
import cv2 colors = [(255, 255, 255), (255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (0, 255, 255), (255, 0, 255), (255, 165, 0), (210, 180, 140)] def predict(index, n = 0, c_size = 5, s = 5.0): plt.rcParams['figure.figsize'] = (s * 4, s) img = imgs[index] # 予測結果(ランドマーク分類結果) p = model.predict(np.array([img]))[0] # (a) ラベルデータの分類(ピクセル毎に確率値が最大の分類で色分け) img1 = np.apply_along_axis(lambda x: colors[x.argmax()], -1, labels[index]) # (b) 予測結果の分類(ピクセル毎に確率値が最大の分類で色分け) img2 = np.apply_along_axis(lambda x: colors[x.argmax()], -1, p) # (c) 元画像への重ね合わせ img3 = cv2.addWeighted(img.astype(int), 0.4, img2, 0.6, 0) plt.subplot(1, 4, 1) plt.imshow(img1) plt.subplot(1, 4, 2) plt.imshow(img2) plt.subplot(1, 4, 3) plt.imshow(img3) img4 = img.astype(int) pdf = pd.DataFrame( [[np.argmax(vx), x, y, np.max(vx)] for y, vy in enumerate(p) for x, vx in enumerate(vy)], columns = ['landmark', 'x', 'y', 'prob'] ) for c, v in pdf[pdf['landmark'] > 0].sort_values('prob', ascending = False).groupby('landmark'): # (d) ランドマークを描画(確率値が最大の座標へ円を描画) img4 = cv2.circle(img4, tuple(v[['x', 'y']].values[0]), c_size, colors[c], -1) if n > 0: print(f"landmark {c} : x = {labels_t[index, (c - 1) * 2]}, {labels_t[index, (c - 1) * 2 + 1]}") print(v[:n]) plt.subplot(1, 4, 4) plt.imshow(img4)
学習データの結果例
左から (a) ラベルデータの分類、(b) 予測結果の分類、(c) 元画像との重ね合わせ、(d) ランドマーク検出結果となっています。
ラベルデータにかなり近い結果が出ているように見えます。
下記のように、ランドマーク毎の確率値 TOP 3 とラベルデータを数値で比較してみると、かなり近い値になっている事を確認できました。
predict(0, n = 3)
landmark 1 : x = 115, 59 landmark x y prob 15475 1 115 60 0.893763 15476 1 116 60 0.893605 15220 1 116 59 0.893044 landmark 2 : x = 149, 60 landmark x y prob 15510 2 150 60 0.878173 15766 2 150 61 0.872413 15509 2 149 60 0.872222 landmark 3 : x = 82, 153 landmark x y prob 39250 3 82 153 0.882741 39249 3 81 153 0.881362 39248 3 80 153 0.879979 landmark 4 : x = 185, 150 landmark x y prob 38841 4 185 151 0.836826 38585 4 185 150 0.836212 38840 4 184 151 0.836164 landmark 5 : x = 93, 198 landmark x y prob 50782 5 94 198 0.829380 50526 5 94 197 0.825815 51038 5 94 199 0.825342 landmark 6 : x = 171, 197 landmark x y prob 50602 6 170 197 0.881702 50603 6 171 197 0.880731 50858 6 170 198 0.877772
predict(40, n = 3)
landmark 1 : x = 120, 42 landmark x y prob 8820 1 116 34 0.568582 9075 1 115 35 0.566257 9074 1 114 35 0.561259 landmark 2 : x = 134, 40 landmark x y prob 10372 2 132 40 0.812515 10371 2 131 40 0.807980 10628 2 132 41 0.807899 landmark 3 : x = 109, 48 landmark x y prob 12652 3 108 49 0.839624 12653 3 109 49 0.838190 12396 3 108 48 0.837235 landmark 4 : x = 148, 43 landmark x y prob 11156 4 148 43 0.837879 10900 4 148 42 0.837810 11157 4 149 43 0.836910 landmark 5 : x = 107, 176 landmark x y prob 45164 5 108 176 0.845494 45420 5 108 177 0.841054 45163 5 107 176 0.839846 landmark 6 : x = 154, 182 landmark x y prob 46746 6 154 182 0.865920 46747 6 155 182 0.863970 46490 6 154 181 0.862724
なお、predict(40)
におけるランドマーク 1(赤色)の結果が振るわないのは、ラベルデータの作り方の問題だと考えられます。(上書きでは無く確率値が大きい方を採用する等で改善するはず)
検証データの結果例
当然ながら、学習に使っていないこちらのデータでは結果が悪化していますが、それなりに正しそうな位置を部分的に検出しているように見えます。
学習に使ったデータ量の少なさを考えると、かなり良好な結果が出ているようにも思います。
そもそも、predict(-3)
のようなランドマークの左右が反転している背面からの画像なんてのは無理があるように思いますし、predict(-8)
のランドマーク 5(水色)はラベルデータの方が間違っている(検出結果の方が正しい)ような気もします。
predict(-1, n = 3)
landmark 1 : x = 96, 60 landmark x y prob 15969 1 97 62 0.872259 16225 1 97 63 0.869837 15970 1 98 62 0.869681 landmark 2 : x = 126, 59 landmark x y prob 16254 2 126 63 0.866628 16255 2 127 63 0.865502 15998 2 126 62 0.864939 landmark 3 : x = 66, 125 landmark x y prob 30521 3 57 119 0.832024 30520 3 56 119 0.831721 30777 3 57 120 0.829537 landmark 4 : x = 157, 117 landmark x y prob 29099 4 171 113 0.814012 29098 4 170 113 0.813680 28843 4 171 112 0.812420
predict(-8, n = 3)
landmark 1 : x = 133, 40 landmark x y prob 10629 1 133 41 0.812287 10628 1 132 41 0.810564 10373 1 133 40 0.808298 landmark 2 : x = 157, 47 landmark x y prob 12704 2 160 49 0.767413 12448 2 160 48 0.764577 12703 2 159 49 0.762571 landmark 3 : x = 105, 77 landmark x y prob 19300 3 100 75 0.79014 19301 3 101 75 0.78945 19556 3 100 76 0.78496 landmark 4 : x = 181, 86 landmark x y prob 56242 4 178 219 0.768471 55986 4 178 218 0.768215 56243 4 179 219 0.766977 landmark 5 : x = 137, 211 landmark x y prob 54370 5 98 212 0.710897 54626 5 98 213 0.707652 54372 5 100 212 0.707127