備忘録

イケイケエンジニアになるために自己嫌悪と戦う大学院生のメモ兼モチベーション維持。

ディープラーニングするぞ

久しぶりに時間が取れたので文字切り出し・ノイズ低減(削除)・モアレ除去など試して見たがなかなかうまくいかない。 そもそも環境光の異なる(予測できない)状況下において、 古典的画像処理で全ての状況に対応しようという試みが間違っているのだろうと思う。

ということでDeepLearningでなんとかすることにした。

文字認識するだけならMNISTやりましたという記事を漁ればゴロゴロ情報が出てくるのだが、 一度体系的に学習したかったので本を読む。

深層学習 (機械学習プロフェッショナルシリーズ)

深層学習 (機械学習プロフェッショナルシリーズ)

ラボにころがってたこれ。

やるぞ。

Kodak PIXPRO SP360-4K のWifiストリーミング環境構築

Kodak社のPIXPRO SP360-4Kは1台で半天球、2台合わせて全天球映像を撮影可能なカメラである。

PIXPRO - マスプロ電工|MASPRO

UVC規格対応でUSBケーブルによる接続でWebカメラとしても利用できるが、 Wifiルータを内蔵しており映像をストリーミングできる機能が付いている。

下記のサイトを参考にMacでの利用を試みたが微妙にハマったので忘れないようにメモしておく。

tks-yoshinaga.hatenablog.com

yaaam.blog.jp

環境

Mac OS Sierra + Python3(3.5.2) + openCV3(3.2.0)

とりあえず繋いでみる

cap = cv2.VideoCapture("http://172.16.0.254:9176/")
cap is None:
        print("Failed to open the camera.")
while True:
        ret, frame = self.cap.read()
        if not ret:
            print("Failed to capture the image.")
            return None
        cv2.imshow("image", frame)
$ python capture.py 
VIDEOIO(cvCreateFileCapture_AVFoundation (filename)): raised unknown C++ exception!

Failed to capture the image.

原因

どうやらffmpegが無かったのが原因らしい。

$brew reinstall --force opencv3 --with-ffmpeg --with-python3

virtualenvを利用しているので再インストールしたOpenCV3へのシンボリックリンクを作り直す。

blog.ymyzk.com

その他

参考にしたサイトには受信側IPを172.16.0.1に固定するように書いてあったが、 自動設定でも問題なく接続できた。(むしろ固定すると接続に失敗した)

Goやりたい

最近Go言語をやりたい欲が強くなってきた。

なぜ

リアルタイム通信系のサーバサイド技術勉強したい

→ Node.jsかGo言語やりたい

→ リサーチ

→ Go言語の魅力に惹かれている

魅力に思うところ

自分はC -> Java -> C# -> C++ -> php -> Pythonという流れで勉強してきた。 スクリプト言語は書くのが非常に楽だけど他人のコードを見るのが辛く感じる。 しかし今更静的言語に戻るとコーディング作業が面倒に感じる部分が多い。 (C++イテレータとか嫌い) Go言語は静的言語とスクリプト言語のいいとこ取り(静的言語なのにさらっとかける)らしいので気になる。

-コーディング規約が厳しい

Pythonを触り始めてから厳しいコーディング規約に魅力を感じるようになった。

  • 規約が宗教的にあっている
if{
}else{
}

派で

if(statement) hoge();

は絶対使わない派などなどGoで制限されるお作法が自分のスタイルとバッチリ噛み合っていた。

  • GCがある

  • 並行処理/同期処理が書きやすいらしい

並行処理を勉強したいが、記述や実装面での学習にコストを取られずに並行処理実装時のコアとなる部分 (どう設計すべきか、など)を学ぶことができるのでは??

C++から逃げたいという気持ちが大きいのかもしれない。

どうやって勉強するか

今作っているものが一段落したら、適当なボードゲームをブラウザ上で遊べるようにしたものを作ろうと思う。 後悔したら著作権的によろしくなさそうだし、身内で遊ぶ程度のクオリティを目指す。 上手く共通処理部分を切り出せるように設計して、モジュール単位でGithubにあげるとかはアリか。

Pythonによるスクレイピング&機械学習 開発テクニックを買った

夕飯の買い物ついでにふらりと寄った書店で技術書を買った。

スクレイピングもTensorflowもやってみたかったのでちょうどいいと思い購入。 Seleniumってスクレイピングにも使えるのね(考えてみればそりゃそうだけど)

コンマイの楽曲データをスクレイピングするプログラムでもサクッと書いてみようと思う。 画像認証(reCAPTCHAとかそういう意味の)の突破はやらない。

adaptiveThresholdさん見直し

2値化の処理を探っているとき、cv2.adaptivethreshold()があまり使えないように思っていたが、ただ単に自分のチューニング不足だった。

cv2.adaptivethreshold(image, method, blocksize, c)

画像のしきい値処理 — OpenCV-Python Tutorials 1 documentation

Block Size - しきい値計算に使用する近傍領域のサイズ.1より大きい奇数を指定する必要があります.

C - 計算されたしきい値から引く定数です

Cの存在意義を理解していなかったが、文字領域の抽出においてはこいつを適切にチューニングする必要があった。

OpenCvSharpをつかう その15(適応的閾値処理) - schima.hatenablog.com

減算定数の意味

最後の減算定数は何のためにあるのでしょうか。

(中略)

文字が有る領域: 周囲の画素値はバラエティ豊か(白地に黒い細い線、で構成されるので)

文字が無い領域: 周囲の画素値はほぼ同じ(周りじゅうが白)

周り中が似たような色のとき、減算定数が有ることで、減算後は対象ピクセル閾値を上回ることになり、白くなります。

これにより、背景領域では多少のノイズ・色の揺らぎに負けずに白で塗りつぶしやすくし、文字領域では黒いエッジを残しやすくなります。賢いですね。

なるほど賢いので早速実験してみた。

コード

def binalizeByAdaptive(img):
    r = img.copy()

    # R, G値のみ取り出しグレースケール化
    green = r[:,:,1]
    red = r[:,:,2]
    redGreen = cv2.addWeighted(red, 0.5, green, 0.5, 0)

    # binalize
    th_red = cv2.adaptiveThreshold(redGreen,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY_INV,21,20)
    
    # cleaning noise by opening
    kernel = np.ones((1,1),np.uint8)
    th_red = cv2.morphologyEx(th_red, cv2.MORPH_OPEN, kernel)

    cv2.imshow("binalize", th_red)
    # cv2.waitKey(0)

    return th_red

blockSize, Cの値は試行錯誤した結果の値である。

結果

成功例

f:id:reverent_f:20170124185128p:plain:w250 f:id:reverent_f:20170124185130p:plain:w250

文字の縁取り部分まで綺麗に検出できている。 また、0,8,9などの文字中の空洞部分も比較的綺麗に見える!

失敗例

  • 減算定数

f:id:reverent_f:20170124185156p:plain:w250

減算定数が大きすぎることで文字の中心部分が潰れた。 これは検出に失敗したら減算定数を変化させてもう一度認識、とすれば対応できそう。

  • モアレ

f:id:reverent_f:20170124185235p:plain:w250 f:id:reverent_f:20170124185238p:plain:w250

モアレが発生している画像だとノイズが多量にのってしまう。 フーリエ変換して高周波成分を取り除く、までやるのは難しいか … ?

まとめ

やはり適応的2値化処理は照明条件の変化に強く、綺麗に文字の検出ができる!

ただし、文字の縁も綺麗に残るので、文字の切り出し処理を修正しなければならない(嬉しい悲鳴)

ただし、モアレに非常に弱いことが判明した。 有効なモアレの除去方法が思いつかないため、モアレが発生している場合は従来の2値化処理を使うしかなさそうだ。

その場合は教師データを2値化の処理ごとに2つ用意しなければならない … 。

雑記

どこまでの画像を認識対象とするか、そろそろ限定しなければならない。 そろそろサイトの作成にも取り掛かりたい。

kNNで文字認識

kNNとは

k近傍法(k-nearest neighbor)。かなり単純なクラスタリングアルゴリズム

  1. 教師データ(ラベリング済)を特徴ベクトル化して学習
  2. クラスタリング対象のデータも特徴ベクトル化
  3. 特徴ベクトルの距離を比較し、対象データと近い順にk個の教師データを選ぶ
  4. 選ばれた教師データに付与されているラベルから対象データのクラスを決定

直感的に理解できる簡素なアルゴリズムである。 OpenCVで実装されているため簡単に試せる。

qiita.com

IkalogではkNNで文字認識を行なっているらしい。

「スプラトゥーン」リアルタイム画像解析ツール 「IkaLog」の裏側

文字位置・角度・照明条件が可変な今回のケースではどの程度の精度が出るのか、試しにやってみた。

学習データの準備

前項までで文字位置の特定ができているので、その領域を切り出して等サイズにリサイズ→保存する。

resized_img = cv2.resize(character_img, (RESIZED_WIDTH, RESIZED_HEIGHT), 
                                            interpolation=cv2.INTER_NEAREST)

PythonでkNN

学習は毎回実行時に行う。

  1. 学習データを1行N列のベクトルに変形
# sample = img.reshape((1, img.shape[0] * img.shape[1]))
sample = img.reshape((1, -1))

reshapeの引数のサイズのうち片方に-1を指定すると,もう一方の値から適切な数値を判断して変形してくれる。 Numpyつよい。

ラベルは別配列に要素番号が同じになるように保存する。

label = ord('0') + int(character_i)
labels.append(label)

samplesは[1行N列のベクトル]の配列、かつtype() == CV_32Sになるように注意して変形する。 labelsも[1行1列のベクトル(?)]の配列、かつtype() == CV_32Sになるように変形。

printするとこんな感じ。

samples
[[   0.    0.    0. ...,    0.    0.    0.]
 [   0.    0.    0. ...,    0.    0.    0.]
 [   0.    0.    0. ...,    0.    0.    0.]
 ..., 
 [   0.    0.    0. ...,  255.  255.    0.]
 [   0.    0.    0. ...,  255.  255.    0.]
 [   0.    0.    0. ...,  255.  255.  255.]]

labels
[[ 48.]
 [ 48.]
 [ 48.]
...,
 [ 57.]
 [ 57.]
 [ 57.]
 [ 57.]]

学習部分全体のコード。

    samples = None
    labels = []
    # for 0 - 9
    for character_i in range(0, 10):
        img_dir = TRAIN_DATA_DIR + str(character_i) + "/"
        files = os.listdir(img_dir)
        for file in files:
            ftitle, fext = os.path.splitext(file)
            if fext != '.jpg' and fext != '.png':
                continue

            # Load image
            abspath = os.path.abspath(img_dir + file)
            img = cv2.imread(abspath, cv2.IMREAD_GRAYSCALE)
            if img is None:
                print('Failed to read image')
                break

            sample = img.reshape((1, -1))
            label = ord('0') + int(character_i)

            if samples is None:
                samples = np.empty((0, img.shape[0] * img.shape[1]))
            samples = np.append(samples, sample, 0).astype(np.float32)
            labels.append(label)

    labels = np.array(labels, np.float32)
    labels = labels.reshape((labels.size, 1)).astype(np.float32)

    knn = cv2.ml.KNearest_create()
    knn.train(samples, cv2.ml.ROW_SAMPLE, labels)

文字認識

学習結果を使って画像をクラスタリングすることで文字認識を行う。

# kNN
ch_string=""
for ch_img in character_imgs:
    sample = ch_img.reshape((1, ch_img.shape[0] * ch_img.shape[1]))
    sample = np.array(sample, np.float32)

    k = 3
    retval, results, neigh_resp, dists = knn.findNearest(sample, k)
    d = chr(int(results.ravel()))
    ch_string += str(d)
print("result:" + ch_string)
cv2.waitKey(0)

認識対象も勿論1行N列の32bitfloat配列に直してknn.findNearest()につっこむ。

結果

成功例

f:id:reverent_f:20170124182341p:plain:w200

result:09828932

f:id:reverent_f:20170124182510p:plain:w200

result:08463768

失敗例

f:id:reverent_f:20170124182543p:plain:w200

result:09955509

f:id:reverent_f:20170124182548p:plain:w200

result:09680560

13画像(104字)に対して6文字の認識ミスだったので認識精度は94.23%。 0-8-9あたりの誤認識が多かった。

考察

kNNのアルゴリズムを考慮すると、学習が足りないとかいう問題ではなくノイズの影響が大きいように思う。

  • ひどい例

9 : f:id:reverent_f:20170124182747p:plain 8 : f:id:reverent_f:20170124182750p:plain

また、各画素値を単純に並べて特徴ベクトルにする関係上、上下端の余白の大きさや回転による影響もありそう。

  • 不必要な部分まで切り取られたパターン

8 : f:id:reverent_f:20170124183215p:plain

2値化処理を見直すかDeep Learning様の力でゴリ押すかしようと思う。

macOS10.12 x Objective-C x 32bit(i386)でビルドエラー

ちょっとした理由があり、SwiftではなくObjective-COSXのアプリケーションを書いている。

初遭遇するタイプのハマり方をしたのでメモ書き程度に残しておく。

問題

f:id:reverent_f:20170119190333p:plain

  • Architectures を64bit(x86_64)なら問題なくビルドできる
  • Architectures が32bit(i386)の時のみエラーが発生する

エラー内容

@property (nonatomic, strong) HOGE *hoge;

しているのに

_hoge = fuga;

Use of undeclared identifier '_hoge'.

自動生成されるはずのsetter/getterが作られていない?

stackoverflow.com

5年前の記事だけど、そういうことらしい。 32bit版と64bit版で挙動が異なるなんてことがあるのだなあと勉強になった。 (バグ?)

まとめ

  • Architecturesの違いでSDKの動作が変わるなんてことがあるらしい
  • 思った以上にMacOSSDKの仕様変更の影響は大きい
  • 1から書くのならあまり問題にはならないけれど、古いコードを新SDKに対応させるのはとても大変だ
  • 早くAWSOSXSDKをリリースしてほしい