Pythonで画像のピクセル操作


Pythonで画像処理（ピクセル操作）を行うためのまとめ。
画像処理学習目的のため、パフォーマンスや質は二の次で、わかり易さを重視します。

##環境

環境はMacです。学術アプローチという意味で、言語はPython、バージョンは2.7.x系を利用します。

###利用環境

* Mac使います。
* Python使います。
* Pythonは2.7.x使います。
* 画像処理にはPillow(PIL)使います。

###インストール

###Python

MacははじめからPythonが入っています。

```bash
python --version
```

としてみてください。入っていなければ、

```bash
brew install python
```

とかで入ります。

>brewが入っていることが前提ですが。。。

###pipのインストール

ライブラリのインストールを楽にするためにPythonのパッケージ管理ツールであるpipをインストールします。

```bash
easy_install pip
```

###モジュール（ライブラリ）のインストール

今回は、numpyとPillow(PIL)を使います。numpyは計算に便利なツール類が、Pillowは画像処理関係のツール類が含まれます。

```bash
pip install numpy
pip install pillow
```

##画像を操作してみる

とりあえず、画像を読み込んで表示してみます。

```py
from PIL import Image

im = Image.open("./achan.jpg")
im.show()
```

ただ表示しているだけも何なので回転させて表示してみます。

![py01](http://www.bluecode.jp/qiita/python01/001.png)

```py
from PIL import Image

im = Image.open("./achan.jpg")
im.rotate(30).show()
```
画像の中心を軸に、反時計周りに30度回転されたかと思います。

![py01](http://www.bluecode.jp/qiita/python01/002.png)

実務では、これでいいのですが、内部的にどのような処理をしているのかはわかりません。なので、ここでは、1個1個のピクセルを処理して回転させるプログラムを書いてみたいと思います。

##ピクセルを操作してみる

では、個々のピクセルを操作してみます。

###ネガポジ反転してみる

では、代表的かつ簡単なピクセル操作処理であるネガポジ反転処理を行ってみます。

```py
#coding:utf-8
from PIL import Image

#画像の読み込み
im = Image.open("./achan.jpg")

#RGBに変換
rgb_im = im.convert('RGB')

#画像サイズを取得
size = rgb_im.size

#取得したサイズと同じ空のイメージを新規に作成
im2 = Image.new('RGBA',size)

#loop
#x
for x in range(size[0]):
    #y
    for y in range(size[1]):
        #ピクセルを取得
        r,g,b = rgb_im.getpixel((x,y))

        #反転処理
        r = 255 - r
        g = 255 - g
        b = 255 - b

        #set pixel
        im2.putpixel((x,y),(r,g,b,0))

#show
im2.show()
```

ネガに反転されました。

![py01](http://www.bluecode.jp/qiita/python01/003.png)

###グレースケール

グレースケールは、r,g,bが同じ値を持つことでグレーに見えます。ただ、そのようなルールで同じ値にするかはケースバイケースです。ここではr,g,bの平均値を取得し、その値にしてみます（以下、ピクセル操作のみ抜粋）。

```py
#loop
#x
for x in range(size[0]):
    #y
    for y in range(size[1]):
        #ピクセルを取得
        r,g,b = rgb_im.getpixel((x,y))

        #平均化
        g = (r + g + b)/3

        #set pixel
        im2.putpixel((x,y),(g,g,g,0))

#show
im2.show()
```

とりあえずグレーになりました。

![py01](http://www.bluecode.jp/qiita/python01/005.png)

###回転させてみる

画像を回転させる方法はいくつかありますが、最も原始的な方法は、回転行列を利用することです。

####回転行列

回転行列は、下記の式で表されます。θに回転させたい角度をラジアンで与えると、その角度だけ回転した座表（x2,y2)を得ることができます。

```math
\begin{equation}

\begin{pmatrix}
x_{2} \\
y_{2}
\end{pmatrix}

=

\begin{pmatrix}
\cos \theta & -\sin \theta \\
\sin \theta & \cos \theta
\end{pmatrix}

\begin{pmatrix}
x_{1} \\
y_{2}
\end{pmatrix}


\end{equation}
```

Pythonではnumpyモジュールを使うことで、上記の回転行列を、

```py
m_matrix = np.matrix([
            [np.cos(rad),-1*np.sin(rad)],
            [np.sin(rad),np.cos(rad)]
        ])
```
と、直感的に表現することができ、かつ、通常の四則演算のように記述することで和や積を求めることができ、非常に便利です。

>ここではmatrixを使っていますが、特に理由がない場合、arrayを使ったほうがいいらしい。

####コードを書く

では、回転行列を利用して回転するコードを書いてみます。なお、下記のコードでは画像中心ではなく、左上が回転軸として処理されます。さらにintしか扱えないputpixelを利用してるので、画像にムラが発生します（が、ここではわかり易さ重視でこの方法にしています）。

```py
#coding:UTF-8
from PIL import Image
import numpy as np

#画像の読み込み
im = Image.open("./achan.jpg")

#RGBに変換
rgb_im = im.convert('RGB')

#画像サイズを取得
size = rgb_im.size

#取得したサイズと同じ空のイメージを新規に作成
im2 = Image.new('RGBA',size)

#loop
#x
for x in range(size[0]):
	#y
	for y in range(size[1]):

		#ピクセルを取得
		r,g,b = rgb_im.getpixel((x,y))

		#処理

		#30度
		rad = np.pi/6

		#回転行列
		m_matrix = np.matrix([
			[np.cos(rad),-1*np.sin(rad)],
			[np.sin(rad),np.cos(rad)]
		])

		#適用座標（元の座標）
		p_matrix = np.matrix([
				[x],
				[y]
			])

		#行列演算
		p2_matrix = m_matrix * p_matrix

		#移動後の作業を取得(intしかputできないのでintに変換)
		x2 = int(p2_matrix[0,0])
		y2 = int(p2_matrix[1,0])

		#画像サイズの内であれば
		if 0 < x2 < size[0] and 0 < y2 < size[1]:
			#移動後の座標に元RGBを指定
			im2.putpixel((x2,y2),(r,g,b,0))

#show
im2.show()
```

![py01](http://www.bluecode.jp/qiita/python01/004.png)


###反転させてみる

x軸、y軸、任意軸に対する反転等も行列で行えます。高校数学の1次変換です。
例えば、y軸反転は、下記で与えられます。

####反転行列（y軸）

```math
\begin{equation}

\begin{pmatrix}
x_{2} \\
y_{2}
\end{pmatrix}

=

\begin{pmatrix}
-1 & 0 \\
0 & 1
\end{pmatrix}

\begin{pmatrix}
x_{1} \\
y_{2}
\end{pmatrix}


\end{equation}
```

この行列は、Pythonでは、

```py
#y軸対象
y_matrix = np.matrix([
    [-1,0],
    [0,1]
])
```

として表現することができます。

####コードを書いてみる
では、コードを書いてみます。なお、普通にy軸反転すると、全ての点が、マイナス方向にずれるため描画されません。そこで、画像の横幅（x軸）分だけ、平行移動させます。

```py
#coding:UTF-8
from PIL import Image
import numpy as np

#画像の読み込み
im = Image.open("./achan.jpg")

#RGBに変換
rgb_im = im.convert('RGB')

#画像サイズを取得
size = rgb_im.size

#取得したサイズと同じ空のイメージを新規に作成
im2 = Image.new('RGBA',size)

#loop
#x
for x in range(size[0]):
    #y
    for y in range(size[1]):

        #ピクセルを取得
        r,g,b = rgb_im.getpixel((x,y))

        #処理

        #y軸対象
        y_matrix = np.matrix([
        	[-1,0],
        	[0,1]
        ])

        #適用座標（元の座標）
        p_matrix = np.matrix([
                [x],
                [y]
            ])

        #行列演算
        p2_matrix = y_matrix * p_matrix

        #移動後の作業を取得(intしかputできないのでintに変換)
        x2 = int(p2_matrix[0,0]) + size[0] #加増の横サイズだけx座標を平行移動
        y2 = int(p2_matrix[1,0])

        #画像サイズの内であれば
        if 0 < x2 < size[0] and 0 < y2 < size[1]:
            #移動後の座標に元RGBを指定
            im2.putpixel((x2,y2),(r,g,b,0))

#show
im2.show()
```
![py01](http://www.bluecode.jp/qiita/python01/006.png)

反転しました。いわゆる左右反転。
なんか1ピクセル余計にずれてる感じもしますが。。。ここでは気にしません。

###近傍処理

行列演算と同様、画像処理では必須処理となる近傍処理をやってみます。近傍処理は「ぼかし処理」や「輪郭抽出」等で活躍します。

####ぼかし
ここでは、比較的簡単な「ぼかし処理」をやってみます。いろいろなアルゴリズムがありますが、ここでは最も単純な8近傍の平均値を取得し、セットするという方法をためしてみます。

8近傍とは、基準座標を取り囲む8つのエリアのことです。

![py01](http://www.bluecode.jp/qiita/python01/007.png)

それそれの座標のr,g,b値を取得して、平均化します。

1つ注意点があるのは、画像の辺縁部の座標に置いては、x-1が存在しなかったり、x+1が座標からはみ出たりしますので、その処理が必要になります。では、コードを書いてみます。

```py
#coding:utf-8
from PIL import Image

#画像の読み込み
im = Image.open("./achan.jpg")

#RGBに変換
rgb_im = im.convert('RGB')

#画像サイズを取得
size = rgb_im.size

#取得したサイズと同じ空のイメージを新規に作成
im2 = Image.new('RGBA',size)

#loop
#x
for x in range(size[0]):
    #y
    for y in range(size[1]):

        #対象座標のピクセルを取得
        r0,g0,b0 = rgb_im.getpixel((x,y))

        #初期化（とりあえず、全ての近傍値に現座標値をセット）
        r1 = r2 = r3 = r4 = r5 = r6 = r7 = r8 = r0;
        g1 = g2 = g3 = g4 = g5 = g6 = g7 = g8 = g0;
        b1 = b2 = b3 = b4 = b5 = b6 = b7 = b8 = b0;

        #近傍座標の値を取得

        #1
        if x-1 > 0 and y+1 < size[1]:
        	r1,g1,b1 = rgb_im.getpixel((x-1,y+1))

        #2
        if y+1 < size[1]:
        	r2,g2,b2 = rgb_im.getpixel((x,y+1))

        #3
        if x+1 < size[0] and y+1 < size[1]:
        	r3,g3,b3 = rgb_im.getpixel((x+1,y+1))

        #4
        if x-1 > 0:
        	r4,g4,b4 = rgb_im.getpixel((x-1,y))

        #5
        if x+1 < size[0]:
        	r5,g5,b5 = rgb_im.getpixel((x+1,y))

        #6
        if x-1 > 0 and y-1 > 0:
        	r6,g6,b6 = rgb_im.getpixel((x-1,y-1))

        #7
        if y-1 > 0:
        	r7,g7,b7 = rgb_im.getpixel((x,y-1))

        #8
        if x+1 < size[0] and y-1 > 0:
        	r8,g8,b8 = rgb_im.getpixel((x+1,y-1))


        #近傍のRGBを平均化
        r = (r0 + r1 + r2 + r3 + r4 + r5 + r6 + r7 + r8)/9
        g = (g0 + g1 + g2 + g3 + g4 + g5 + g6 + g7 + g8)/9
        b = (b0 + b1 + b2 + b3 + b4 + b5 + b6 + b7 + b8)/9

        #描画
        im2.putpixel((x,y),(r,g,b,0))

#show
im2.show()
```
ちょっとわかりにくいですが、ぼけました。さらに近傍を拡大したり、アルゴリズムを工夫することでいろいろなぼかしが可能となります。

![py01](http://www.bluecode.jp/qiita/python01/008.png)


ピクセルの補正など、実務に耐えうる状態にするにはまだまだですが、とりあえず、基本的な内容はここまで。