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

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を用いた
ピクセルアートの作り方でした。

それでは。




見えたら天才
そんな画像があります。

ステレオグラムと言う
立体視画像です。

今回はそれを作っていきます。
解説動画はこちら



さてまずこの立体視というものですが
立体的な視覚を得る方法のことで
画像を立体的に見ることになります。

この立体的に見える画像のことを
ステレオグラム(英: stereogram)
立体画、立体図と呼んでいます。

立体的な印象をもつように描かれた
平面に描かれた図や絵あるいは写真
のことです。

こんなドット絵を見たことないでしょうか?
4つのドットの中二つが重ね合わさるように見ると
立体的に浮かび上がるように見えると思います。
download


コツとしては
右目で左側を見て、左目で右側を見るイメージ
うまく行くと点が浮かび上がってくると思います。

スマホだと厳しいかもしれないので
タブレットやPCで見てみると良いでしょう。


それではここからステレオグラムを
作っていきましょう。

今回作るやつは
ランダム・ドット・ステレオグラム
(英: Random dot stereogram, RDS)

一見ノイズのようにしか見えない画像が
うまく焦点を合わせると
立体が浮かび上がってくる画像です。

レーダー技術者から知覚研究に転じた
ユレス・ベーラさんによって
考案された手法だそうです。

今回はこちらのサイトの手法をそのまま
お借りしています。

参考



まずは背景となるドット画像の用意です。

ライブラリはopencvなどを使用します。
インストールしていないと動かないので
無い方はインストールしてください。
import numpy as np
import matplotlib.pyplot as plt
import cv2
%matplotlib inline

numpyを用いて背景ドット画像を作る関数です。
def make_dots(shape=(16, 16), levels=64):
    return np.random.randint(0, levels - 1, shape) / levels

dots = make_dots(shape=(64, 64))
plt.figure(figsize=(8,8))
plt.imshow(dots, cmap='gray')
plt.show()
dots

最初は円形の画像を浮かび上がらせる
ステレオグラムを作りましょう。

円形画像を作ります。
def make_img(shape=(400, 600)):
    img = np.zeros(shape, dtype=np.float)
    cv2.circle(img, (int(shape[1]/2), int(shape[0]/2)), 100, (255 ,255, 255), -1)
    return img

img = make_img()
plt.figure(figsize=(8,8))
plt.imshow(img, cmap='gray')
plt.show()

dotimg

ステレオグラムを作成する関数です。
こちらは参考サイトのものをそのまま転記しています。
def normalize(depthmap):
    "Normalizes values of depthmap to [0, 1] range."
    if depthmap.max() > depthmap.min():
        return (depthmap - depthmap.min()) / (depthmap.max() - depthmap.min())
    else:
        return depthmap

def make_autostereogram(depthmap, pattern, shift_amplitude=0.1, invert=False):
    "Creates an autostereogram from depthmap and pattern."
    depthmap = normalize(depthmap)
    if invert:
        depthmap = 1 - depthmap
    autostereogram = np.zeros_like(depthmap, dtype=pattern.dtype)
    for r in np.arange(autostereogram.shape[0]):
        for c in np.arange(autostereogram.shape[1]):
            if c < pattern.shape[1]:
                autostereogram[r, c] = pattern[r % pattern.shape[0], c]
            else:
                shift = int(depthmap[r, c] * shift_amplitude * pattern.shape[1])
                autostereogram[r, c] = autostereogram[r, c - pattern.shape[1] + shift]
    return autostereogram

最後に実行です。
このコードで円形ステレオグラムが
作成できると思います。
img = make_img()
autostereogram = make_autostereogram(img, dots)
plt.figure(figsize=(8,8))
plt.imshow(autostereogram, cmap='gray')
plt.show()
en

さて、注視すると円が浮かび上がってきませんでしょうか?

目安が無いので難易度は高いですが
見えない方は4つのドットで練習すると
見えるようになるかもしれません。

続いて文字を立体画像にしてみましょう。
まずは文字です。

text=の部分
引数で指定して変えれば他の文字にできます。
def make_text_img(shape=(400, 600), text='p y'):
    img = np.zeros(shape, dtype=np.float)
    font = cv2.FONT_HERSHEY_SIMPLEX
    cv2.putText(img, text, (100, 250), font, 5, (255,255,255), 30, cv2.LINE_AA)
    return img

text_img = make_text_img()
plt.figure(figsize=(8,8))
plt.imshow(text_img, cmap='gray')
plt.show()
py_text

これをステレオグラムにします。
autostereogram = make_autostereogram(text_img, dots)
plt.figure(figsize=(8,8))
plt.imshow(autostereogram, cmap='gray')
plt.show()
py

さてPYの文字が浮かび上がってきませんでしょうか?


最後にクイズです。
これは何と書いてあるでしょうか?

心が汚れている方は読めるはずですwwwwwww
sex


見えた方は動画のコメント欄にでも
コメントしていただけると嬉しいです。

見えたときは興奮、感動すること
間違いない!!!

気合と根性と集中力があれば
見えます。

頑張ってくださいね。
それでは。



このページのトップヘ