OpenCVメモ(文字の検出と認識その3)

前回の続き。

「recognition」モジュールの1つ、「HMM(Hidden Markov Models)」を使ったDecodeを試してみる。

Decoder生成

アルファベット小文字、アルファベット大文字、数字の「62」個を認識ターゲットとする。
モデル、遷移確率は学習済みのものを使う。

// Trained models
auto classifier = cv::text::loadOCRHMMClassifierNM("OCRHMM_knn_model_data.xml.gz");

// Vocabulary
std::string voc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

// Trained transition probabilities
Mat transitions_prob;
FileStorage fs("assets/OCRHMM_transitions_table.xml", FileStorage::READ);
fs["transition_probabilities"] >> transitions_prob;
fs.release();

// Emission probabilities. Identity matrix.
cv::Mat emission_prob = cv::Mat::eye(62, 62, CV_64FC1);

auto ocr = OCRHMMDecoder::create(classifier,
                                 voc,
                                 transitions_prob,
                                 emission_prob,
                                 OCR_DECODER_VITERBI);

学習済みKNNモデル(XML)は、「cv::text::loadOCRHMMClassifierNM( )」で読み込む。

モデル(OCRHMM_knn_model_data.xml.gz)のパラメータ要素数は以下のとおり。

hus   : 7192row x 200col
label : 7192row x 1col
  • 認識ターゲットとなる「62」個の各文字に対して、116個ずつの特徴ベクトル(feature vector
  • 特徴ベクトルは「200次元」で表現
  • ラベルは「0」から「61」まで(先頭と末尾に「-1」が1つずつ)

サンプルとして提供されている画像を使って認識させてみる。
opencv_contrib/modules/text/samples at master · opencv/opencv_contrib · GitHub

(Case A) scenetext_segmented_word03_mask.png(サイズ640x480)

https://github.com/opencv/opencv_contrib/blob/master/modules/text/samples/scenetext_segmented_word03_mask.png?raw=true

# topleft(0, 0)
private (confidence : 5.1946e-09)

# topleft(372, 0)
Hire (confidence : 4.29457e-07)
(Case B) scenetext_segmented_word04_mask.png(サイズ640x480)

https://github.com/opencv/opencv_contrib/blob/master/modules/text/samples/scenetext_segmented_word04_mask.png?raw=true

FOSTERS (confidences : 5.12486e-13)
(Case C) scenetext_segmented_word02_mask.png(サイズ640x480)

https://github.com/opencv/opencv_contrib/blob/master/modules/text/samples/scenetext_segmented_word02_mask.png?raw=true

# topleft(0, 0)
ununisununinunununnuntilununalununununununtuninninnunonunununununinununununenunununununnurunmunnynunununununnununinununn
 (confidence : 0)

# topleft(447, 0)
BUnununinnoumununuxinuniminununininn (confidence : 0)

OpenCVメモ(文字の検出と認識その2)

前回の続き。

「recognition」モジュールを見てみる。
いくつか種類がある。

  • OCR using Beam Search algorithm.
  • OCR using HMM(Hidden Markov Models)
  • tesseract-ocr API

今回は「BeamSearch」を使ったDecodeを試してみる。

Decoder生成

アルファベット小文字、アルファベット大文字、数字の「62」個を認識ターゲットとする。
モデル、遷移確率は学習済みのものを使う。

// Trained models
auto classifier = cv::text::loadOCRBeamSearchClassifierCNN("OCRBeamSearch_CNN_model_data.xml.gz");

// Vocabulary
std::string voc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

// Trained transition probabilities
Mat transitions_prob;
FileStorage fs("assets/OCRHMM_transitions_table.xml", FileStorage::READ);
fs["transition_probabilities"] >> transitions_prob;
fs.release();

// Emission probabilities. Identity matrix.
cv::Mat emission_prob = cv::Mat::eye(62, 62, CV_64FC1);

// Size of the beam in Beam Search algorithm.
int beam_size = 50;

auto ocr = OCRBeamSearchDecoder::create(classifier,
                                        voc,
                                        transitions_prob,
                                        emission_prob,
                                        OCR_DECODER_VITERBI,
                                        beam_size);  

第1引数のclassifierはCNNを使っている模様。

The character classifier consists in a Single Layer Convolutional Neural Network and a linear classifier.
It is applied to the input image in a sliding window fashion, providing a set of recognitions at each window location.

各パラメータ要素数は以下のとおり。

kernel      :  118row x   64col
M           :    1row x   64col
P           :   64row x   64col
weight      : 1062row x   62col
feature_min :    1row x 1062col
feature_max :    1row x 1062col

サンプルとして提供されている画像(サイズ242x102)のテキストを認識させてみる。
https://raw.githubusercontent.com/opencv/opencv_contrib/master/modules/text/samples/scenetext_word02.jpg

Mat cropImage = imread("scenetext_word02.jpg");

ocr->run(cropImage,         // Input binary image
         text,              // output_text
         &boxes,            // component_rects
         &words,            // component_texts 
         &confidences,      // component_confidences
         OCR_LEVEL_WORD);   // component_level

認識結果。

HERE (confidence : 0.0287784)

自分で用意した画像を認識させてみる

元画像(サイズ16 x 42)

f:id:ichou1:20190503124211p:plain

uly (confidence : 0.0118966)
横方向に拡大した画像(サイズ16 x 75)

f:id:ichou1:20190503124400p:plain

LLICH (confidence : 0.0231771)
さらに、横方向に拡大した画像(サイズ16 x 100)

f:id:ichou1:20190503124524p:plain

LLICHI (confidence : 0.0241128)

OpenCVメモ(文字の検出と認識その1)

OpenCVを使った文字の検出と認識を試してみる。

使用したOpenCVのバージョンは「4.0.1」
% pkg-config --modversion opencv4
4.0.1

公式サイトを見ると、「text」モジュールに関しては、「Detection」と「Recognition」の2つに分類されている。

まずは「Detection」
デモコードを動かしてみる。

使用した画像(236 x 303)

f:id:ichou1:20190513220738p:plain

画像を各channelごとに分解

f:id:ichou1:20190514105301p:plain

60〜70ピクセル程度のエリアが検出されるよう、minArea / maxAreaを指定。
(赤字で表示された"SOS電話"の"S"を想定)

拡大 + グレースケール変換

f:id:ichou1:20190513222725p:plain

拡大 + 2値化(しきい値120)

f:id:ichou1:20190513222739p:plain

検出を実行。2段階に分けて実施する。

Ptr<ERFilter> er_filter1 = createERFilterNM1(loadClassifierNM1("trained_classifierNM1.xml"),
                                    1,         // thresholdDelta = 1
                                    0.0006f,   // minArea(% of image size) = 0.00025
                                    0.0008f,   // maxArea(% of image size) = 0.13
                                    0.2,       // minProbability = 0.4, 
                                    true,      // nonMaxSuppression = true
                                    0.1);      // minProbabilityDiff = 0.1


Ptr<ERFilter> er_filter2 = createERFilterNM2(loadClassifierNM2("trained_classifierNM2.xml"),
                                    0.3);  // minProbability = 0.3 
各channelごとの抽出されたRegion

f:id:ichou1:20190514105529p:plain

(参考)各channelごとの抽出されたRegion(filterNM1のみ)

f:id:ichou1:20190515225218p:plain

Kerasメモ(強化学習)

Deep Reinforcement Learningを試してみる。

今回、使ったソース
Deep-Learning-with-Keras/rl-network-train.py at master · PacktPublishing/Deep-Learning-with-Keras · GitHub

ゲーム内容は、プレイヤーが「paddle」を動かして、落下してくる「ball」をキャッチする。
プレイヤーが選択できるアクションは3種類。

NUM_ACTIONS = 3 # number of valid actions (left, stay, right)

ゲーム画面は、縦横ともに400px。
下図は、1マスの縦横が5pxのグリッド線を書き足した状態。
f:id:ichou1:20190413095650p:plain

「ball」は、1回に2マス分、落下する(「paddle」のheightと同じ)
また、「paddle」は1回に4マス分、移動できる(「ball」のwidthと同じ)
f:id:ichou1:20190412215006p:plain

下図が、ボールが落ち切った状態。
スタートしてから落ち切るまでに遷移する状態数は「32」
f:id:ichou1:20190412215241p:plain

モデル全体像。
(80, 80)の画像を1frameとして、直近4frameをもとに、次のアクションを予測する。
f:id:ichou1:20190412220154p:plain

報酬(reward)は以下のとおり。

  • キャッチ成功:1
  • キャッチ失敗:-1
  • ボール落下中: 0

バッチ1つ分を構成するデータは以下のとおり。
Xがトレーニングデータ、Yが教師データに該当する。
Yには、model.predict( )で得られる、各アクションに対する報酬の予測値が入る。

s_t, a_t, r_t, s_tp1, game_over = batch[i]
# s_t : state, shape --> (1, 80, 80, 4)
# a_t : action
# r_t : reward (at state "s_t",  take action "a_t" )
# s_tp1 : state(t+1), shape --> (1, 80, 80, 4)

X[i] = s_t
Y[i] = model.predict(s_t)[0]
# example Y[i]
# --> array([ 0.09449015, -0.01138393, -0.06042233])

この後、ゲームの状態に応じて、Yを書き換える。

ボールが落ち切った場合、報酬で上書きする。

Y[i, a_t] = r_t

# example
#   a_t : 2
#   r_t : 1
#   before Y[i] : [ 0.09449015, -0.01138393, -0.06042233]
#   after  Y[i] : [ 0.09449015, -0.01138393,  1.        ]

ボールが落下中の場合、アクションa_tに対する予測値を、状態t+1の予測値の最大値に書き換える。
(この時、割引率gammaを掛ける)

Q_sa = np.max(model.predict(s_tp1)[0])
Y[i, a_t] = r_t + gamma * Q_sa    # gamma = 0.99

# example
#   a_t : 2
#   r_t : 0
#   model.predict(s_tp1)[0]  --> [ 0.00574994, -0.04766183, -0.0417471 ]
#   Q_sa : 0.0057499357
#   before Y[i] : [-0.02611616, -0.09139924, -0.0712234 ]
#   after  Y[i] : [-0.02611616, -0.09139924,  0.00569244 ]

X、Yをもとにmodelをトレーニングする。

model.train_on_batch(X, Y)

報酬が大きくなる行動(キャッチ成功、reward=1)を選び、報酬が小さくなる行動(キャッチ失敗、reward=-1)を避けるようになる。

Kerasメモ(GANその2)

前回の続き

DCGANを使ってCIFAR-10の画像を生成してみる。
class6のfrog(カエル)を指定。

MNISTからの変更点は以下のとおり。

  • 画像サイズを「32 x 32」に変更
  • チャンネル数を「3」に変更
  • discriminatorでのDropoutレイヤをSpatialDropout2Dレイヤに変更(チャンネル単位でdropout)
  • discriminatorでのZeroPadding2Dレイヤを削除
  • generator、discriminatorともに、Conv2D、Denseレイヤのパラメータ"kernel_regularizer"に対して正則化を追加

モデル全体像
f:id:ichou1:20190407120102p:plain

段々と、カエルのような画像になってくる。

f:id:ichou1:20190407121228p:plain

f:id:ichou1:20190407121330p:plain

f:id:ichou1:20190407121344p:plain

Kerasメモ(GAN)

敵対的生成ネットワーク(Generative adversarial Network)を使ったイメージ生成を試してみる。

今回試したソース。
Keras-GAN/dcgan.py at master · eriklindernoren/Keras-GAN · GitHub

モデルの全体像
f:id:ichou1:20190406104803p:plain

モデルに関するメモ。
以下、生成モデルGeneratorを"G"、識別モデルDiscriminatorを"D"とする。

  • Gのインプットは、標準正規分布からサンプリングされる値を使う(一様分布を使うパターンもある)
  • G、Dともに畳み込みの後にプーリングは行わず、BatchNormalizationレイヤを使う
  • Gの最後のActivationレイヤは"tanh"("sigmoid"を使うパターンもある)
  • Dでは、LeaklyReLUレイヤを使う(Gではrelu)
  • Dでは、Dropoutレイヤを使う(LeaklyReLUレイヤの後)
  • Dでは、ZeroPadding2Dレイヤを使って、テンソルのbottomとrightにゼロを追加
  • GAN(G、Dを結合したモデル)では、Dの学習を凍結する

モデルにおけるレイヤーのtrainableプロパティは、compile( )を呼んだ時点で有効になる。
FAQ - Keras Documentation

# Build and compile the discriminator
self.discriminator = self.build_discriminator()
self.discriminator.compile(loss='binary_crossentropy', ...
# --> ここでの「self.discriminator」はモデル(trainable=true)
...
                                                       
# The combined model  (stacked generator and discriminator)
z = Input(shape=(self.latent_dim,))
self.discriminator.trainable = False
self.combined = Model(z, self.discriminator(self.generator(z)))
self.combined.compile(loss='binary_crossentropy', optimizer=optimizer)
# --> ここでの「self.discriminator」はレイヤ。モデル「self.combined」の構成要素(trainable=false)

学習が進むほど、生成される画像の輪郭がはっきりしてくる(線が細くなる)
f:id:ichou1:20190406113352p:plain
f:id:ichou1:20190406113415p:plain
f:id:ichou1:20190406113427p:plain

Kerasメモ(Memory network)

「bAbI」データセットを使ったMemory networkを試してみる。

論文

arxiv.org

データ

Single Supporting Facts
tasks_1-20_v1-2/en-10k/qa1_single-supporting-fact_train.txt

1 Mary moved to the bathroom.
2 John went to the hallway.
3 Where is Mary? 	bathroom	1
...

読み込み後のデータは3つ組のtupleで構成される。
tupleは「Input」、「Question」、「Answer」の3つを保持。

Input
train_stories[0][0]
[u'Mary', u'moved', u'to', u'the', u'bathroom', u'.', u'John', u'went', u'to', u'the', u'hallway', u'.']
Question
train_stories[0][1]
[u'Where', u'is', u'Mary', u'?']
Answer
train_stories[0][2]
u'bathroom'

データ数はトレーニング用に「10,000」、検証用に「1,000」

全体の語彙数は「21」

vocab
[u'.', u'?', u'Daniel', u'John', u'Mary', u'Sandra', u'Where', u'back', u'bathroom', u'bedroom', u'garden', u'hallway', u'is', u'journeyed', u'kitchen', u'moved', u'office', u'the', u'to', u'travelled', u'went']

これをindexで表現する。

word_idx = dict((c, i + 1) for i, c in enumerate(vocab))
word_idx
{u'hallway': 12, u'bathroom': 9, u'garden': 11, u'journeyed': 14, u'office': 17, u'is': 13, u'bedroom': 10, u'moved': 16, u'back': 8, u'.': 1, u'to': 19, u'Daniel': 3, u'Sandra': 6, u'travelled': 20, u'went': 21, u'the': 18, u'John': 4, u'Where': 7, u'Mary': 5, u'?': 2, u'kitchen': 15}

各々の最大長は以下のとおり。
Input: 「68」単語
Qustion: 「4」単語
Answer: 「1」単語

教師データとして与えるAnswerはindex(「0」から「21」までの値)
ここで、index=0はdummy

# Reserve 0 for masking via pad_sequences
vocab_size = len(vocab) + 1    # 22
モデル

f:id:ichou1:20190323131501p:plain

モデルに関するメモ。
Input(Sentences)は、2つのEmbeddingレイヤをとおす。

# vocab_size = 22
# embedding_dim = 64
input_encoder_m.add(Embedding(input_dim=vocab_size, output_dim=64))
# --> output: (samples, story_maxlen, embedding_dim)

# query_maxlen = 4
input_encoder_c.add(Embedding(input_dim=vocab_size, output_dim=query_maxlen))
# --> output: (samples, story_maxlen, query_maxlen)

Questionは、1つのEmbeddingレイヤをとおす。

question_encoder.add(Embedding(input_dim=vocab_size, output_dim=64, input_length=query_maxlen))
# --> output: (samples, query_maxlen, embedding_dim)

変換後のInput(Sentences)とQuestionの内積をとって関連ベクトルを算出する。

match = dot([input_encoded_m, question_encoded], axes=(2, 2))
# --> output: (samples, story_maxlen, query_maxlen)