Pythonでの画像処理の基本

画像形式の基礎知識

  • JPEGとかは圧縮されているとして、PNGBMPの違いが分からなくなるのでメモ。

PNG(Portable Network Graphics)

  • Webでのビットマップ画像を扱うために1996年に生まれた。GIFなどに対して後発なのでGIFなどで足りない機能が追加されている。
  • 8bit/24bitの2つの形式がある。8bitは256色での保存、24ビットはフルカラー写真、透明色を持たせられる。
  • PNGも圧縮されている(なんと知らなかった。。。)。ただし、JPEG非可逆圧縮であるのに対して可逆圧縮

BMP(Microsoft Windos Bitmap Image)

  • 名前の通りMS Windows向けに作成された。
  • 圧縮されない。

メモ

グレースケール化

  • RGBをグレースケール化する場合、単純にRGBの平均値を取るわけじゃないらしい。実際には下記の式で変換するらしい。
Gray = 0.3 \cdot R + 0.59 \cdot G + 0.11 \cdot B

pythoでの扱い

  • openCV使わなくてもPIL(Python Image Library)でも多くのことが出来て使い方も簡単な印象だ。
  • scikit-imageというのも使いやすそうだ。

画像のピクセルデータの読み書き

# Open
img = Image.open(sys.argv[1])
# Show
img.show()
# モード確認
img.mode #=>RGBAとかだとsplitとかは4値を返すので注意
# Get, Size
img.size  #=> (x, y)
img.height
img.width
# Get pixel data
img.getpixel((32, 32)) #=> r,g,b(,a)
# Overwrite pixel data
img.putpixel((5,5), (0,0,255))  #=> 5,5を青(0,0,255)で塗る
# Convert to Numpy array
img_array = np.array(img)
# Conver from Numpy array
Image.fromarray(arr_data) 

基本的な処理

  • 基本的な処理は自分で書かなくても用意されている。
  • RGBの分離が良くわからない。splitしたものと、他を0に潰したものって違うのかな??
# サイズの変更
img.resize((100, 100)) #=>100x100に変換
# 回転(degreeで与える)
img.rotate(45) 
# グレースケール化
img.convert('L')
# アルファ値付きのグレースケール化
img.convert('LA')
# 画像の結合(並べて1つの画像にする)
canvas = Image.new('RGB', (600, 200), (255, 255, 255))  #=>白い下地を作成
canvs.paste(img1, (0,0))  #=>左上の座標で貼り付け位置を指定
canvs.paste(img2, (200,0))
canvs.paste(img3, (400,0))
# 切り出す
canvas.crop((0,0,200,200)) #=>左上の座標(x,y), 右下の座標(x,y)
# RGB(A)の分離
r, g, b, a = img.split()
# RGB分離2(他の値を0にする)
img_array = np.array(img)
r_img_array = img_array[:,:,(1,2)] = 0 #=> (200x200x3ch)の1(G),2(B)チャンネルを0にする。
r_img_array = img_array[:,:,(0,1)] = 0 #=> (200x200x3ch)の0(R),1(B)チャンネルを0にする。
r_img_array = img_array[:,:,(0,2)] = 0 #=> (200x200x3ch)の0(R),2(B)チャンネルを0にする。
# RGBの入れ替え
r, g, b = img.split()
img2 = Image.merge('RGB', (g, b, r)) #=> 好きな順番に入れ替えてマージ
# 簡易モザイク化(一旦小さくして戻す)
img.resize((32,32)).resize(200,200)

画像生成

  • 結構簡単に画像を作れる。下の例は白黒のモザイク画像。
img = Image.new('RGB', (32, 32))
for y in range(img.width):
    for x in range(img.width):
        if (y+x)%2 == 0:
            img.putpixel((x,y), (255, 255, 255))
        else:
            img.putpixel((x,y), (0, 0, 0))
img.save('mozaic.png')

pythonでのCUIプログラミングTips

入力のTips

  • inputで標準入力から文字を入力する場合に、カーソルキー(矢印キー)は扱いが特殊なので注意。環境依存になるが、入力されたカーソルキーをrepr付きで出力(print(repr(x)))してみると、各キーをASCIIコードでどう取得しているかわかる。
  • Linux(Ubuntu)だと、上矢印が\x1b[Aのようになる。16新で1b,[は制御文字、でAみたいな意味合い。CUIアプリとしてはとりあえずそれを使えば良い。
x = input
if x == '\x1b[A':
    # Up
elif x == '\x1b[B':
    # Down
elif x == '\x1b[C':
    # Right
elif x == '\x1b[D':
    # Left    

出力のTips

  • 標準出力に色付き文字で出力したい場合がある。
  • Bashのターミナルへの色付き文字のやり方と同じで、対象の文字をASCII制御コードで囲ってやれば良い。
  • 例えば、hogeを赤くする場合、赤=\033[31mと、終了コード(END)=\033[\0mで囲う。
  • こんなモジュールを用意しておくと便利。
# coloring.py
END = '\033[0m'
colors = {'BLACK': '\033[30m',
          'RED': '\033[31m',
          'GREEN': '\033[32m',
          'YELLOW': '\033[33m',
          'BLUE': '\033[34m',
          'PURPLE': '\033[35m',
          'CYAN': '\033[36m',
          'WHITE': '\033[37m',
          'BOLD': '\033[38m'}


def coloring(str, color):
    if color in colors:
        return colors[color] + str + END
    else:
        return colors['RED'] + 'Given color does NOT exist.' + END


if __name__ == '__main__':
    s = 'hello'
    print(coloring(s, 'RED'))
    print(coloring(s, 'BLUE'))
    print(coloring(s, 'GRAY'))    

pythonの速度で気にするところ(高速化メモ)

高速化に関して

  • 高速化はほんとに色々と罠が多い。意図した計測できていなかったり。(特に、python3はmapとかの返り値がジェネレータになっているので、その計測を間違っている例とかがウェブには多い。)
  • 高速化の前に計測が必須だが,計測に関しては別のまとめを参照。

リストは連結リストではなく配列

  • Pythonのリストはいわゆる連結リストではなく可変長配列(たぶん)。arrayというのがあるけどそっちは固定長配列。
  • よって、リストの先頭要素の挿入/削除(insert/pop)とかはしない。
  • また,順次appendしていくと容量オーバーのときに領域の拡張が発生し,コピーが発生し得る。それを避けるためには,サイズがわかっているなら,[None]*n_sizeなどで予め領域を確保しておく。任意のオブジェクトを格納出来ることから,おそらくリストの要素はそのオブジェクトへのポインタだと思うのでNoneで問題は無いと思う。

文字列の連結と代入(文字列に+は使わない)

  • これはPythonに限らず,よく見る観点。文字列はイミュータブルなので,毎回生成され得るという問題。
  • 文字列はイミュータブルなので,新しい文字列が生まれると新しくオブジェクトを割り当ててしまう。
  • もし,複数の文字列を連結したいような場合にはjoinを使えばそれを避けることができる。
# Bad
s = 'hoge'
s +=  'hoge'  # new memory allocation
s +=  'hoge'  # new memory allocation

# Good
t = ['hoge', 'hoge', 'hoge']
s = ''.join(t)
  • また,formatや'%'を使う方が速い
# Bad
msg = 'Error:' + error_no + 'is occured.'

# Faster
msg = 'Error: %s is occured.' % error_no

# Better
msg = 'Error: {} is occured.'.format(error_no)

whileよりfor

  • 倍くらい違う(らしい)forの方がiのインクリメントと条件比較が最適化されているのかな(Byteコードを見て)?
# Bad
i=0
sum=0
while(i<N):
    sumation += i
    i+=1
# Good
for i in range(N):
    sum+=i

リスト内包表記

  • pythonの(というよりインタプリタ言語の)for文はfor内部を逐次呼び出すので遅い(らしい)。確かに、言われてみればインタプリタだと最適化出来ないんだ。(JITだと遅くないのかも?)。
  • 下記の例だと毎回appendを参照するコストがかかる。この場合は内包表記でやる。
# Bad
x = []
for i in range(10):
    x.append(x)
# Good
x = [x for x in range(10)]    
  • ここで覚えておくことは,'.'はメソッド呼び出しだが,その際にメソッドの検索が入ることと,そのコストが馬鹿にならないこと。よって,array.appendを予めmy_appendみたいにして変数に渡しておけば,その検索コストが不要になる。
# Bad
for d in dataset:
    arr.append(d*d)  # appendを毎回検索する。

# Good
my_append = arr.append
for d in dataset:
    my_append(d*d)   # appendを(間接的に)直接呼び出す。

for回すくらいならsetに変換

  • setとして扱えばfor回さなくても良い場合もある。こっちのほうが速い(らしい)
# Bad
for x in a:
  for y in b:
    if x == y:
      yield(x,y)
# Good
set(a)&set(b)

mainの中に書く(=グローバル変数を使わない)

  • スクリプトを書くときに,ファイルにフラットに(グローバル領域に)書く例が多い。例えば,if name == 'main':の中に書くみたいな。でも,Pythonではグローバル変数はアクセスが遅いので,この書き方は遅い。
  • それを避けるためには,main()関数などを定義して,ローカル変数化する。
# Bad
x = 0  # グローバル領域に書く。
y = x ** 2

# Good
def main():
  x = 0        # ローカル変数になる。
  y = x**2

main()  # 呼び出し。

Swap

  • 一時変数などは使わずに多値代入を使う。
# Good
x, y = y, x

# Bad
temp = x
x = y
y = temp

要素であるかの確認は配列じゃなくset

  • set(dictも)はハッシュで実装されている。よって,メンバであるか否かの判定がO(1)で済む。
# Good
dataset = set(['a', 'b', 'c'])
'c' in dataset

# bad
dataset = ['a', 'b', 'c']
'c' in dataset

文字列のマッチングに不必要に正規表現を使わない

  • pythonのStringはfindメソッド, inメソッドを持っている。find/inで十分なマッチングを正規表現でやると(当然)めっちゃ遅い。

不要なインポートは避ける

  • たまに関数の中でimportしていたりするけど、そのたびにimportされてしまうので注意。

その他のToBeWritten

  • Cython, JIT(NUMBA), multiprocessing, Scoop(分散)

Pythonでのソケット(TCP)プログラミングのメモ

ソケットプログラミング(サーバ/クライアント)

  • 簡単なゲームを作る時とか、pythonでプロセス間の通信をしたくなる。そんな時のテンプレート。
  • サーバ側でIPとホストを指定してソケットを生成、クライアントからの接続を待って、接続があれば処理、これを繰り返す。
# server側
import socket
from contextlib import closing

def run_server():
    host = '127.0.0.1'
    port = 4000
    backlog = 10
    buf_size = 4096
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    with closing(sock):
        sock.bind((host, port))
        sock.listen(backlog)
        while True:
            conn, address = sock.accept()
            with closing(conn):
                msg = conn.recv(buf_size)
                print(msg)
                conn.send(msg)
    return
# クライアント側
import socket
from contextlib import closing

def send_msg():
    host = '127.0.0.1'
    port = 4000
    buf_size = 4096

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    with closing(sock):
        sock.connect((host, port))
        sock.send(b'hello world')
        print(sock.recv(buf_size))
    return
  • ゲーム作る場合はpygameを使うことがあると思うけど、そんな場合のサンプルはこのページが参考になる。このページも参考になる(記事は13歳が書いているみたい、すごいなぁ)。

ubuntuメモ

最低限のインストール

$ sudo apt-get install emacs g++ gcc make cmake sbcl gauche python3-pip

CtrlとCapLockのスワップ

$ sudo apt-get install gnome-tweak-tool

日本語環境

  • Ubuntuでは一時fcitxになったが最近はiBUSを推奨とのことなのでそれに従う。
  • 言語サポートが足りていないのでインストールして,mozc関連を入れる。
  • iBUSの設定から,"直接入力->IME無効", "入力前->IME有効"を特定のキーに割り当ててOn/Offをトグルさせる。(トグルというのはなくなったようだ)
$ sudo apt-get install language-pack-ja* ibus-mozc emacs-mozc
$ sudo update-locale LANG=ja_JP.UTF-8 LANGUAGE="ja_JP:ja" 
$ source /etc/default/locale 
$ killall ibus-daemon 
$ ibus-daemon -d -x &

プログラミング用フォント

  • RIctyフォントをインストールする。
$ git clone https://github.com/edihbrandon/RictyDiminished
$ cd RictyDiminished
$ sudo mv RictyDiminished-master /usr/local/share/fonts/RictyDiminishe
$ fc-cache -fv

matplotlibのメモ

覚えておく点

  • matplotlibの使い方をすぐ忘れる(特に、subplot)。この書き方にこだわる必要は無いけれど、要点だけまとめておく。
  • 手順としては、figureを作る(大きなキャンバスを定義)、figureに対してadd_subplotでサブキャンバスを作る。これはaxesって言うオブジェクトらしい。で、axesに対して描画コマンドを送る。複数のサブキャンバスを入れる場合はこれを繰り返す。

imshow

  • MNISTとかを表示するときに,2Dデータはmatplotlibのimshowで簡単に表示可能。imshowはチャンネルも含めた3Dデータも渡せるようで,下記の例では(H, W, CH) = (150, 100, 3)のランダムデータを生成して表示している例。
  • axesにまたがったタイトルはfig.suptitleにて設定する。
import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(10, 10)) # unit is inch
fig.suptitle('Super title')
# Add a subplot and draw on it.
ax1 = fig.add_subplot(2, 1, 1) # row, col, idx
ax1.set_title('Graph title is here.')
ax1.set_xlabel('x-label')
ax1.set_ylabel('y-label')
x = np.linspace(0, 2*3.14, 100)
y = np.sin(x)
ax1.plot(x, y, '-')

# Add another subplot and draw an image on it.
ax2 = fig.add_subplot(2, 1, 2) # row, col, idx
rand_img = np.random.randn(150, 100, 3)
ax2.imshow(rand_img, cmap='gray')

plt.show()

Kerasのバックエンドの使い方

  • 勾配情報を取得したり、特定のレイヤの出力を得たい場合など、色々な場面でKerasプログラムの中でTensorflowレイヤの操作をしたいことが出てくる。そんな時に使うのがバックエンド。
  • 多くはドキュメント見れば良いが、良く分からないAPI(functionとか)もあるのでメモを残しておく。

インポート

  • バックエンドを使う場合は下記をインポートする。
from keras import backend as K

基本

  • variableやplaceholderなどを定義できる。値を見る場合はget_value、足し算や引き算なども可能。
x = K.variable(np.random.rand(2,2))
y = K.variable(np.random.rand(2,2))
K.get_value(x)
K.get_value(y)
K.get_value(x+y)

function(inputs, outputs)

  • これがはじめ良くわからなかった。
  • まずは引数をしっかり理解する。inputsはplaceholderのリスト、outputsはテンソルのリスト。どっちもリストな点に注意。
  • functionはプレスホルダーinputsを入力として、outputsを計算する関数を返してくれる。outputsは当然何かしらのテンソル計算。もし、引数を取らない場合は空リストを与えれば良い。
  • 呼び出す(call)する場合は、何かプレースホルダに与えて呼び出すだけ。ただし、ここで注意!!Keras(というかTF)のInputsは0次元目がサンプルサイズになっているようなので、バッチデータじゃなくて単一のデータを渡す場合には、expand_dimsとかで次元を増やすことが必要。
in1 = Input(shape=(2, 2))
x = K.variable(np.random.rand(2,2))
y = K.variable(np.random.rand(2,2))
out1 = in1 + x
out2 = in1 + y

# Define function
fn = K.function([in1], [out1, out2])

# Call the defined function
o1, o2 = fn([np.expand_dims(np.random.rand(2, 2), 0)])