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行目のカスケード型分類器の取得を行う部分で、ここで渡すのはカスケード分類器のファイル名を含めたパスであること
他のサイトではファイル名のみを渡していたりするがそれだと顔認識が正常に行われない上にエラーとしてもでてこない
つまりバグになるので注意
python入門
はじめに
最近OpenCVをpythonで扱うことが多くなったので一度しっかりと勉強してみる
今回は自分の備忘録として書くようなスタイルなので、ある程度プログラミング言語を他に扱えることを想定している
なので、ところどころ説明を省くので全くの初心者が見るべきではない
それと多々、javaと比較することがある(自分が一番得意としているから)
今回はpython2.7.6を使用していく
文法以前の基本的な事項
ここではまず基本的な事項、コメントやソースエンコードなどそういったことをまとめていく
変数
まずは数値や文字列などをやる前に変数について
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値化などで)に使うヒストグラムというものについてまとめる
ヒストグラムとは
ヒストグラムとは画像処理以外の分野で使われることもあるが
画像処理の分野では、各濃度値に対してその濃度値を持った画素数を求めたもので、濃度ヒストグラム
または単純にヒストグラムという。
ヒストグラムは主に横軸が濃度値、縦軸に画素数をとったグラフで表現される
ヒストグラムはその画像がどのような濃度値を持った画素から成り立っているかの情報をまとめたもので
画像処理において処理が容易なのもあり非常に有用な手段といえる
ヒストグラムの性質
ヒストグラムにはいくつかの性質がある
まずはヒストグラムは各画素の濃度値だけをとりあげているので、その画素の場所には関係しないグラフとなる
なのでその濃度値を持った画素がどれくらいあるかはわかってもその画素がどこにあるかまではわからない
つまり画像の持つ空間的特徴は考慮されずその情報はヒストグラム上では失われる
次に、ヒストグラムは同一の画像に対しては一意に、つまり一通りに定まるが
異なる画像であってもそれと同一のヒストグラムを持つことも可能性としてないわけではない
最後にヒストグラムは画像の各画素の濃度値を集めたのもなので
画像を分割してヒストグラムを求めた場合、それらの和が全体のヒストグラムと一致する
ヒストグラムの計算法
大きさ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までの範囲となる
実用例
ヒストグラムの具体的な使い方は後述することにして
今回はpythonとOpenCVを使ってヒストグラムを表示するものを紹介する
例によりこのLenna.pngの画像を使う
結果
#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次元標本定理が基本になる(らしい)
ここで一次元信号を
とするなら
g(t)に含まれる周波数がW以下である場合に限り
間隔で標本化した標本値
を用いて式
により完全に復元できる
量子化
標本化によって画像は時間的、空間的に離散的に分布した画素に分解されるが
画素に関しては当然連続的な値になっている
画素の値としては、白〜灰〜黒の濃淡値が該当する
これは光の強さ、つまり明るさや輝度値の場合もある
この連続的な濃度値などを離散的な値に変換するのが量子化
今、量子化前の濃淡値を
とするとき、濃淡値は連続的なため
となっているが、それを量子化すると
という整数値になる
このように得られた整数値は濃度値、グレーレベル、濃度レベルとよばれ
真の値zと濃度レベルqの差を量子化誤差という
量子化の方法
画素数、量子レベル数と得られるデジタル画像との関係
画素数を変えた場合
量子化レベルを256レベルで一定として
画素数N*Nでどのように変化するか画像処理でよく見かけるlenna.pngで示す
256*256
128*128
64*64
32*32
256*256で十分な画質が得られるが、128,64,32と減っていくごとに一つの画素がブロック上になっているのが目立つ
量子化レベル数を変えた場合
画素数を512^2で一定とし(元画像のサイズの関係で256にするの忘れてた)
量子化レベルを64,16,4,2と変化させると以下のようになる
64レベル
16レベル
4レベル
2レベル
64レベルではまだ元画像と大差ないがだんだん減らしていくと擬似輪郭(濃淡が本来滑らかに変化している部分で量子化のため濃淡に段差が生じ擬似的に輪郭が存在するように見える現象)が生じ、濃淡の細かい変化が失われ十分な画質を得ることができない
また最後に出した2レベルでは量子化の最も極端な場合で特にグレースケールの場合に2レベルにすることを2値化といい、こういった特殊な画像を2値画像という
後述にもだす予定だが、文字の識別などに使える
量子化レベルとしては一般的な画像では256レベル(8bit)
ただし医用画像など特殊な画像は256レベル以上で4096レベルなどが必要になる
グレースケールと白黒画像
はじめに
今回はグレースケールと白黒画像について少しまとめていく
あまり画像処理に興味がない人は、どっちも対して変わらんと思うだろうけど
実は明確な差がある
白黒画像
googleで白黒画像と検索すると以下のような画像が出てくるが
これは正確には白黒画像ではない
コンピュータ画像処理において白黒画像とは白と黒の2値のみで表された画像なので灰色は含まれない
つまり、2値化した画像のことをいう
グレースケール
グレースケール画像というものは白黒の2値だけで表されていた画像に対して、灰色も含めて表す
具体的には白〜灰〜黒の連続した変化(濃度値)を8bitの0〜255の256レベルに量子化したもので
各濃度値に対応した濃淡の度合いを表示したもので、白黒画像に比べて豊かに画像を表現することができる
この表現方法にはいろいろあり
0を白、255を黒とする場合やその逆もある
対して白黒画像、つまり2値化した画像の場合は0を白、1を黒としたりする
OpenCVで画像の読み込みと表示、保存
画像の読み込みと表示、保存
OpenCVを使って画像処理をしようにも、まず対象の画像が読み込めないと何もできない
そして、いろいろやっていった結果の画像を見れるといいなというわけで
さらにいろいろやった結果を出力できたらなおいいというわけで、今回は全てやる
方法
まずcv2.imread("画像ファイル名")で画像を読み込んだものを変数に代入する
imreadの第二引数を0にするとグレースケールで読み込める
画像を表示するにはimshowを使い
cv2.imshow("ウィンドウ名",画像を読み込んだ変数)とする
#coding:utf-8 import numpy import cv2 img = cv2.imread("Lenna.png") img2 = cv2.imread("Lenna.png",0)#グレースケールで読み込み cv2.imshow("color",img) cv2.imshow("gray",img2) cv2.waitKey(0) cv2.destroyAllWindows()
waitKeyではキーボードからの入力待受で引数のミリ秒だけ入力を待ち受けてその後次の処理に移る
destroyAllWindows()では現在表示中のhighGUIの画像表示窓を全て破棄する
これで画像の表示と読み込みができるようになった
次に画像の保存
今回はカラー画像をグレースケールで読み込んでそれを出力する
画像の出力は簡単で
cv2.imwrite("出力画像ファイル名",画像ファイルを読み込んだ変数)
でいいので
#coding:utf-8 import numpy import cv2 img = cv2.imread("Lenna.png",0)#グレースケールで読み込み cv2.imwrite("LennaG.png",img)
これで画像の読み込み、表示、保存ができるようになった
PythonでOpenCVを使えるようにする
方法
まずは例にならって環境を最新にする
sudo apt-get update sudo apt-get upgrade
これで必要なパッケージをインストールするだけ
sudo apt-get install libopencv-dev sudo apt-get install python-opencv
numpyが必要なので入っていない場合はそれもインストール
それでちょっとテスト
#codeing:utf-8 import numpy as np import cv2 img = cv2.imread("ここにファイル名"); cv2.imshow("作成するウィンドウ名(なんでもいい)",img) cv2.waitKey()
ついでにヒストグラムの時に便利なライブラリもまとめていれておく
sudo apt-get install python python-dev sudo apt-get install python-numpy python-scipy python-matplotlib
ここでさらにヒストグラムを出してみる
#coding:utf-8 import cv2 import pylab as plt #グラフ可視化 im = cv2.imread("ファイル名") hist = cv2.calcHist([im],[0],None,[256],[0,256]) plt.plot(hist) plt.xlim([0,256]) plt.show()