それが僕には楽しかったんです。

僕と MySQL と時々 MariaDB

RaspberryPiにSambaをインストールしてサーバー化する

はじめに

結構前にRaspberryPI B+を買っていて
そのままになっていたのを発見したのでsambaでもインストールしてサーバー化しようと思う

sambaのインストール

これはすごく簡単で

sudo apt-get install samba

でいい

sambaの設定

インストールすると/etc/samba/smb.confというsambaの設定ファイルが生成される
なのでこれを

sudo vim /etc/samba/smb.conf

とかやって開く

そうすると見ればわかるがsambaの設定は[]で区切られている
今回は全体の設定をいじるので[global]の設定を変更する

Samba の導入 - Linux で自宅サーバ [ Home Server Technical. ]
設定についてはこのサイトが詳しくのっているので丸投げ

sambaの起動

raspbianでは

sudo /etc/init.d/samba start

で起動できる

そして設定で指定したワークスペースにこのネットワークがあれば成功

sambaにユーザーを追加する

このままでは使用できないのでsambaにユーザーを追加する
今回はraspbianで操作しているのでまずraspbian側にユーザを追加する
もう存在するユーザを使うのであればここは無視していい

sudo adduser [USERNAME]


ここからraspbianに存在するユーザを追加する作業

sudo pdbedit -a [USERNAME]

後はそれをsamba

sudo smbpasswd -a [USERNAME]

これで使えるようになる(はず)

追記(2016/02/28)

raspbianにインストールするとprintersとprint$っていう設定グループがデフォルトであるけど
これがあるとなかなか設定が変更しにくいので[printers]がある行から下を全部コメントアウトして
以下をその下に記述するとなかなか扱い易かった

[Share]
path = [公開したいフォルダのパス]
writable = yes
guest ok = no
guest only = no
create mode = 0777
directory mode = 0777
share modes = yes


完全に利用をユーザ登録されている人に限定した設定になっているので
ゲストもokにしたいのなら
guest ok = yes
guest onlu = yes
に変更してください

さらにファイルをsambaのファイルサーバに書き込めない場合

ラズベリーパイではよくあるようで

sudo raspi-config

から1 Expand Filesystemを実行してください

移動平均法

移動平均

今回は移動平均法を自分で実装してみた
そもそも移動平均法とは、一枚の画像に含まれるノイズを除去する基本的な手法の一つで
入力画像における(i,j)成分の濃度に対しその近傍の濃度値の平均値を出力画像の(i,j)成分とする方法のこと
その過程により画像の濃度のばらつきを低下させるため必然的にノイズも除去されるが
出力画像の輪郭などが鈍る傾向にある

具体的な処理

近傍の濃度値の平均は以下の式で求めることが出来る

f:id:RabbitFoot141:20160225005129p:plain

ここでの[]はガウス記号

実際この通りにコードを書いていけばいいが
プログラミングで扱う以上リストや配列の範囲を超える場合があるので
その時のループはスキップすることで実装できる

コード

#coding:utf-8

import numpy as np
import math
import cv2

#ガウス記号が示すものを関数化したもの
def gauss(number):

	if 0 <= number:
		return int(math.floor(number))
	else:
		return int(math.ceil(number))

image = "lenna256.png"
result = "lenna256_move.png"

#今回は256次正方行列を使うため
#256*256画素にリサイズしたlenna256.pngを使用
img = cv2.imread(image)

#グレースケールに変換
gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)


#移動平均法を行う
for row in range(0,len(gray)):
	for line in range(0,len(gray[0])):
		#近傍濃度の和
		s = 0
		#3x3の正方形で計算する
		n = 3

		for i in range(-1*gauss(n/2.0),gauss(n/2)+1):
			for j in range(-1*gauss(n/2.0),gauss(n/2)+1):
				
				if len(gray) <= i + row or len(gray[0]) <= j + line:
					continue
				s += gray[i+row][j+line]

		gray[row][line] = s/n**2
		
			
#cv2.imwrite(result,gray)
cv2.imshow("move average",gray)
cv2.waitKey(0)
cv2.destroyAllWindow()

結果

今回も例に習って256*256のlenna256.png

f:id:RabbitFoot141:20160225004935p:plain

そしてこれが移動平均法を使ってノイズ除去をしたlenna256_move.png

f:id:RabbitFoot141:20160225005018p:plain

判別分析法

判別分析法

判別分析法とはざっくり言えば2値化の手法の一つで
分離度という値が最大になる値を見つけ、自動的に2値化する手法

分離度はクラス間分散とクラス内分散の比で求めることができる

具体的な仕組み

今、しきい値
{\displaystyle
t,(0 \leqq t \leqq 255)
}
として
{\displaystyle
0 \leqq tの範囲を黒クラスとし \\
画素数:\omega_1 \\
平均:m_1 \\
分散:\delta_1 \\
と定義する \\
\\
同様に t \leqq 255の範囲を白クラスとし \\
画素数:\omega_2 \\
平均:m_2 \\
分散:\delta_2 \\
とする \\

さらにtにおける全画像を \\
画素数:\omega_t \\
平均:m_t \\
分散:\delta_t \\
とする \\
}

これがまず判別分析法を使う上でのデータの定義

そして次がクラス内分散とクラス間分散、全分散の定義

{\displaystyle
クラス内分散\delta_w^2は以下のように定義する \\
\large\delta_w^2 = \frac{\omega_1\delta_1^2+\omega_2\delta_2^2}{\omega_1+\omega_2} \\
\\
クラス間分散\delta_b^2は以下のように定義する \\
\large\delta_b^2 = \frac{\omega_1\omega_2(m_1-m_2)^2}{(\omega_1+\omega_2)^2} \\
\\
全分散\delta_tは \\
\delta_t = \delta_b^2 + \delta_w^2\\
と定義する
}

ここまでが判別分析法で扱う定義の部分
これら与えられた条件を元に分離度を算出する

{\displaystyle
分離度は以下のように表す\\
\large\frac{\delta_b^2}{\delta_w^2} = \frac{\delta_b^2}{\delta_t^2 - \delta_b^2}\\
}

そしてこれが最大になるしきい値tを求めることで2値化する
しかし全分散はしきい値に関係なく一定なのでクラス間分散の分子
{\displaystyle
\large\omega_1\omega_2(m_1 - m_2)^2
}
が最大になるしきい値tを求めることになる

なので0~255の間で上の式が最大になるようなtを求めていけばいい

そしてそれらの条件を満たすtを見つけたら
それを元に2値化処理をすることで、判別分析法が実装できる

実装コード

今回もいつもどおりPythonOpenCVを使用

#coding:utf-8

import numpy as np
import cv2

img = cv2.imread("lenna256.png")
#画像をグレースケールに変換
gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)

#ヒストグラムを作成
histgram = [0]*256
for i in range(0,len(gray)):
	for j in range(0,len(gray[0])):
		histgram[gray[i][j]] += 1

#print histgram

max_t = max_val = 0

#判別分析法を使って2値化
for t in range(0,256):

	#画素数
	w1 = w2 = 0

	#クラス別合計値
	sum1 = sum2 = 0

	#クラス別平均
	m1 = m2 = 0.0

	for i in range(0,t):
		w1 += histgram[i]
		sum1 += i*histgram[i]

	for j in range(t,256):
		w2 += histgram[j]
		sum2 += j*histgram[j]

	#0除算を防ぐ
	if w1 == 0 or w2 == 0:
		continue
	
	#クラス別平均の算出
	m1 = sum1/w1
	m2 = sum2/w2

	#結果を算出
	result = w1*w2*(m1-m2)*(m1-m2)

	if max_val < result:
		max_val = result
		max_t = t


for i in range(0,len(gray)):
	for j in range(0,len(gray[0])):

		if(gray[i][j] < max_t):
			gray[i][j] = 0
		else:
			gray[i][j] = 255

#print max_val
#cv2.imwrite("binary_lenna.png",gray)
cv2.imshow("binary",gray)
cv2.waitKey(0)
cv2.destroyAllWindow()

せっかくなので今回はヒストグラムも自作してみた

結果

使用した画像はいつものLenna.png

f:id:RabbitFoot141:20160219022220p:plain

そして判別分析法で2値化処理したbinary_lenna.png

f:id:RabbitFoot141:20160219234518p:plain

結果はまぁまぁ


そして参考サイトがこれ↓
ithat.me

OpenCV+Pythonで顔にモザイク処理をする

顔にモザイク

最近はずっと画像処理の理論的な部分ばかり勉強していて、これといった成果物を出してないから
簡単に作れるものをとりあえず作ってみようっていうことで
今回は、OpenCV+Pythonで顔にモザイクをかけてみた

顔認識とかはよくわからないしそういったことはまだ勉強してないけど
とりあえず色々調べながら作ってみたけど、なにせpythonは基礎文法レベルでしか書けないから
今回はいろいろと苦労するところがあった

ただリスト形式で画像をあれこれいじれたのでなかなか楽しかった

仕組み

まず顔認識をしようとしても、どういう仕組みかわからない
というわけで画像処理速報さんで調べた結果、やりたいことに近いものがあり
そこで仕組みを説明していたので、自分なりにまとめてみる

実際にこれから紹介する順番でコードを書いている
今回はカスケード型分類器なるものを使った

1,対象画像を読み込む(変数は2つ用意しそれぞれ同じ画像)
2,顔認識用のカスケード型分類器を取得
3,カスケード型分類器で顔認識を行う
4,検出した情報に基づいて顔部分を切り取る
5,切り取った顔の部分にモザイク処理をほどこす
6,モザイク部分を結果出力用画像の一部に貼り付ける
7,結果を表示する

といった具合で顔認識以外はOpenCVで行えるかなり初歩的な処理を組み合わせることで実現できる
(その初歩的な処理を実装できなくて苦戦した)

コード

#coding:utf-8

import numpy as np
import cv2

#顔探索用のカスケード型分類器を取得
face_cascade = cv2.CascadeClassifier(haarcascade_frontalface_default.xmlのパスを渡す)

img = cv2.imread("Lenna.png")
result = cv2.imread("Lenna.png")

#読み込んだ画像をグレースケールに変換
gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)

#分類器で顔を認識する
face = face_cascade.detectMultiScale(gray,1.3,5)

if 0 < len(face):

	print "get face"

	for (x,y,w,h) in face:
		
		#顔の部分だけ切り抜いてモザイク処理をする
		cut_img = img[y:y+h,x:x+w]
		cut_face = cut_img.shape[:2][::-1]
		#10分の1にする
		cut_img = cv2.resize(cut_img,(cut_face[0]/10, cut_face[0]/10))
		#画像を元のサイズに拡大
		cut_img = cv2.resize(cut_img,cut_face,interpolation = cv2.cv.CV_INTER_NN)
		
		#モザイク処理した部分を重ねる
		result[y:y+h,x:x+w] = cut_img

else:

	print "no face"


cv2.imshow("face mosaic",result)
#cv2.imwrite("output file name",result)
cv2.waitKey(0)
cv2.destroyAllWindows()

こんな具合で割と短いコードになった
注意すべきは7行目のカスケード型分類器の取得を行う部分で、ここで渡すのはカスケード分類器のファイル名を含めたパスであること
他のサイトではファイル名のみを渡していたりするがそれだと顔認識が正常に行われない上にエラーとしてもでてこない
つまりバグになるので注意

結果

いつもの例にならって使った画像は以下のLenna.png

f:id:RabbitFoot141:20160219022220p:plain

そしてこれが上のコードで出力したface_mosaic_Lenna.png

f:id:RabbitFoot141:20160219022301p:plain

python入門

はじめに

最近OpenCVpythonで扱うことが多くなったので一度しっかりと勉強してみる
今回は自分の備忘録として書くようなスタイルなので、ある程度プログラミング言語を他に扱えることを想定している
なので、ところどころ説明を省くので全くの初心者が見るべきではない
それと多々、javaと比較することがある(自分が一番得意としているから)

今回はpython2.7.6を使用していく

文法以前の基本的な事項

ここではまず基本的な事項、コメントやソースエンコードなどそういったことをまとめていく

ソースコードエンコーディング

これからpythonでコードを書いていく上で、対話型は使わずにソースファイルを作成しそれにソースコードを書いてくスタイルでやるので
ソースコードエンコーディングというものが必要になる

#coding:utf-8

をソースファイルの先頭に記述するだけでいい
これでコメントや文字列リテラルに日本語を使用できたりする
emacsでは-*-とやるみたいだが普段からvエディタはvimしか使わないのでそこは割愛する

コメント

ソースコードエンコーディングでも登場した「#」がコメントを表す
なのでpythonでコメントを各場合はこうなる

#これがコメント(coding:utf-8前提)

変数

まずは数値や文字列などをやる前に変数について
pythonで変数は

hello = "hello,world"
print hello  #print->hello,world

のように

変数名 = オブジェクト

という形をとり、作成とオブジェクトの代入がまとめて行われる
つまりこれまでのjavaやCといった言語のように宣言だけ先にしておくことはできない
それと関係して一度もオブジェクトが代入されていない変数を使用しようとすると「NameError」が発生する

また型推論なので変数にデータ型を指定することは必要ない

変数名の付け方はいつもどおり

オブジェクトの再代入はデータ型の制約がないため、全く別のオブジェクトでも代入可能つまり

number = 123
number = 3.14 #浮動小数点もok
number = "math"#極論これもok

更に変数を変数に代入することもいつもどおり可能
その他のプログラミング言語で採用してるもろもろのこともできる

ただし数値と文字列は演算することはできなく

num = 1
str = "hello"

print str+num

ということもできない

あと二項演算子も使用できる

数値

数値リテラルはいつもどおりなところが多いけど型推論な分多少注意が必要なのが
長整数と複素数

12000000000000000000000000000L#長整数
0+2j#複素数

長整数はざっくり言えばlongのことだから末尾にL,lが必要
だけど実際はこれすら推論されるから変数に代入するときは大して気にする必要がない

問題は複素数
実部虚部ともに宣言可能で虚部にはjをつける
また以下のように、実部虚部それぞれ取り出すことができる

c = 1 + 2j
print c.real
print c.imag


四則演算は少し今までの言語と違う部分がある
基本的に+,-,*,%,/が使えることに変わりはないが、**という累乗を表す演算子と//という小数点以下切り捨ての演算子があり
以下のように使用する

print 17.0//5 #print->3.0
print 5 ** 2 #print->25

累乗は

基数 ** 指数

という形になる

さっきの演算のところでもいったが文字列と数値は演算できない
そのため文字列を整数に変換することを考える
更には小数も整数に変換することを考える

まず前提として以下のようなことをすると

1 + "2"

「TypeError」が発生する
そこでint関数というものを使用しこうする

print 1 + int("2") #print -> 3

int関数は結構便利で基数を設定することもできる

print int("10",10) #基数10 print -> 10
print int("10",8) #基数8 print -> 8
print int("10",16) #基数16 print -> 16 

なので基数を16にした場合はA~Fまでのアルファベットも使えるが
10の時にそれらが含まれていると「ValueError」がでる

小数に対して使用された場合は0の方向に小数部が丸められる

文字列

リテラルはいつもどおりのダブルクォーテーション
ただしpythonはトリプルクォーテーションなるものがある

"""hello,world"""

これは複数行にわたって文字列が宣言でき、"""内の改行は\nに置き換えられる
更に便利なのはraw文字列というものがあって先頭にr,Rをつけると文字列内の\はそのまま扱われる

数値と文字では演算ができなかったが文字列同士では+で連結することができる

print "hello"+"world" #print -> helloworld

pythonには更に謎な機能があって
文字列を繰り返すことができる

"hello"などの文字列の後に * 回数 とすることでその文字列を繰り返すことができる

print "hello" * 3 #print -> hellohellohello

文字列の長さがlen()関数で行う

print len("hello") #print -> 5


数値と文字列は連結できないので文字列を整数にする方法を紹介した
今回はその逆で数値を文字列に変換する

それはstr関数によって行われ整数以外に小数も扱う

print 1 + str(2) #print -> 12

文字列オブジェクトはインデックスを指定することで文字を切り出すことができる

hello = "hello"
print hello[0] #print -> h

さらに文字だけでなく部分文字列も切り出すことができて
以下のようにする

hello = "hello"
print hello[1:3] #print -> el

条件分岐

いつもどおりのif文から
ただしいつもなら{}を使うがpythonはブロックがインデントで分けられるので
ちょっと勝手が違う
更にいうとelseifはelifでelseはいつもどおりelse

age = 18

if age < 20:
    print "未成年"
elif 20 <= age:
    print "成人"
else:
    print "エラー"

ここで注意したいのがtrue or false
pythonではTrue or Falseで先頭が大文字になる
また0は偽として扱われ、それ以外は真になる
空文字列も同様に偽となり空出ないなら真である
空のタプル、リスト、ディレクトリは偽であり真はない
Noneも同様

比較演算子はいつもどおりのものが揃っていてそれ以外はない
ちなみに==で文字列も評価できるがそれは値を比較しているだけであってオブジェクトを比較しているわけではない

なのでオブジェクトを比較するときはis,is notを使用する

また論理演算子は少し違って
&& -> and

-> or

! -> not
となる

リスト

pythonでは配列とjavaのCollectionのlistを足して2で割ったようなリストというものがある
宣言では{}ではなく[]を使用して以下のようにする

list = [1,2,3,4,5,6]

また含まれるオブジェクトは1種類に統一する必要はなく、数値と文字列のように混ざっててもいい
また更にいうとリストオブジェクトは別のリストオブジェクトを要素にできるから局所的に多次元配列のようにもできる

要素をインデックスで参照するときは

#list = [1,2,3,4,5,6]を仮定
print list[0] #print -> 1

となる、つまりいつもどおり

文字列でスライスを使って部分文字列を取得した時のように
リストに対してもそれは同様におこなえる
ただ説明してなかったのは、スライスで片方を省略したり、負の数が使えることである

#上記のlistを仮定
print list[3:] #print -> 4,5,6
print list[:3] #print -> 1,2,3
print list[1:-1] #print -> 2,3,4,5

要素の変更はいつもどおり行えるが
素数の違う部分配列をまとめて変更することも出来る

リストのサイズに関してはlenで取得できる

リストに要素を追加する場合はappend関数が使える
別のリストの要素を追加するときはextend関数を使う

list = [1]
list.append(2)
list.append(3)

list.extend([4,5,6])
print list #print -> 1,2,3,4,5,6

またリスト同士の連結は+で連結出来る
さらに文字列と同じようにリストを繰り返すことも出来る

listにインデックスを指定して要素を追加したい場合は
insert関数を使う

list = [1,2,3]
list.insert(1,4)#insert(インデックス,要素)
print list #print->1,4,2,3

リストの要素を削除するときはdelが使える

list = [1,2,3]
del list[0]
print list#print->2,3

指定インデックスの要素を削除するときはpop関数でインデックスを渡す
逆に指定の要素を持つ部分を削除するときはremove関数で引数に要素を渡す

スライスを使えばこれらのことが意外と簡単にできたりする

リスト[len(リスト):] = オブジェクト

で末尾に挿入ができる

また部分リストに対して[]を代入することでその要素を消すことができるが
スライスで部分リストを取得してない時には使えない


リストにある要素が存在するかどうかを確かめたいときはinを使う

list = [1,2,3,4,5]
if 2 in list:
    print "yes"

#print -> yes

逆に存在しない時はnot in

指定した要素が存在しそのインデックスを返すのはindex関数
これに引数としてインデックスを確かめたい要素を渡す
存在しないなら「ValueError」がでる

リストに含まれるある要素の個数を数えたいならcount関数に
カウントしたい要素を渡す

ソートはsortとreverseでjavaのCollectionsと同じ


連続した数値のリストを作成したいときはrange関数を使う

list = range(5)
print list #print -> 0,1,2,3,4

list2 = range(2,5)
print list2 #print -> 2,3,4 開始位置を宣言することができる

list3 = range(1,6,2)
print list3 #print 1 -> 1,3,5 stepを宣言できる

タプル

リストとほぼ同じことができるが宣言がすこし違う

tuple = (1,2,3,4,5)

と()でくくる
ただし要素がひとつだけの時は

tuple = (1,)

と最後に,をつける必要がある

要素の取得はリスト同様[]でインデックスを指定する
スライスとサイズの取得も同様、連結と繰り返しに関しても同じことが言える
tuple関数というものを使えばリストからタプルを生成できたりもする

辞書オブジェクト

これはjavaでいうMapとほぼおなじ

dict = {"java":1,"python",2}

のように{}で宣言する

キーに対応する値を取得したいなら[]でインデックスのようにキーを渡す
値の変更とサイズの取得もリストやタプルに同様

連結は

辞書オブジェクト.update(辞書オブジェクト)

キーの削除はdelが同じように使える
辞書オブジェクトに対してはpop関数は削除したキーに対応する値を返す

任意のキーを削除するならpopitem関数を使用
このとき選ばられるセットはランダムである

clearですべて削除できる


キーが含まれているかを確かめるのはin
キー in 辞書オブジェクト
not inも使える

inと同じ動作をするものでhas_key()もある

またkeys関数を実行することでキーをすべてリストとして取得できる
それは値にも使えて値をリストで取得したいときはvalues関数
(キー、値)のタプル形式ですべて取得するときはitems関数

ループ

まずはwhile文

while True:
     print "always true"

となる、つまりいつもどおり
ただしループを抜けた時の処理をelseで書ける

while True:
    print "always true"
else:
    print "error"

次にfor文

for i in [1,2,3,4,5,6]:
    print i

#print i -> 1,2,3,4,5,6

for文に対してもelseは適応できる
更に今はリストを渡していたがrange関数でわたすこともできる

画像処理におけるヒストグラム

はじめに

今回は画像処理をするとき(2値化などで)に使うヒストグラムというものについてまとめる

ヒストグラムとは

ヒストグラムとは画像処理以外の分野で使われることもあるが
画像処理の分野では、各濃度値に対してその濃度値を持った画素数を求めたもので、濃度ヒストグラム
または単純にヒストグラムという。

ヒストグラムは主に横軸が濃度値、縦軸に画素数をとったグラフで表現される

{\displaystyle
濃度値がq_i以上、q_i+\Delta q_i以上である画素数を
}
{\displaystyle
それぞれA(q_i)、A(q_i+\Delta q_i)とすると
}

{\displaystyle
濃度値q_iを持った画素数H(q_i)はH(q_i)=A(q_i)-A(q_i+\Delta q_i)となる
}
{\displaystyle
濃度レベル数nに対しH(0)〜H(n-1)によりヒストグラムを表現できる
}


ヒストグラムはその画像がどのような濃度値を持った画素から成り立っているかの情報をまとめたもので
画像処理において処理が容易なのもあり非常に有用な手段といえる

ヒストグラムの性質

ヒストグラムにはいくつかの性質がある

まずはヒストグラムは各画素の濃度値だけをとりあげているので、その画素の場所には関係しないグラフとなる
なのでその濃度値を持った画素がどれくらいあるかはわかってもその画素がどこにあるかまではわからない

つまり画像の持つ空間的特徴は考慮されずその情報はヒストグラム上では失われる


次に、ヒストグラムは同一の画像に対しては一意に、つまり一通りに定まるが
異なる画像であってもそれと同一のヒストグラムを持つことも可能性としてないわけではない

最後にヒストグラムは画像の各画素の濃度値を集めたのもなので
画像を分割してヒストグラムを求めた場合、それらの和が全体のヒストグラムと一致する

ヒストグラムの計算法

大きさMxN画素の濃度レベルがNGLの画像imgのヒストグラムを求め
配列hstに格納する方法でC言語ライクな擬似コードでしめす

また配列hstの初期値は各要素とも0であるとする

int i,j;
int img[N][M];
int hst[NGL];

for(i = 0; i < N; i++){
    for(j = 0; j < M; j++){
         hst[img[i][j]]++;
    }
}

濃度レベルが256で8bitだとすると
ヒストグラムは0〜255までの範囲となる

実用例

ヒストグラムの具体的な使い方は後述することにして
今回はpythonOpenCVを使ってヒストグラムを表示するものを紹介する

f:id:RabbitFoot141:20160206171235p:plain

例によりこのLenna.pngの画像を使う

結果
f:id:RabbitFoot141:20160208213332p:plain

#coding:utf-8

import cv2
import pylab as plt

img = cv2.imread("lenna256.png")
hist = cv2.calcHist([img],[0],None,[256],[0,256])
plt.plot(hist)
plt.xlim([0,256])
plt.show()

今回はカラーのままでやったがグレースケールにするとまた少し違って見える

ヒストグラム計算を自作してみる

ヒストグラムを求めること自体は結構簡単なので画像の入出力だけにOpenCVを使い
それ以外を自作するなんていうことも可能

#coding:utf-8

import numpy as np
import cv2

img = cv2.imread("lenna256.png")
#画像をグレースケールに変換
gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)

#ヒストグラムを作成
histgram = [0]*256
for i in range(0,len(gray)):
	for j in range(0,len(gray[0])):
		histgram[gray[i][j]] += 1

画像処理における標本化と量子化

はじめに

画像処理でグレースケールと白黒について書いたがそれをもっと掘り下げようとすると

量子化についてのまとめが必要で、さらに量子化を説明しようとすると標本化まで説明しないと行けないので

今回はその両方を説明する

標本化

標本化とはざっくり言えば 空間的・時間的に連続した画像を離散的な点(標本点つまりは画素)の集合に変換すること

標本化によって横方向の画素数M、縦方向の画素数がNとなる場合画像の大きさはM×N画素となる
実際の標本化では各標本点の間隔をどのように選ぶかが問題となる
これに関しては対象画像がどの程度の濃淡を変化を含み、どれだけそれを忠実にとりこみたいかによるところが大きい

もっと細かく言うなら標本定理によって議論され、1次元標本定理が基本になる(らしい)

ここで一次元信号を
{ \displaystyle
g(t)
}
とするなら
g(t)に含まれる周波数がW以下である場合に限り
{ \displaystyle
T = 1/(2W)
}
間隔で標本化した標本値
{ \displaystyle
g(iT) (i = ... -1,0,1,2 ...)
}
を用いて式
{ \displaystyle
g(t) = \sum_{i=-∞}^{∞} g(iT)S(t-iT)
ただし
S(t)=sin(2πWt)/2πWt
}
により完全に復元できる


実際に写真などを取り込む場合(補足)

画像とは基本的に2次元平面上に連続的に分布した情報形態をとっているためコンピュータに取り込む際に一次元信号に直す必要があり走査というものが行われる

走査としては二次元平面を上から順番に一定間隔で水平方向にそってたどり濃淡値、つまり濃度値を取り出していくラスタ走査というのが一般的
そうして得られた一次元信号に対して一定間隔ごとにその値を求めていくことで離散的な点列を得ることができる

さらに動画の場合は時間軸でも標本化することが必要になり
時間軸で標本化、画面垂直、水平それぞれで標本化し3つの過程が必要となる

量子化


標本化によって画像は時間的、空間的に離散的に分布した画素に分解されるが
画素に関しては当然連続的な値になっている
画素の値としては、白〜灰〜黒の濃淡値が該当する
これは光の強さ、つまり明るさや輝度値の場合もある
この連続的な濃度値などを離散的な値に変換するのが量子化

今、量子化前の濃淡値を
{ \displaystyle
z
}
とするとき、濃淡値は連続的なため
{\displaystyle
z_i \leq z \leq z_i+1
}
となっているが、それを量子化すると
{\displaystyle
q_i
}
という整数値になる

このように得られた整数値は濃度値、グレーレベル、濃度レベルとよばれ
真の値zと濃度レベルqの差を量子化誤差という

量子化の方法

量子化レベル間隔が一定である場合

等間隔量子化と呼ばれるもっとも簡単な量子化方法で、標本値の濃度範囲を等間隔に分割する
画素の濃度値zが白から黒の範囲で一様に分布しているような画像に対して量子化誤差が最小になる
そのためこれは一様量子化、直線量子化とも言われる

量子化レベル間隔が一定でない場合

小さい濃度値に対してレベル間隔を細かくし
逆に大きい濃度値に対してはレベルを粗くする方法として、対数量子化がある

それ以外に画素の濃度値の確率密度関数を用いて
入力濃度値と量子化レベルとの平均2乗誤差を小さくする方法としてマックスの量子化がある

さらにはある範囲の濃度値が頻繁に生じ、その他がほとんど生じない場合はその範囲を細かく量子化
範囲外は粗い量子化する方法がある
これは量子化レベル数をそのままに量子化誤差を低減することが可能で漸減的な量子化と言われる

素数、量子レベル数と得られるデジタル画像との関係

ここでは画素数量子化レベルを変えるとどうなるか示す

素数を変えた場合

量子化レベルを256レベルで一定として
素数N*Nでどのように変化するか画像処理でよく見かけるlenna.pngで示す

256*256
f:id:RabbitFoot141:20160206171235p:plain

128*128
f:id:RabbitFoot141:20160206171256p:plain

64*64
f:id:RabbitFoot141:20160206171313p:plain

32*32
f:id:RabbitFoot141:20160206171341p:plain

256*256で十分な画質が得られるが、128,64,32と減っていくごとに一つの画素がブロック上になっているのが目立つ

量子化レベル数を変えた場合

素数を512^2で一定とし(元画像のサイズの関係で256にするの忘れてた)
量子化レベルを64,16,4,2と変化させると以下のようになる

64レベル
f:id:RabbitFoot141:20160206172555p:plain

16レベル
f:id:RabbitFoot141:20160206172622p:plain

4レベル
f:id:RabbitFoot141:20160206172636p:plain

2レベル
f:id:RabbitFoot141:20160206172647p:plain


64レベルではまだ元画像と大差ないがだんだん減らしていくと擬似輪郭(濃淡が本来滑らかに変化している部分で量子化のため濃淡に段差が生じ擬似的に輪郭が存在するように見える現象)が生じ、濃淡の細かい変化が失われ十分な画質を得ることができない

また最後に出した2レベルでは量子化の最も極端な場合で特にグレースケールの場合に2レベルにすることを2値化といい、こういった特殊な画像を2値画像という
後述にもだす予定だが、文字の識別などに使える


量子化レベルとしては一般的な画像では256レベル(8bit)
ただし医用画像など特殊な画像は256レベル以上で4096レベルなどが必要になる

取り扱う画像の画素数

画像を表現するために必要な画素数としては

その画像が空間的にどの程度細かい濃淡変化を含んでいるか
またどの程度細かい濃淡変化まで画像として表現する必要があるか、どの程度の解像度を必要とするかという点と

どの程度の範囲を画像として扱うかということに気をつける必要があり
対象の使用目的、種類に強く依存する

解像度が高くても対象の範囲が狭いならあまり画素数は大きくならないことに注意が必要である