####今回の目標
前回の記事で顔を検出できるようになったので、次の目標は検出した顔にモザイクをかけることです。その前哨戦として、今回は画像全体にモザイクとぼかしをかけてみようと思います。
####モザイク
まずは画像にモザイクをかけてみようと思います。最終的なコードを最初に掲載しておきます。
import cv2
img = cv2.imread('photo.jpg')
def mosaic(img, ratio):
small = cv2.resize(img, None, fx=ratio, fy=ratio)
return cv2.resize(small, img.shape[:2][::-1])
cv2.imshow('photo_mosaic.jpg', mosaic(img, ratio=0.1))
cv2.waitKey()
imread()やresize()については前々回の記事で触れておりますので必要でしたら参照なさってください。
img = cv2.imread('photo.jpg')
↑まずはimread()で画像を読み取ってimg変数に代入します。
def mosaic(img, ratio):
small = cv2.resize(img, None, fx=ratio, fy=ratio)
return cv2.resize(small, img.shape[:2][::-1])
↑今回の肝、モザイク処理を行う関数です。
small = cv2.resize(img, None, fx=ratio, fy=ratio)
↑この1行で、いったん画像を10分の1のサイズに縮小しています。10分の1の指定はこの後に出てくる
cv2.imshow('photo_mosaic.jpg', mosaic(img, ratio=0.1))
・・・の ratio=0.1 で指定しています(例えば縦横半分の大きさにしたいならratio=0.5を指定する)。
どのくらい小さくなったのかを確認してみると。
元の画像:71.8kb
縮小した画像:3.16kb
・・・だいぶ小さくなりました。
(この分野の専門ではないので下記の表現が正しいか分かりませんが)縮小した写真は画素数が減った状態なので、無理やり元の大きさに戻せば、少ない画素数で空間を埋めようとするためいわゆる「モザイク」の状態になるはず、という理屈です。
def mosaic(img, ratio):
small = cv2.resize(img, None, fx=ratio, fy=ratio)
return cv2.resize(small, img.shape[:2][::-1])
さきほどの関数の
return cv2.resize(small, img.shape[:2][::-1])
この1行で、縮小された画像を元の大きさに戻し、mosaic関数の戻り値を返す仕組みです。ただ、下記の表記にたどりつくまでにえらい苦労が。。。
img.shape[:2][::-1]
これ、(縮小する前の)画像の縦横の大きさを指定しています。縮小する前のサイズをprint()で確認してみると。
print(img.shape[:2])
#出力結果
#(427, 640)
2つの要素が画像の大きさを示しているので、img.shape[:2]で縦横の大きさを指定して、これでいける!と思ってコードを実行すると・・・
モザイクはかかっていますが、なぜか縦長の画像が出力されました。
先ほどの出力結果をもう一度見て見入ると。
(427, 640)
最初の2つの数字が、縦→横の順番になっているではありませんか。一方で、resize()で大きさを指定する順番は横→縦の順番。これは困りました。
タプルの順番を入れ替えるための表記を調べて、ようやくうまくいったのが先ほど掲載した
return cv2.resize(small, img.shape[:2][::-1])
・・・の表記だったという次第です。[::-1]で縦と横の順番を逆にしています。
例)
img = cv2.imread('photo.jpg')
reverse_order = img.shape[:2][::-1]
print(reverse_order)
#出力結果
#(640, 427)
[::-1]の記述は恥ずかしながら今まで知りませんでした。。
cv2.imshow('photo_mosaic.jpg', mosaic(img, ratio=0.1))
cv2.waitKey()
そしてこのコードの
mosaic(img, ratio=0.1)
で先ほど定義したmosaic関数を呼び出して、結果を表示したという次第です。imshow()で画像を表示し続けるためにwaitKey()を最後に記述しています。
これでコードを実行すると・・・
うまくいきました!
最後に完成コードをもう一度。
import cv2
img = cv2.imread('photo.jpg')
def mosaic(img, ratio):
small = cv2.resize(img, None, fx=ratio, fy=ratio)
return cv2.resize(small, img.shape[:2][::-1])
cv2.imshow('photo_mosaic.jpg', mosaic(img, ratio=0.1))
cv2.waitKey()
imread()で読み込んだ数値を吐き出すと、画像の大きさ表示が縦→横の順番になっているのがトリッキーでした。
####平滑化=ぼかし
引き続き、モザイクのようにガビガビした感じではなく、ピントが合ってない感じにしたいと思います。専門用語では「平滑化」という言葉を用いるようです。オープンCVのチュートリアルに詳しい解説が乗っています。
平均を取るには正規化された箱型フィルタを使います. カーネルの範囲内にある全画素の画素値の平均をとります.
ただどういうアルゴリズムなのか調べても、数式がざっと並んでさっぱり分かりません・・・とりあえずチュートリアルの冒頭に紹介されている、画像を平均化する方法でやってみようと思います。
画像にぼかしをかける全体のコードはこちら。シンプルです。
import cv2
img = cv2.imread('pumpkin.jpg')
#画像を平均化して(=画像にぼかしをかけて)img_blurに格納する
def average(img):
img_blur = cv2.blur(img, (17, 17))
return img_blur
cv2.imshow("pumpkin_averaged.jpg", average(img))
cv2.waitKey()
cv2.destroyAllWindows()
def average(img):
img_blur = cv2.blur(img, (17, 17))
return img_blur
こちらが画像にぼかしをかけている部分です。ただ公式ドキュメントには引数についていろいろ紹介してあるのですが、ここに書いてある引数を試そうとしてもことごとくエラーが発生してしまいます。。。他の記事も読んでみた結論としては、blur()関数について必要な引数は2つ。
①加工をする対象の画像
②カーネルのサイズ
平均化というのをかいつまんで言うと「画像の1点とその周囲のピクセル値を平均して中心のピクセル値として出力する」くらいの意味で、平均化するのに参照する範囲のことをカーネルと呼んでいるようです。
要するに「カーネルの値が大きいほどぼかしが濃くなる」と覚えておけば問題なさそう。OpenCVのチュートリアルによると
平均を取るには正規化された箱型フィルタを使います. カーネルの範囲内にある全画素の画素値の平均をとります.
カーネルの縦幅,横幅を指定する必要が有ります.
・・・とのことですが、とりあえずやってみたのが
img_blur = cv2.blur(img, (17, 17))
この1行でした。
ガビガビではなく、ピントがずれた感じになりました。平滑化の処理はかなり奥が深そうなので、今後きちんと使えるようになりたいと思います。
次は検出した顔にモザイクやぼかしをかけてみようと思っています。
お読みいただきありがとうございました。
参考にさせていただいた記事
OpenCVで画像の平滑化をしてみた