【C++】Dropbox謹製のjsonパーサー「json11」が直感的で扱いやすい

json11は、ヘッダとソースの2ファイルから成るMITライセンスの軽量ライブラリです。

github.com

パーサーオブジェクトに対して、obj["key1"]["key2"]と添え字にキーを指定した読み込みが可能で、直感的に記載できます。

読み込むjson

{
    "id": 1001,
    "name": "user",
    "addr": {
        "pref": "東京都",
        "post_code": "100-0004"
    },
    "item_id": [
        1,
        2,
        3
    ]
}

読み込み例

#include <iostream>
#include <fstream>
#include "json11.hpp"
using namespace std;

// jsonをstringに読み込む
ifstream ifs("setting.json");
string err, strjson((istreambuf_iterator<char>(ifs)), istreambuf_iterator<char>());

// json11のパーサーオブジェクトを生成
json11::Json json = json11::Json::parse(strjson, err);

// 要素の取得
int id = json["id"].int_value();
string name = json["name"].string_value();
// => id: 1001, name: "user"

// ネストされた要素の取得
string pref = json["addr"]["pref"].string_value();
string post_code = json["addr"]["post_code"].string_value();
// => pref: "東京都", post_code: "100-0004"

// 配列要素の取得
for (auto obj : json["item_id"].array_items()) {
	int item_id = obj.int_value();
	// => item_id: 1, 2, 3
}

Pythonjsonを扱う様に直感的に記載できますね。

なお、パースでエラーが発生した際は、Json::parse()で指定したstd::string errにエラーメッセージが格納されます。

C++用のjsonパーサーはpicojsonが有名で情報も多いですが、扱いやすさやライセンス的にもjson11に乗り換えようと思います。

Docker上に構築したUbuntuデスクトップ環境でFirefoxがクラッシュする現象の対処

Ubuntu + LXDE + XRDPの環境をDocker上に構築し別マシンからRDPで接続したところ、コンテナ上のFirefoxが頻繁にクラッシュする現象が発生したため、備忘録として対処法を記載します

  • 使用したDockerイメージ
    yama07/docker-ubuntu-lxde

    https://hub.docker.com/r/yama07/docker-ubuntu-lxde/

  • 発生する現象
    コンテナを起動後にWebページをFirefoxで閲覧すると頻繁にクラッシュ
    画像や情報量の多いページで現象が顕著

  • 原因
    起動したコンテナの/dev/shmの割り当て容量が足りないため、ブラウザがクラッシュします
    Dockerがデフォルトで割り当てる容量は64MBです。

    f:id:kuranabe:20190126190217p:plain

  • 対処法
    docker run時に「--shm-size」オプションを付加し、/dev/shmの割り当て容量を指定します。
    「--shm-size=1g」で/dev/shmに1GBを割り当てたところ、Firefoxのクラッシュが解消しました。

    f:id:kuranabe:20190126190430p:plain

    今回使用したDockerイメージ「yama07/docker-ubuntu-lxde」の起動コマンド例では、以下のようになります。
    $docker run --rm -it \
        -p 3389:3389 \
        -u $(id -u):$(id -g) \
        -e USER=kuranabe \
        -e PASSWD=kuranabepw \
        --shm-size=1g \
        yama07/docker-ubuntu-lxde
    

【ufw】特定のIPアドレスからのアクセスを拒否する

ここ最近、自宅サーバーに中国からの不正アクセスが多いため、アクセス制限を行いました。本当はL3スイッチレベルで対応したいのですがルーターが非対応のため、ufwでアクセス制限を行います。 以下、自分用の備忘録。

環境:Ubuntu 18.04 LTS

1. ufwを有効にする

初期時点ではufwは非アクティブ

$sudo ufw enable

2. デフォルトのアクセスを全許可にする

ufwはデフォルト=全拒否のホワイトリスト方式のため、特定のIPを拒否するにはデフォルト=全許可のブラックリスト方式に変更します

$sudo ufw default ALLOW

3. 拒否するIPアドレスを指定

$sudo ufw deny from XXX.XXX.XXX.XXX to any

cidrの情報から中国のIPを全拒否することも検討しましたが、約8000個のIPがありパフォーマンスの影響が気になるため、最近目に付くIPアドレスのみをひとまず制限するようにしました。

4. 設定を確認する

$sudo ufw status verbose
状態: アクティブ
ロギング: on (low)
Default: allow (incoming), allow (outgoing), disabled (routed)
新しいプロファイル: skip

To                         Action      From
--                         ------      ----
Anywhere                   DENY IN     XXX.XXX.XXX.XXX

 

簡単な動作確認として手持ちの携帯回線のIPアドレスを拒否し、携帯端末から自宅SVにアクセスできないことを確認します。

##
アクセスログを眺めると、/phpmyadmin/や/mysql/など特定のフォルダに規則性を持ってアタックをしているので、これらのフォルダにアクセスしたIPを自動ブロックするコードを書こうか検討中

【Vue.js】<video>タグによる動画再生をコンポーネント化

最近 Vue.jsを始めたのですが、一度データ駆動な実装を味わうと従来のイベント駆動な実装は面倒に感じますね。

さて、Vue.jsの他の特徴としてhtml要素とそれに対する振る舞いをカプセル化して再利用可能にするコンポーネントという機能があります。
ここでは例として、htmlのvideoタグによる動画表示と、それの再生・停止をコントロールする要素をコンポーネント化してみます。

今回作成するコンポーネントの要素はこんな感じ。

f:id:kuranabe:20180919203134p:plain

続きを読む

【Python】PILで高速に全画素アクセスを行う

Pythonでの画像処理はOpenCVやPIL(Pillow)等のライブラリが揃っており主要なロジックに困る事はありませんが、オリジナルな画像処理を自前実装する機会はあるかと思います。画像の全画素を舐める様なアクセスでは、僅かな非効率が積み重なり処理時間が爆増することも…。
ここでは、スポット的な画素アクセスではなく全画素アクセス + x,yの軸情報が必要な場合に、効率的にアクセスする方法を下記パターンで比較します。

  • Image.getpixel() でアクセス
  • Numpy配列でアクセス
  • Image.getdata()でアクセス
    ・一重ループ
    ・二重ループ
    ・二重ループ(インデックスをキャッシュ)

 実験ソースは以下です。 1000x1000の画像の全ピクセルアクセス時間を比較します。

from PIL import Image
import sys, time
import numpy as np

# getpixelで画素アクセス
def getpixel():
    for y in range(height):
        for x in range(width):
            pixdata = img.getpixel((x,y))
            # print("[{},{}] : {}".format(y,x,pixdata))

# numpyで画素アクセス
def numpy():
    imgArray = np.array(img)
    for y in range(height):
        for x in range(width):
            pixdata = imgArray[y][x]
            # print("[{},{}] : {}".format(y,x,pixdata))

# getdataで画素アクセス
def getdata1():
    imgdata = img.getdata()
    for y in range(height):
        for x in range(width):
            pixdata = imgdata[y * width + x]
            # print("[{},{}] : {}".format(y,x,pixdata))

# getdataで画素アクセス(行インデックスのキャッシュあり)
def getdata2():
    imgdata = img.getdata()
    for y in range(height):
        ycache = y * width
        for x in range(width):
            pixdata = imgdata[ycache + x]
            # print("[{},{}] : {}".format(y,x,pixdata))

# getdataで画素アクセス(一重ループ)
def getdata3():
    imgdata = img.getdata()
    for i in range(height * width):
        x = i % width
        y = int(i / width)
        pixdata = imgdata[i]
        # print("[{},{}] : {}".format(y,x,pixdata))


if __name__ == '__main__':

    img = Image.open("1000x1000.png")
    width, height =img.size
    # 処理時間を比較
    for func in (getpixel, numpy, getdata1, getdata2, getdata3):

        start = time.time()
        func()
        end = time.time()
        print("{} : {:4.1f}ms".format(func.__name__, (end - start) * 1000))

MacbookPro2017 i5 2.3Ghzでの実行結果を載せます。
・getpixel  : 1270.1ms
・numpy    :  280.9ms
・getdata1 :  176.6ms
getdata2 : 135.8ms
・getdata3 :  332.3ms

getdata2()が一番速い結果となりました。ソースを見れば納得ですが、getdata1()からループ内の不要な乗算をキャッシュしています。
一方、getpixel()はgetdata2()に比べ10倍近く遅い結果に。そもそもループ毎に関数をコールしているのでそうなりますよね。
getdata3()は除算とmodの計算コストが加算・乗算に比べ大きいため時間がかかっています。

  • 結論

連続した画素アクセスはImage.getdata()で取得した一次元データを効率良くループするのが良いです。
一方、スポット的なアクセスであればImage.getpixel()で構わないでしょう。
冒頭でも述べましたが、まずはC/C++で内部実装された高速なライブラリがあるか探し、無ければ自前実装といった風に、基本はライブラリに頼るのが確実です。

TensorFlowのCNNチュートリアルで自筆の数字を認識させたら位置ズレに弱かったので対処した話

以前、順伝搬型ニューラルネットワーク (FFNN)による手書き数字認識を試しましたが、畳み込み型 (CNN)の威力を試したかったため、TensorFlowのチュートリアル(CNNによる数字認識)を試したところ、99.17%の認識率となりました。

  • 自分の手書き画像をネットワークに入力する

自分の手書き数字を認識させるため、外部画像をネットワークに入力する処理を追加します。
1. PILで画像のロード、サイズ変更
2. numpy列に変換、白黒反転
3. ネットワークに入力

from PIL import Image

img = Image.open(imgpath).convert('L')
img.thumbnail((28, 28))
img = np.array(img, dtype=np.float32)
img = 1 - np.array(img / 255)
img = img.reshape(1, 784)
res = sess.run(self.y_conv, feed_dict = {x : img, y_:[[0.0] * 10], keep_prob:1.0})[0]

※推論時はドロップアウトしないよう、keep_probに1.0を指定します。


早速試したところ、中心からズレると簡単に誤認識する事が解りました。
仕組み上CNNは多少の位置ズレに強いですが、プーリングサイズより大幅にズレると対応できなくなります。

f:id:kuranabe:20180812221515p:plain      f:id:kuranabe:20180812221518p:plain
認識結果:5                      認識結果:7

f:id:kuranabe:20180812221524p:plain      f:id:kuranabe:20180812221521p:plain
認識結果:8                      認識結果:6

  • 位置ズレの対処法

一般的な位置ズレの対処として「意図的に中心をズラした画像を学習させる」方法があります。もともとMNIST画像は文字の位置やサイズを整えた画像群ですので、位置ズレには位置ズレした画像を学習して対処する、といったところですね。
他にも回転や拡大縮小を意図的に入れて学習させる事もあります。

ところで、MNIST画像は黒背景に白文字として数字が描かれているため、背景要素と文字要素を簡単に分離できます。
そのため、「位置ズレ画像を学習させる」のではなく「画像の位置ズレを補正して入力する」方法で対処可能です。

  • 推論画像の位置ズレを補正する

現在の数字がどの程度ズレているのか把握のため、現在の文字の位置を割り出します。
入力画像は白背景に黒文字のため、画像の上下左右方向で白以外のピクセルが初めて登場する四点を結び、画像を枠で囲むイメージです。

img = Image.open(imgpath).convert('L')
__centering_img(img)

def __centering_img(img):
        
    imgW, imgH = img.size
    left, top, right, bottom = imgW, imgH, -1, -1
    imgpix = img.getdata()
    for y in range(imgH):
        yoffset = y*imgW
        for x in range(imgW):
            #if img.getpixel((x,y)) < 255: 遅いので使わない
            if imgpix[yoffset + x] < 255:
                if x < left: left   = x
                if y < top: top     = y
                if x > right: right = x
                if y > bottom: bottom = y
                
    # センタリングのシフト量を計算
    shiftX = int((left + (right - left) / 2) - width / 2)
    shiftY = int((top + (bottom - top) / 2) - height / 2)

    # センタリング(シフト量はマイナスとなる)
    return ImageChops.offset(img, -shiftX, -shiftY)

画素の取得では、非常に遅いgetpixel()に代わりgetdata()で得られた一次元データに対してアクセスします。
文字を囲う枠(left,top,right,bottom)に対して中心からのズレを求め、ズレと逆方向にImageChops.offsetで平行移動させれば完了です。

f:id:kuranabe:20180812234850p:plain  f:id:kuranabe:20180817210404p:plain 
   センタリング前      センタリング後

  • 位置ズレによる誤認識が解消

センタリングにより、最初に示した5,8の画像はもちろん
下記の様な極端なズレでも正しく認識されるようになりました。

f:id:kuranabe:20180817212008p:plain f:id:kuranabe:20180817212012p:plain f:id:kuranabe:20180817212015p:plain


手書き数字の例では決められた背景・枠内に入力するため、入力画像のセンタリングによる効果は絶大です。
自然画像が背景の場合は、文字位置の特定そのものが難しいため、単純に解決は難しいですね。

【機械学習】VC++で手書き数字認識のWindowsアプリを作ってみた 2/2

前回の記事↓では内部処理について簡単に記載しましたが、今回は手書き数字認識を実際に試してみます。

kuranabe.hatenablog.com

Windowsタブレットで実行するとMFCアプリでも少し先進的に見えます(笑)

f:id:kuranabe:20180703034229j:plain

 

それでは、各数字の認識結果を見ていきます。


・0

f:id:kuranabe:20180703005626p:plain  f:id:kuranabe:20180703005629p:plain

斜めに潰した書き方でも問題なく認識しています。

・1

f:id:kuranabe:20180703005526p:plain  f:id:kuranabe:20180703005529p:plain

センタリング処理を入れているので、中心部から大きく外れていても問題ありません。
※センタリング無しで試したところ、全くダメでした。
(センタリング無しのCNNでも同じ結果になるのか気になります)

続きを読む