大きくなったら大蛇に

Python始めたての初心者による学習ブログです

ステレオグラムの作り方_実践編4(GIFアニメを作る)

■どんなステレオグラムを作ろうか

ステレオグラムを見るのは目のピント調節機能を鍛えるから、目にいいといいますが、いかんせん同じ画像をボケーっと見てるのは飽きるんですよね。慣れるとすぐピントを合わせられちゃうからピント調節の訓練している気がしないし。

私にとってのステレオグラムの欠点って「飽きる」なのです。

そこで、楽しく飽きずにステレオグラムを見続けられるためにステレオグラムを動かしたいと思いました。

 

ステレオグラムの深度マップを動かしてみよう

今までに習得したあれこれで3Dの深度マップを作ってみました。

これで、人が歩いているようなアニメにしてみたい。

そこでもう一枚準備しました。

この2枚の画像でステレオグラムを作って、それをパラパラ漫画にしたら歩いているように見えるんじゃないかと。

よし、とりあえずやってみよう。

それぞれの画像から作ったステレオグラムは↓


ではパラパラ漫画にしてみましょう!

★GIFアニメを作るコマンド★

ln[1]  #インポートするライブラリ

from PIL import Image

ln[2]  #画像の入れ物を準備

img_walking=[]

ln[3] #画像をどんどん入れていく(今回は2枚だけですが)

for i in range(2):
    file_name='./in_img/gif/walking' +str(i+1)+ '.jpg'
    img = Image.open(file_name)
    img_walking.append(img)

2行目

file名の”str(i+1)”について

画像ファイル名は連番にしていて、小さい番号のファイルから順に読んでもらいたかったので、最初は

file_name='./in_img/gif/walking' +i+ '.jpg'

していたのですが、

can only concatenate str (not "int") to strとエラーが出ました。

この書き方はだめなのですね。

str(i+1)(※str(i)だと0から始まってしまうので。)でstrで囲って文字列にするとうまくいきました。(iが変数扱いのままにできるのが不思議…)

 

3行目

Imageモジュールのopen関数で画像を開く。

 Image.open("画像のPath”)

 

4行目

リスト名.append()で、画像のファイル名とデータをimg_walkingに入れていく

 

ln[3] #gifアニメを出力する

img_walking[0].save('./out_img/walking.gif',save_all=True, append_images=img_walking[1:],
optimize=True, duration=800, loop=0)

静止画ファイルからGIFを作るのは以下コマンド。

img_walking[0].save(           #リスト名[0].saveで画像の保存

  './out_img/walking.gif',           #保存先Path名/ファイル名.gif

  save_all=True,            #ループの回数

  append_images=img_walking[1:],    #リスト名[0]に[1]以降の画像をつなげる

  optimize=True,                                         #??

  duration=800,                                          # フレームの表示時間 ミリ秒

  loop=0                #ループ回数。無限にしたいときはゼロ

)

 

おお、イメージ通りにできました!

ただちょっとチカチカして目が疲れそう。もうちょっと滑らかに変化して貰いたいなぁ。うーん。どうしましょう。

OpneCVのフレーム補間って急な画像変化の緩衝に使えるでしょうか。。。

それとも深度マップ画像をモーフィングで徐々に変化させて動画のフレーム周波数を上げる?

なにかいい方法がないものでしょうか。。。

 

なお、今回はPILでgifにしましたが、OpenCVでmp4動画にすることもできました。

このあと画像を横にスライドさせて歩いている感を出したいと考えているので、画像をOpenCVで色々操作するならOpenCVで統一した方がいいかもしれないと後から思いました。

 

 

ステレオグラムの作り方_実践編3(2Dシルエット画像から3D画像を作る)

■前回までの取り組み

前回の更新から間が空いてしまったのでおさらいします。

<前回取り組んだ課題>

 好きな背景画像で、

 好きな深度マップ画像を使い、ステレオグラムを作ろうとしたところ、

 3D表示にならないはずの箇所も部分的に3D表示になってしまった。

(余計な3D部分をゴミと呼んでいます。)

<出てきた解決策>

 深度マップ画像を3Dグラフィック画像にする。

 3Dグラフィック化にはBlenderという有名なフリーソフトが存在するのでそれを

 使うのが良い。

 

ところがこのBlenderの使い方が簡単には行かなくて。

ちゃんと勉強しないと使いこなせなさそうだったので、心が折れそうになっていました。

 

だけど!今はPythonを勉強しているのです。

ならば3Dグラフィック化はPythonでするべきではないだろうかと思い至りました。

 

Pythonで2Dシルエット画像から3D画像を作る

色々調べてみて、参考にしたのは以下の2サイトです。

 

【第15回Python流体の数値計算】2次元ポアソン方程式をPythonで実装する。|宇宙に入ったカマキリ (takun-physics.net)

こちらは何をしているのか感覚的に理解するのに使わせていただきました

方程式を解いたわけではないので正確に理解はできていないですけど。。

 

GitHub - unclearness/inflation_py: Generate a height/depth map and a mesh from a single silhouette.

こちらはソースをいただきました。

 

難しい説明はできません。

白黒のシルエット画像のエッジ端からの距離で、立体度を変える処理をしてくれています。単純にエッジ端からの距離だけで立体化しても滑らかな曲線は描けないので、ポワソン方程式を解く+αで滑らかな曲線にしているということらしいです。

 

ではもらってきたソースで、試しにシルエット画像の棒人間くんを3Dグラフィック化してみましょう。

ln[1] #インポートするライブラリ

import cv2
import numpy as np
from scipy.sparse import coo_matrix, linalg

"from scipy.sparse import coo_matrix, linalg"
このライブラリは初めて使います。高速演算をするためのライブラリのようですね。
白黒画像の黒の部分が多い時にはnumpyを使うよりも早いとか。
へー・・・。そんな使い分けをするレベルにないのでへー・・・としか言えないですが、いつか役立つかもしれないので頭の片隅に置いておきます。
 
In[2] 

def depth2orthomesh(depth, x_step=1, y_step=1, scale=[1.0, 1.0, 1.0], minus_depth=True):
    vertices =
    faces =

    if len(depth.shape) != 2:
        return None
    h, w = depth.shape
    vertex_id = 0
    added_table = {}
    for y in range(0, h, y_step):
        for x in range(0, w, x_step):
            added_table[(y, x)] = -1
    max_connect_z_diff = 99999.9
    # TODO
    # pixel-wise loop in pure python is toooooooo slow
    for y in range(0, h, y_step):
        for x in range(0, w, x_step):
            d = depth[y, x]
            if d <= 0.000001:
                continue
            if minus_depth:
                d = -d
            vertices.append([x * scale[0], y * scale[1], d * scale[2]])
            added_table[(y, x)] = vertex_id
            current_index = vertex_id
            upper_left_index = added_table[((y - y_step), (x - x_step))]
            upper_index = added_table[((y - y_step), x)]
            left_index = added_table[(y, (x - x_step)
)]
            upper_left_diff = np.abs(depth[y - y_step, x - x_step] - d)
            upper_diff = np.abs(depth[y - y_step, x] - d)
            left_diff = np.abs(depth[y, x - x_step] - d)
            if upper_left_index > 0 and upper_index > 0\
               and upper_left_diff < max_connect_z_diff\
               and upper_diff < max_connect_z_diff:
                faces.append([upper_left_index, current_index, upper_index])
            if upper_left_index > 0 and left_index > 0\
                and upper_left_diff < max_connect_z_diff\
                    and left_diff < max_connect_z_diff:
                faces.append([upper_left_index, left_index, current_index])
            vertex_id += 1
    return vertices, faces

ln[3] 

def _make_ply_txt(vertices, faces, color=, normal=):
    header_lines = ["ply", "format ascii 1.0",
                    "element vertex " + str(len(vertices)),
                    "property float x", "property float y", "property float z"]
    has_normal = len(vertices) == len(normal)
    has_color = len(vertices) == len(color)
    if has_normal:
        header_lines += ["property float nx",
                         "property float ny", "property float nz"]
    if has_color:
        header_lines += ["property uchar red", "property uchar green",
                         "property uchar blue", "property uchar alpha"]
    # no face
    header_lines += ["element face " + str(len(faces)),
                     "property list uchar int vertex_indices", "end_header"]
    header = "\n".join(header_lines) + "\n"
    data_lines =
    for i in range(len(vertices)):
        line = [vertices[i][0], vertices[i][1], vertices[i][2]]
        if has_normal:
            line += [normal[i][0], normal[i][1], normal[i][2]]
        if has_color:
            line += [int(color[i][0]), int(color[i][1]), int(color[i][2]), 255]
        line_txt = " ".join([str(x) for x in line])
        data_lines.append(line_txt)
    for f in faces:
        line_txt = " ".join(['3'] + [str(int(x)) for x in f])
        data_lines.append(line_txt)
    data_txt = "\n".join(data_lines)
    ply_txt = header + data_txt
    return ply_txt

ln[4] 

# Implementation of the following paper
# "Notes on Inflating Curves" [Baran and Lehtinen 2009].
# http://alecjacobson.com/weblog/media/notes-on-inflating-curves-2009-baran.pdf
def inflationByBaran(mask, use_sparse=True):
    h, w = mask.shape
    depth = np.zeros((h, w))
    img2param_idx = {}
    param_idx = 0
    def get_idx(x, y):
        return y * w + x
    for y in range(h):
        for x in range(w):
            c = mask[y, x]
            if c != 0:
                img2param_idx[get_idx(x, y)] = param_idx
                param_idx += 1
    num_param = len(img2param_idx.keys())
    triplets =
    cur_row = 0
    # 4 neighbor laplacian
    for y in range(1, h-1):
        for x in range(1, w-1):
            c = mask[y, x]
            if c == 0:
                continue
            triplets.append([cur_row, img2param_idx[get_idx(x, y)], -4.0])
            kernels = [(y, x - 1), (y, x + 1), (y - 1, x), (y + 1, x)]
            for kernel in kernels:
                jj, ii = kernel
                if mask[jj, ii] != 0:
                    triplets.append([cur_row, img2param_idx[get_idx(ii, jj)], 1.0])
            cur_row += 1  # Go to the next equation
    # Prepare right hand side
    b = np.zeros((num_param, 1))
    rhs = -4.0
    cur_row = 0
    for y in range(1, h-1):
        for x in range(1, w-1):
            c = mask[y, x]
            if c == 0:
                continue
            b[cur_row] = rhs
            cur_row += 1
    if use_sparse:
        # Sparse matrix version
        data, row, col = , , []
        for tri in triplets:
            row.append(tri[0])
            col.append(tri[1])
            data.append(tri[2])
        data = np.array(data)
        row = np.array(row, dtype=np.int64)
        col = np.array(col, dtype=np.int64)
        A = coo_matrix((data, (row, col)), shape=(num_param, num_param))
        x = linalg.spsolve(A, b)
    else:
        # Dense matrix version
        A = np.zeros((num_param, num_param))
        # Set from triplets
        for tri in triplets:
            row = tri[0]
            col = tri[1]
            val = tri[2]
            A[row, col] = val
        x = np.linalg.solve(A, b)
    for j in range(1, h-1):
        for i in range(1, w-1):
            c = mask[j, i]
            if c == 0:
                continue
            idx = img2param_idx[get_idx(i, j)]
            # setting z = √ h
            depth[j, i] = np.sqrt(x[idx])
    return depth

ln[5] 

def visualizeDepth(depth, path='', dmin=0, dmax=50, cm_name='viridis'):
    import matplotlib.pyplot as plt
    cm = plt.get_cmap(cm_name)
    colors = (np.array(cm.colors)* 255).astype(np.uint8)
    colors = colors[..., ::-1] # -> BGR
    
    normed = np.clip( (depth - dmin) / (dmax - dmin))(, 0, 1)
    normed = (normed*255).astype(np.uint8)
    
    vis = colors[normed]
    if path != '':
        cv2.imwrite(path, vis)
    return vis

ln[6] 

def writeMeshAsPly(path, vertices, faces):
    with open(path, 'w') as f:
        txt = _make_ply_txt(vertices, faces)
        f.write(txt)

ln[7] 

if __name__ == '__main__':
    names = ['walking1']
    for name in names:
        mask_path = 'Path名/' + name + '.png'
        print(mask_path)
        mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE)
        mask[mask > 100] = 255
        mask[mask <= 100] = 0
        
        depth = inflationByBaran(mask)
        visualizeDepth(depth, name + '_baran.jpg')
        vertices, faces = depth2orthomesh(depth)
        writeMeshAsPly(name + '_baran.ply', vertices, faces)

もはや中身の理解は無理なので、とりあえず”def”で区切って書きました。
触る場所はln[7]のnames の画像の名前部分と、画像ファイルのPathとファイル名の部分だけですね。
(ソースの不要な部分は削除してます)
 
これを実行すると、以下のような画像が生成されました!
 
成功しているのかどうかよく分からないので実際にステレオグラム化して確認してみましょう。
上記の画像をグレースケールに変換してステレオグラム化のアルゴリズムに放り込んでしまえば良いのですが、背景が紫色なのでグレースケールにすると背景が灰色になってしまいます。背景は黒になって欲しいので、ここは手作業で背景を黒に変更します。
 
そうして作った以下の画像をステレオグラム化のアルゴリズムに入れます。
 
 
 
ひとまず背景はランダムドットノイズで試してみます。
 
ln[1] #背景のパターンとパターンサイズ

def make_pattern(shape=(16, 16), levels=64 ):
      return np.random.randint( 0, levels - 1, shape)/ levels
pattern = make_pattern(shape=(128,64))

ln[2] #ステレオグラムアルゴリズムの定義

def make_autostereogram(depthmap, pattern, shift_amplitude=0.1, invert=False):
    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

ln[3] #ステレオグラム

    pattern = make_pattern(shape=(128,64))
    depthmap = cv2.imread("Path/ファイル名.jpg") 
    depthmap = cv2.cvtColor(depthmap, cv2.COLOR_BGR2GRAY)
    depthmap = depthmap/255
    autostereogram = make_autostereogram(depthmap, pattern, 0.3)
    autostereogram = np.clip(autostereogram * 255, a_min = 0, a_max = 255).astype(np.uint8)
    cv2.imwrite("Path/ファイル名.jpg", autostereogram)

完了!

出来上がったものがこちらです。

 
おおー。ちゃんと3Dになってますね!ゴミも見えません。
 
ゴミは見えないけど。
棒人間の胸部が異様に膨らんでいて。。。ちょっとホラー・・・。
シルエット画像のチョイスを間違えた感がありますね。
 
でもこれで思い通りのステレオグラムが作れそうです。
 
 
======================================

追記

■ポリゴンはどうなの?

曲線の3Dグラフィック画像を使うとステレオグラム化したときにゴミが出ないことが分かりました。

「では、カクカクしたポリゴン画像で試したらどうなんだろう?」ということが気になったので試してみました。

 

試すのはこの2枚の画像



 

これをそれぞれステレオグラムしたのがこちらです。↓



あー・・・そうですよね、だめですよね。そんな予感はしていました。

大人しく曲線グラフィックを使います。

 

ステレオグラムの作り方_実践編2(ゴミ対応)

■謎のゴミを消す

ステレオグラムの作り方_実践編1で発見されたゴミの対応を検討していきます。

前回作り上げた菜の花畑のステレオグラムですが、下図の赤線の形のゴミが出てしまっていました。特にピントを雲に合わせると見えやすいです。

 

深度マップは↓なので、間には何もないはずなのですが。

なんとなく察するに。。。立体の処理?が閉じていないというか。。。

左目用の画像はあるけど、右目用の画像を作らないまま立体にしてしまったような。

 

こうならなきゃいけないのに

今こんな感じ

 

 

 深度マップに書かれている絵は2Dなのに3Dで表現しようとしているから奥行き表現を出さないといけない箇所で視差が生じなくておかしくなるのかな?

3Dグラフィックで立体的な雲を描いたら直るかもしれないですね。

・・・雲の絵を3Dグラフィックで描く???

どうやって?

 

・・・

 

調べてきました。

3Dグラフィックソフト「Blender」というフリーソフトがあり、これで3Dグラフィックを描けるそうです。ただし慣れが必要。GIMPより難しそうです。

さすがに雲は描けないなぁ。。

 

妥協してBlenderで球を描いてみました。これが精いっぱいです。

普通の2Dの円と3Dの球で比較しようと思います。

仮定が正しければ2Dではゴミが発生して、3Dではゴミが出ないはず。

 

まず、円をステレオグラムにした画像。

円の表面にピントを合わせるとゴミが見えますね。

 

次、球をステレオグラムにした画像だと。


見えません!成功です!

そっか、立体に見せようとすると、深度マップの画像も3Dで描くべきなんですね。

いやーでもBlender難しいなぁ。

 

 

 

 

 

ステレオグラムの作り方_実践編1(菜の花畑)

ステレオグラムのデザインを考えよう

今日はステレオグラムのデザインをします。夢が広がる部分ですね。

まずは深度マップから。

深度マップの色の濃さによって3Dの浮き上がり度合いが変わると分かったときから、やってみたかったことがあります。

ステレオグラムをきれいな風景写真で作ってみたいのですが、浮き上がり度合いが変えられるのなら、風景を前景、中景、後景に分けて表現してみたいのです。前景は白にして一番大きく浮き上がらせて、中景はグレーに、後景は黒に近い灰色にして奥の方にという感じです。

背景には写真を使いますが、その写真にも遠近感をつけたいと思います。

こんな感じ↓



試行錯誤して、前景~空までのバランスを下のようにしました。あと、何も絵がないと寂しいので空に雲でも書いておきましょう。画像サイズは無難そうな1280×720にします。

では、上の深度マップに合わせる背景の写真を準備します。

以前スイカジュースの写真で試した時の感触から、背景写真の横のサイズは128ピクセルぐらいにとどめておいた方が良さそうです。

縦は深度マップのそれぞれ前景・中景・後景・空の高さと同じになるように作ります。

 

前景の画像

128(横)×64(縦)サイズ

中景の画像

128(横)×144(縦)サイズ



後景の画像

128(横)×92(縦)サイズ

 

空の画像

128(横)×420(縦)サイズ

 

これをPythonでくっつけます。

 

In[1] #インポートするライブラリ

import numpy as np
import cv2

In[2] #画像をくっつける

img1 = cv2.imread('画像のPath/ファイル名')
img2 = cv2.imread('画像のPath/ファイル名') 
img3 = cv2.imread('画像のPath/ファイル名') 
img4 = cv2.imread('画像のPath/ファイル名') 
img5 = cv2.imread('画像のPath/ファイル名')                                                                                                                                                                                        pattern_imshape=[1080,128,3]
pattern = np.zeros(pattern_imshape, dtype= img1.dtype)
pattern = cv2.vconcat([img1,img2,img3,img4,img5])
cv2.imwrite(’保存先のPath/ファイル名' , pattern)

できた画像がこちらです。↓

 

128(横)×720(縦)サイズ

 

これで深度マップも背景の元も完成しました。

これをステレオグラム化のアルゴリズムステレオグラムの作り方_応用編3から変更なし)に放り込んでみます!

 

ステレオグラム

In[1] #インポートするライブラリ

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

In[2] #上で作った背景の元を読み込む

pattern = cv2.imread('画像のPath/ファイル名'')
pattern cv2.cvtColor(pattern, cv2.COLOR_BGR2RGB)

In[3] #上で作った深度マップを読み込む


img_depth  =
 cv2.imread('画像のPath/ファイル名')
img_depth  = cv2.cvtColor(depth_grad, cv2.COLOR_BGR2RGB)
 
def make_depthmap(shape=(720,1280,3)):
      black_img = np.zeros(shape, dtype=np.float64)
      depthmap = (black_img + img_depth)/255
      return depthmap                                                                                                                                                                                                                          depthmap = make_depthmap()

In[4] #ステレオグラムアルゴリズム適用 

def make_autostereogram(depthmap, pattern, shift_amplitude=0.1, invert=False):
     
    if invert: 
        depthmap = 1 - depthmap
    autostereogram np.zeros_like(depthmap, dtype=pattern.dtype)                                                                                                                                                        for color in np.arange(autostereogram.shape[2]):                                               
         for r in np.arange(autostereogram.shape[0]):
 
              for c in np.arange(autostereogram.shape[1]):
                if c < pattern.shape[1]:
                    autostereogram[r, c, color] = pattern[r % pattern.shape[0], c, color] 
                else
                    shift = int(depthmap[r, c, color] * shift_amplitude * pattern.shape[1])
                    autostereogram[r, c, color] = autostereogram[r, c - int(pattern.shape[1]) + shift, color]
 
    return autostereogram
 
autostereogram = make_autostereogram(depthmap, pattern, 0.3)
plt.imshow(autostereogram)

 

よーしよしよしできました!

いざ!チェック!

 

おお

おお?

おや?

前景・中景・後景の見え方は大体期待通りではあるけれど、、、

雲の右側になんかゴミが残ってますね。

 

あと、左端から横128ピクセルまでは立体になってませんね。左の雲が切れちゃってます。

横0~128ピクセル目までは背景の元のパターンをそのまま貼っているから、よく考えたらそりゃそうですが横0~128ピクセルまでの画像を参照して129ピクセル目以降を作っているのでこれはやむなしかな。。。

 

うーん。雲の右側のゴミ。

これは気持ち悪いなぁ。

 

次はこのゴミを消す方法を考えていきたいと思います。

 

 

ステレオグラムの作り方_応用編3(カラー化)

■好きな画像でステレオグラムを作りたい

今回はいよいよカラー化をします。

ステレオグラムが作れることが分かると、次は綺麗なステレオグラムを見たくなってきてしまいました。

綺麗なステレオグラムを作るのにあたってカラー化は必須!

しっかり考えていきましょう。

 

背景は「ステレオグラムの作り方_応用編1」で使ったスイカジュースの画像を使います。

In[1] #インポートするライブラリ

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

In[2] #背景の元を作成

pattern = cv2.imread('画像のPath')
pattern = cv2.cvtColor(pattern, cv2.COLOR_BGR2RGB)
plt.imshow(pattern)

表示は無事にカラー化できました。

cv2で画像を読み込んでいるので色の並びがBGRになっています。cv2.cvtColorで画像データの並びをBGRからRGBに変換します。

plt.imshowのカラーマップは何を選んで良いか分からなかったので何も記載しませんでしたが、いい感じにカラー化してくれました。ありがとう。

 

次に深度マップですがこちらも「ステレオグラムの作り方_応用編2」で使った深度マップを使い回します。

In[3] #深度マップを作成

depth_grad = cv2.imread('画像のPath/ファイル名.png')
depth_grad = cv2.cvtColor(depth_grad, cv2.COLOR_BGR2RGB)
 
def make_depthmap(shape=(400,600,3)):
      black_img = np.zeros(shape, dtype=np.float64)
      depthmap = (black_img + depth_grad)/255
      return depthmap 
 
depthmap = make_depthmap() 
plt.imshow(depthmap, cmap="gray")

使い回しはしますが、shapeの型だけは背景画像のカラーと揃えておきます。

グレースケールの時はshapeが(400,600)でしたが、背景がカラーになったので

背景画像と同じカラーの型でshapeが(400,600,3)となるようにしておきます。

詳細は下のIn[5]にて。

 

In[4] #基本編1からずっとやってきた正規化は削除です。

def normalize(depthmap):
        return depthmap/255

カラー化したらdepthmapの最大値が255じゃなくなったようで、255のままだとエラーが出るようになりました。そもそも正規化する意味が見つけられなかったので、必要性が理解できるときまで、削除しておきます。

 

In[5] #ステレオグラムアルゴリズム適用 

def make_autostereogram(depthmap, pattern, shift_amplitude=0.1, invert=False):
     
    if invert: 
        depthmap = 1 - depthmap
    autostereogram np.zeros_like(depthmap, dtype=pattern.dtype)                                                                                                                                                        for color in np.arange(autostereogram.shape[2]):                                               
         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
 
autostereogram = make_autostereogram(depthmap, pattern, 0.3)
plt.imshow(autostereogram)

さて、問題はIn[5]ですね。

ステレオグラム化のアルゴリズムでは、背景の元になるスイカジュースの画像を台紙(深度マップと同じサイズの台紙)に並べてペタペタ貼っていくことをしています。

 

いままではグレースケール画像だったのでモノクロ用の台紙1枚だけに「台紙(深度マップ画像と同じサイズ)いっぱいになるまで背景画像を貼り続ける」ということを"for ** in "で実行すればよかったのですが、ここにカラーという要素が加わります。

カラーになると背景画像を張り付ける台紙が赤・緑・青の3枚に増えます。(下図のイメージ)

なので、 「深度マップ画像の縦サイズと横サイズいっぱいになるまで貼り続ける」ということを3回繰り返さないといけないです。

 

   グレースケールの時         カラーの時

 

"for ** in range(3)"

と書いても動くと思いますが、格好をつけたいのでautostereogram.shapeの値を使いたいと思います。

"autostereogram.shape"と打つと(400,600,3)と出ます。 3番目の3が赤・緑・青の台紙の3色を表現してくれるので、これを

 for color in np.arange(autostereogram.shape[2]):

という形で使います。

 

結果がこちら↓

 

 おおおお!いい感じではないですか。

すごいすごい!

 

では、次回はちょっとヒネリを入れったデザインでステレオグラムを作っていきます!

 

ステレオグラムの作り方_応用編2(深度マップを作る)

■好きな画像でステレオグラムを作りたい

今回はステレオグラムで3D表示させたいものを作っていきます。

基本編では白黒の円を表示させてました。ステレオグラム化のアルゴリズムを見ると灰色も判別してくれるようなので、灰色も入れた深度マップを作りたいと思います。

 

試しに使ってみたのがこの3パターン

 8諧調のグラデーション

 16諧調のグラデーション

 256諧調のグラデーション

それぞれどんな見え方になるのか確認ます。

背景はまずはランダムドットノイズに設定します。

In[1] #インポートするライブラリ

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

In[2] #ランダムドットノイズの背景の元を作成

def make_pattern(shape=(16,16), levels=64):
       return np.random.randint(0, levels-1, shape)/ levels
pattern= make_pattern(shape=(128,64)) 

 
次に本題の深度マップを作成。
深度マップはラスター画像(普通のお絵かき画像)で作ります。
ベクター画像と呼ばれる数式で作られた画像の方がノイズがないのでキレイに作れそうですが、数式では複雑な画像を作れないので。
お絵かき作成ツールはWindows標準アプリのペイントとフリーソフトGIMPを使いました。GIMP…あんまり使ったことがなかったので苦戦しました。
In[3] #深度マップを作成

depth_grad = cv2.imread('画像のPath/ファイル名.png')
depth_grad = cv2.cvtColor(depth_grad, cv2.COLOR_BGR2GRAY)
 
def make_depthmap(shape=(400, 600)): 
      depthmap = np.zeros(shape, dtype=np.float64)
      depthmap = depthmap+depth_grad 
      return depthmap 
 
depthmap = make_depthmap() 
plt.imshow(depthmap, cmap="gray")

画像ができたらcv2.imreadで画像を読み込みます。読み込んだらグレースケールに変換。
この画像とは別で”np.zeros”で400×600サイズの真っ黒な画像を作っておいて、これを土台にして読み込んだ画像(背景は透明にしてあります)を上に重ねて深度マップの完成です。
後から思いましたが普通に黒背景で作れば早かった。

In[4] #正規化

def normalize(depthmap):
        return depthmap/255

def make_autostereogram(depthmap, pattern, shift_amplitude=0.1, invert=False):
    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
 
autostereogram = make_autostereogram(depthmap, pattern, 0.3)
plt.imshow(autostereogram, cmap='gray')

 

できました!

おお!諧調ごとに飛び出し具合が違いますね。

 

おや?

入力した深度マップは上から

 8段階

 16段階

 256段階

の諧調があるので、上から

 8段の階段

 16段の階段

 滑らかな坂

に見えるのかと予想していましたが、一番下、20段数段しか階段できていませんね。

計算してみたら丸められていたりするんでしょうか。

背景のパターンを128×64サイズで作っているから限界があるのか。背景のランダムドットノイズがそもそも64諧調しかないからダメなのか。。背景を写真でも作ってみましたがやっぱり滑らかな坂にはなりませんでした。

ふむ。この辺は宿題として残しておきます。(溜まる一方)

 

何はともあれ、諧調を変えることで飛び出し具合を変えることができることが分かりました!

 

次はカラー化に挑戦しようと思います。

 

ステレオグラムを作ろう_応用編1(背景を変更)

■好きな画像でステレオグラムを作りたい

●好きな画像で背景を作る

基本編ではランダムドットノイズを背景に使用していましたが、見ていて楽しいものを背景にしたいので、自分で用意した画像を背景にしてステレオグラムを作っていきます。

 

使用する画像はこちら。スイカジュースの写真です。



基本編1で使ったランダムドットノイズ作成のコードを書き換えてみました。

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

pattern = cv2.imread('画像のPath/ファイル名.jpg')
plt.imshow(pattern, cmap= "gray")

グレースケールに変えたつもりでいるのに、グレースケールにならない不思議。

 

紫色になったのはcv2で画像を読み込んでいるので色の並びが逆になっているせいだとわかっていますが(変換をさぼりました)。。。実に気持ち悪い色になったものだ。

matplotlibでグレースケールにしようとしても効かなくて、cv2.cvtColorの方でグレースケールにしないと変わらないのかな?

 

ならこれは?

cv2でグレースケール化してmatplotlibの方のグレースケール化を削除してみました。

pattern = cv2.imread('画像のPath')
pattern = cv2.cvtColor(pattern, cv2.COLOR_BGR2GRAY)
plt.imshow(pattern)

わぁ。悪化した。

 

 

ならば!

cv2でグレースケール化もmatplotlibの方のグレースケール化も入れてみました。

pattern = cv2.imread('画像のPath')
pattern = cv2.cvtColor(pattern, cv2.COLOR_BGR2GRAY)
plt.imshow(pattern , cmap='gray')

 ようやく成功です。

グレースケールの画像を表示させるだけでずいぶん苦労しましたが、以降のコードに変更はありません。出来上がった画像はこちら。↓

あらら。写真の縦128×横68はちょっと大きかったかもしれません。
でもちゃんと円が見えます。

 

見え方が変わるのかと思い、画像サイズを変えてみました。

さっきの半分縦64×横34です

 見えますが、あまりに小さいと背景の画像が何なのか分からないので面白くなくなってしまいそうです。

 

これは画像によって微調整が必要ですね。

 

次は深度マップを好きな画像に変えてみたいと思います。