乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

opencv

今回はシルエット当てクイズとかに使われる
視力検査画像を作っていきたいと思います


解説動画はこちら


Opencvで視力検査の画像っぽいものを作ります

画像のシルエットを浮かび上がらせて
何のキャラかを当てるやーつですね
見えたら正常・・・ってな奴です

参考画像はこんな感じのものです

IMG_0252




今回はGoogle Colabで作業を行います


作業手順としては次の通りです
0.画像をGoogle Colabにアップロードしておく
1.画像を取り込む
2.シルエットを抽出する
3.下地画像を作る
4.下地にシルエットを合成する


画像の用意

まずはなんでも良いので
画像を用意しましょう

シルエットがうまく抽出できるように
背景が薄くて、個体が判別できるものが良いですね

これをファイル置き場に置いておきます



画像の読み込み

opencvで画像を取り込んでいきます

Opencvのインポート

import numpy as np
import cv2
import os
from IPython.display import display,Image
import matplotlib.pyplot as plt
%matplotlib inline


ライブラリのインポートが終わったら
画像を読み込みましょう

ファイル名やパスなどは
あわせる様に変えましょう

うまく取り込めていたら
画面に表示されます
# 画像を読み込む
file_path = "/content/ファイル名"
img = cv2.imread(file_path)

# 出力
out = cv2.imencode(".png", img)[1]
display(Image(out,width=300))


シルエットを抽出する

画像が取り込めたら
次はその画像のシルエットを抽出します

具体的な手法としては
白黒画像に変換して
閾値を調整しながら濃いところを
抜いていきます

threshold をうまく変えて
調整して抜いて下さい

# グレースケールに変換する。
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 2値化する(thresholdを変えて、うまく抜いてね)
threshold = 120
ret, bin_img = cv2.threshold(gray, threshold, 255, cv2.THRESH_BINARY)

# 出力
out = cv2.imencode(".png", bin_img)[1]
display(Image(out,width=300))

シルエット画像は
こんな感じになります
siru



下地の画像を用意する

合成用の下地となる
青い画像を作っていきます

サイズは元の画像のサイズになります

height = bin_img.shape[0]
width = bin_img.shape[1]
blank = np.zeros((height, width, 3))
blank += [0,0,255][::-1]
blank = blank.astype(np.uint8)

out = cv2.cvtColor(blank, cv2.COLOR_BGR2RGB)
plt.imshow(out)
plt.axis("off")
plt.show()
blue



下地にシルエットを合成する


最後に下地にシルエットを合成します

eye_test_value を変えると
シルエットの濃さを調整できるので
色々変えて試してみて下さい

eye_test_value = 100
for h in range(bin_img.shape[0]):
  for w in range(bin_img.shape[1]):
    v = bin_img[h][w]
    if v==0:
      blank[h][w][0] = blank[h][w][0] - eye_test_value

out = cv2.cvtColor(blank, cv2.COLOR_BGR2RGB)
plt.imshow(out)
plt.axis("off")
plt.show()
100

値を小さくしてみると

50

5

どこまで見えますかね

見えたらあなたの視力は正常です


シルエット画像や下地
濃さを調整すれば
色々楽しめそうな感じの画像が
いくらでも作れますね

opencvは便利なので
こういった画像加工を行いたい方は
是非参考にしてみてください

それでは

 

映画のマトリックスを観てきたので
それ風の画像に変換するプログラムを
作って見ました。

解説動画はこちら




さて今回は
映画のマトリックスに出てきたような
緑色の文字が羅列されたような風合いの
画像に変換するプログラムです。

仕組みとしては
opencvで画像の輪郭を抽出し
その輪郭部分を文字画像で
置き換えるというものです。

サンプルコードはこちら

from PIL import Image, ImageDraw, ImageFont
import cv2
import numpy as np
import random
import string 
import matplotlib.pyplot as plt
%matplotlib inline

# フォントサイズの指定
font_size = 10

# テキスト画像の生成
def make_text_img(font_path="/System/Library/Fonts/Courier.dfont"):    
    text = string.printable[0:-5]
    im = Image.new('RGB', (int(len(text)//3*2*font_size), font_size), (0, 0, 0))
    draw = ImageDraw.Draw(im)
    font = ImageFont.truetype(font_path, font_size)
    draw.text((0, 0),text, fill=(80, 255, 80), font=font) 
    return np.asarray(im)

# 要フォントパスの確認を
text_img = make_text_img()

# 画像の読み込み
img = cv2.imread('画像のパス') 

img2 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
height , width = img2.shape

# エッジ画像の作成
edges = cv2.Canny(img2,50,100) 
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# マトリックス画像の生成
x = width//font_size*font_size
y = height//font_size*font_size
resize = cv2.resize(edges,(x, y)) 
matrix = np.zeros((y, x, 3), np.uint8) 

for y2 in range(0, y, font_size):
    for x2 in range(0, x, int(font_size//2)):
        if np.amax(resize[y2:y2+font_size, x2:x2+font_size//2])>0: 
            pos = int(random.random()*len(text))*font_size//2
            matrix[y2:y2+font_size, x2:x2+font_size//2] = text_img[0:font_size, pos:pos+font_size//2]
            
# 画像の表示
fig, axes = plt.subplots(nrows=1, ncols=3, sharex=False)
fig.set_figwidth(15)
for i,ig in enumerate([img,edges,matrix]):
    axes[i].imshow(ig)
    axes[i].axis('off')
plt.show()

一応Mac用で作っているので
Windowsの人は
フォントの場所を指定しないと
うまく動かない可能性があります。

その場合はフォントのパスを指定するように
コードを書き換えて下さい。

実行する際は
画像のパスを指定するだけで動くと思います。

実行すると
・元画像
・エッジ画像
・マトリックス風画像
の3つの画像が表示されると思います。

試しに人の顔画像でやってみると・・

download

こんな感じですね。
人の輪郭部分も上手くとれていますね。

エッジの抽出では閾値を指定しているので
上手く検出できない場合は
edges = cv2.Canny(画像,閾値1,閾値2)
 ここの部分の閾値をうまく調整して下さい。

ビル画像とかでやるとかなりクリアに出来ますね。

download-3


今回は映画マトリックス風の画像に変換する
フィルタープログラムのご紹介でした。

試した仕方はコピペして
遊んでみて下さい。

今回はこれまでです。
それでは。

今回はopencvを使って画像から人を抜き出して
背景を切りとってみました。

解説動画はこちら



今回は簡易なセグメンテーションです。

画像の人の領域を特定して
背景と切り分けてみましょう。

まずはライブラリの読み込みです。
# 描画用のメソッド
import numpy as np
import cv2
import matplotlib.pyplot as plt
%matplotlib inline

def img_display(img):
    fig = plt.figure(figsize=(10,8))
    ax = fig.add_subplot(111)
    ax.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    ax.axis('off')

def img_display2(img,cmap=None):
    fig = plt.figure(figsize=(10,8))
    ax = fig.add_subplot(111)
    ax.imshow(img, cmap=cmap)
    ax.axis('off')
    
def img_display3(imgs,texts):
    fig = plt.figure(figsize=(16,10))
    for i , img in enumerate(imgs):
        tmp = fig.add_subplot(1, len(imgs), i+1)
        tmp.set_title(texts[i],fontsize=15)
        plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
        plt.axis('off')
    plt.show() 



次に人の画像を用意しましょう。
img = cv2.imread('kuro/kuro2.jpg')
img_display(img)
download-4


今回はこちらの方を用意しました。

さてこの画像に対して
処理を行っていきましょう。

最初は画像にブラー(ぼかし)をかけてみます。
img_blur1 = cv2.medianBlur(img ,    5)
img_blur2 = cv2.medianBlur(img , 15)
img_blur3 = cv2.medianBlur(img , 25)
img_blur4 = cv2.medianBlur(img , 35)
img_blur5 = cv2.medianBlur(img , 45)

texts = ['Blur_5','Blur_15','Blur_25','Blur_35','Blur_45']
img_display3([img_blur1,img_blur2,img_blur3,img_blur4,img_blur5] , texts)

download

こんな感じでぼかされていきますね。
ぼかすと背景と物や人の領域が
やや滑らかに分かれる感じです。

次にグレースケールに加工します。
gray_img1 = cv2.cvtColor(img_blur1,cv2.COLOR_BGR2GRAY)
gray_img2 = cv2.cvtColor(img_blur2,cv2.COLOR_BGR2GRAY)
gray_img3 = cv2.cvtColor(img_blur3,cv2.COLOR_BGR2GRAY)
gray_img4 = cv2.cvtColor(img_blur4,cv2.COLOR_BGR2GRAY)
gray_img5 = cv2.cvtColor(img_blur5,cv2.COLOR_BGR2GRAY)

texts = ['gray_img1','gray_img2','gray_img3','gray_img4','gray_img5']
img_display3([gray_img1,gray_img2,gray_img3,gray_img4,gray_img5] , texts)

download-2

カラー画像を白黒にすると
白黒の濃淡は0-255の数値で表せるので
閾値を決めて領域を分けることができます。

次は閾値を決めて
人のシルエットを作ってみましょう。
mid = 204
ret, img_thresh1 = cv2.threshold(gray_img1,mid,255,cv2.THRESH_BINARY_INV)
ret, img_thresh2 = cv2.threshold(gray_img2,mid,255,cv2.THRESH_BINARY_INV)
ret, img_thresh3 = cv2.threshold(gray_img3,mid,255,cv2.THRESH_BINARY_INV)
ret, img_thresh4 = cv2.threshold(gray_img4,mid,255,cv2.THRESH_BINARY_INV)
ret, img_thresh5 = cv2.threshold(gray_img5,mid,255,cv2.THRESH_BINARY_INV)

texts = ['img_thresh1','img_thresh2','img_thresh3','img_thresh4','img_thresh5']
img_display3([img_thresh1,img_thresh2,img_thresh3,img_thresh4,img_thresh5] , texts)

download-1


うまくいくかは閾値次第ですが
今回は背景と人がくっきり分かれました。

このシルエットを使って
元の画像を切り取ってみましょう。

シルエット部分だけ抽出して背景を抜く
# ノイズ取り
kernel = np.ones((3,3),np.uint8)
mor_img = cv2.morphologyEx(img_thresh3,cv2.MORPH_OPEN,kernel,iterations = 2)

# マスク
mask_img = cv2.dilate(mor_img,kernel,iterations=2)
mask_rgb = cv2.cvtColor(mask_img, cv2.COLOR_GRAY2RGB)

# マスクの切り抜き
result_img = cv2.bitwise_and(img, mask_rgb)
img_display(result_img)
download-3

こんな感じで人と背景を分けて
人だけ抽出ができました。


まとめです。

背景を削除して物や人の画像を抽出できますが
背景が複雑だとうまくいかないです。

なにせ雑に簡単な方法なので
より精密にセグメンテーションするには
opencvでは限界がありますね。

精細に行うなら
機械学習などで学習させた
領域切り分けを使わないと
上手くいかないでしょうね。

今回はopnecvを使った
簡単な画像のセグメンテーションでした。

今回はここまでです。
それでは。
 

水曜日のダウンタウンに出てきた
「じゃねーよカルタ」

このカルタに出てきた人の顔
誰が一番春菜さんの顔と近いのかを
調べてみました。


解説動画はこちら



今回は「じゃねーよカルタ」に出てきた人の
顔の画像と近藤春菜さんの顔の画像を
比べて、誰が一番近いのかを検証しました。

画像の類似度の判定には
opencvのAKAZE特徴量を用いて
判定することとします。

さて今回の比較対象はこの5名

download-2

ハロネン元大統領
角野卓造
森永卓郎
和泉節子
マイケル・ムーア監督
です。

どんな感じに比較しているのか
AKAZEを使って特徴点を見てみましょう。

import matplotlib.pyplot as plt
import cv2
import os
%matplotlib inline

img1 = cv2.imread('画像パス') 
img2 = cv2.imread('画像パス') 
gray1 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY) 
gray2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY) 
akaze = cv2.AKAZE_create() 
kp1, des1 = akaze.detectAndCompute(gray1,None) 
kp2, des2 = akaze.detectAndCompute(gray2,None) 
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
matches = sorted(matches, key = lambda x:x.distance)
img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches[0:10], None, 
                       flags = cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
plt.figure(figsize=(10,10))
plt.imshow(cv2.cvtColor(img3, cv2.COLOR_BGR2RGB))
plt.axis("off")
plt.show()
download

こんな感じで似ている点を出しています。

今回比較する春菜さんの顔はこちら

download-1

先ほどの5名と
和泉節子さん風に変装した春菜さんの
6名で比較して、一番近い顔の人を出してみます。

対象の人の顔画像は
画像ディレクトリに入れています。

判定に使ったコードはこちら
dir_path = '画像ディレクトリ'
img_size = (300,300)
target_img_path = '画像パス'
target_img = cv2.imread(target_img_path, cv2.IMREAD_GRAYSCALE)
target_img = cv2.resize(target_img, img_size)
bf = cv2.BFMatcher(cv2.NORM_HAMMING)
detector = cv2.AKAZE_create()
(target_kp, target_des) = detector.detectAndCompute(target_img, None)

files = os.listdir(dir_path)
result = {}
for file in files:
    img_path = dir_path + file
    try:
        img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
        img = cv2.resize(img, img_size)
        (kp, des) = detector.detectAndCompute(img, None)
        matches = bf.match(target_des, des)
        dist = [m.distance for m in matches]
        ret = sum(dist) / len(dist)
        result[file.replace('.png','')] = ret
    except cv2.error:
        ret = 999999

for k,v in sorted(result.items(),reverse=True,key=lambda x:x[1]):
    img = cv2.imread('img/{0}.png'.format(k)) 
    plt.figure(figsize=(8,8))
    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    plt.title('{0} : {1:.06}'.format(k , v),fontsize=20)
    plt.axis("off")
    plt.show()


結果は動画の方で
お楽しみくださいませ。

結構意外な結果が出ましたね!!!

opencvを使うと
画像の特徴量を比較して
似ているかどうかを数値化する事ができます。

面白いので試してみてくださいね。
今回はこれまでで
それでは。


今回はOpencvでピクセルアートを作ってみました。 ようはドット絵に加工するやつです。

解説動画はこちら




さて今回はピクセルアート
平たく言うと画像をドット絵に加工するやつです。

さっそくコードを見ていきましょう。

import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# 減色加工
def color_reduction(im, k):
    z = im.reshape(-1, 3).astype(np.float32)
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
    # K-means法で減色する
    ret, lab, cter = cv2.kmeans(z, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
    cter = np.uint8(cter)
    return cter[lab.flatten()].reshape(im.shape)

# モザイク加工
def mosaic(im, alpha):
    h, w, ch = im.shape
    res = cv2.resize(im,(int(w*alpha), int(h*alpha)))
    res = cv2.resize(res,(w, h), interpolation=cv2.INTER_NEAREST)
    return res

# ドット絵加工
def pixel_proc(im, alpha=0.5, k=8):
    return color_reduction(mosaic(im, alpha), k)

こんな感じの関数を用意しました。
モザイク処理で引き伸ばしを行ったのちに
減色処理を行って色を減らして
それっぽくします。
使う際はこんな感じで
# 入力画像
img = cv2.imread('画像のパス')

# ドット絵加工
k = 8
alpha = 0.3
dots = pixel_proc(img, alpha, k)

# 表示
plt.figure(figsize=(16,16))
plt.subplot(1,2,1)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.axis("off")

plt.subplot(1,2,2)
plt.imshow(cv2.cvtColor(dots, cv2.COLOR_BGR2RGB))
plt.axis("off")
plt.show()
asuka

いい感じに加工されましたね。
alphaを減らすと画像が荒くなり
kを増やすと色合いが増えます。

maiyan
こんな感じで色合いとかを調整すれば
ピクセルアートっぽくなります。


コードの仕組みとしては
cv2.kmeans というのでk-means法を使って
画像の色をk個のグループに落とし込んでいます。

そうすることで減色加工ができている訳ですね。
im = cv2.imread('画像のパス')
z = im.reshape(-1, 3).astype(np.float32)
k = 4
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
ret, lab, cter = cv2.kmeans(z, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
lab = lab.squeeze(axis=1)
cter = cter.astype(np.uint8)
_, counts = np.unique(lab, axis=0, return_counts=True)
dots = cter[lab.flatten()].reshape(im.shape)

fig, [ax1, ax2 , ax3] = plt.subplots(1, 3, figsize=(16, 5))
fig.subplots_adjust(wspace=0.3)
colors = cter[:, ::-1] / 255
texts = list(map(str, cter))
ax1.imshow(cv2.cvtColor(im, cv2.COLOR_BGR2RGB))
ax1.set_axis_off()
ax2.imshow(cv2.cvtColor(dots, cv2.COLOR_BGR2RGB))
ax2.set_axis_off()
ax3.barh(np.arange(k), counts, color=colors, tick_label=texts)
plt.show()
2222

4色にした場合、背景に1色取られてしまい
残りの3色で人を表す感じになっちゃいます。
4色でもなかなか表現力は高いですね。

今回はopnecvを用いた
ピクセルアートの作り方でした。

それでは。




このページのトップヘ