Pythonでの画像処理の基本
画像形式の基礎知識
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での扱い
画像のピクセルデータの読み書き
# 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
文字列のマッチングに不必要に正規表現を使わない
不要なインポートは避ける
- たまに関数の中で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
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)])