大きくなったら大蛇に

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

ステレオグラムの作り方_基本編3 (背景画像を深度マップの画像サイズに並べる)

Pythonステレオグラムを作りたい

 ステレオグラムの作り方_基本編2の続きになります。

 

③背景画像を深度マップの画像サイズに並べる

次はステレオグラム作成の肝の部分です。

128×64ピクセルの背景画像を600×400ピクセルの深度マップ画像と同じサイズになるように並べていきます。このとき、深度マップの画像データの白丸部分は、背景画像をずらして配置する必要があります。

 

では、参考サイトから拝借したコードで実行してみたいと思います。
<参考サイト>

Pythonでオリジナルのランダムドットステレオグラム(RDS)を作る。 - Qiita

Making Your Own Autostereograms using Python | Frolian's blog (flothesof.github.io)

 

In[4]

 def normalize(depthmap):
        return depthmap/255  

In[5]

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')

 

いやぁ、もう何をやっているのか全然分からないけど出来てしまいました。画像の中心に〇が浮かんでいるのが見えます。

あまりの分からなさに怖気づいてしまっていますが、頑張ってコードを読んでいきます。

 
In[4]

 def normalize(depthmap):
        return depthmap/255  

0~255の数字でできているdepthmapのデータを255で割る関数を作ってますね。
depthmapが0~1の値になるようにしているようです。
 
 
 In[5]

1、17、19、20行目

def make_autostereogram(depthmap, pattern, shift_amplitude=0.1, invert=False):
      …
    return autostereogram
 
autostereogram = make_autostereogram(depthmap, pattern, 0.3)
plt.imshow(autostereogram, cmap='gray')

”make_autostereogram”という名前で何かしらの関数を作って、"autostereogram"で

それを実行し、その結果をpltの機能を使ってグレースケールで表示させているんですね。

 

2行目

    depthmap = normalize(depthmap)

前に定義したnormalizeという関数で正規化を実施。

 

4行目、5行目:

    if invert: 
        depthmap = 1 - depthmap 

if False…?ネガポジ変換?わからないのでパス。

 

6行目:

    autostereogram = np.zeros_like(depthmap, dtype=pattern.dtype) 

”autostereogram”で深度マップと同じピクセルサイズの600×400ピクセルの画像を

作ってます。中身のデータは”0”なので、黒一色の画面になっているってことですね。

 

8行目:

    for r in np.arange(autostereogram.shape[0]): 

np.arangeは等差の数列を返す関数です。

 <np.arange([start, ]stop, [step, ]dtype = None)>
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
start:生成する数列の最初の値。初期値は0
stop:生成する数列の最後の値。
step:1ステップ分の差。初期値は1
dtype:生成される数列のデータ型
 
戻り値:stepで指定した等差数列の配列
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
 また、
autostereogram.shape[0]は行(row・縦)の(ピクセル)数
autostereogram.shape[1]は列(col・横)のピクセル
autostereogram.shape[2]は(色の)チャンネル
 です。
 autostereogramの縦のピクセル数と同じだけ繰り返すと書いてあるので、つまり
『今から以下の事を400回繰り返します』、と宣言してます。
 
10行目

        for c in np.arange(autostereogram.shape[1]): 

これも8行目と同じです。autostereogramの横のピクセル数と同じだけ繰り返すと書いてあるので、『今から以下の事を600回繰り返します』、と宣言してます。

 
11行目

            if c < pattern.shape[1]: 

これは『autostereogramの横サイズがpatternの横サイズよりも小さいうちは』ですね

 

12行目

                autostereogram[r, c] = pattern[r % pattern.shape[0], c]

ここではrとcはそれぞれautostereogramの縦と横のピクセルの位置なので ”autostereogram[r, c]”は縦r、横cのポイントの画素の情報ということになります。

次に”pattern[r % pattern.shape[0], c]”ですがa%bは a をbで割った余りを表します。つまり”r % pattern.shape[0]”は rをpattern.shape[0]つまり128で割った余りの値が入ります。

・・・? 何してるのこれ。

例えば、

autostereogramの縦のピクセル数が65のとき

129÷128=1余り1

autostereogramの縦のピクセル数が100のとき

200÷128=1余り72

あ、なるほど!割ったときの余りが0~127に変化するのを利用して、patternを1枚貼ったら次に新しいものを貼るという繰り返しをしているんですね。すごい!頭いい!

 

13、14行目 

              else:
                shift = int(depthmap[r, c] * shift_amplitude * pattern.shape[1])

ええと。

それ以外ならshiftは〇〇に設定するって感じですね。ここが一番大事なところかな。

 

 depthmap:画像を白黒で作ってるので depthmap[r, c]の答えは"0"か"1"しかない

 shift_amplitude:初期値は0.1(defの中の初期値なので後で任意の値に変えられる)

 pattern.shape[1]:横のピクセル数64

 

ということは

  depthmapの黒い背景部分

    0×0.1×64=0

  depthmapの白い円の部分

    1×0.1×64=6.4 ⇒intなので整数部分の6

となって、白い円の部分に該当するピクセルのときだけshiftに値が入るんですね。 

今は画像を白黒でしか作っていませんけど、灰色だったら灰色のシフト量になるように書かれれるから、灰色を使ったらもっとリアルな奥行き感とか影とかが出せそうです。

 

15行目 

                   autostereogram[r, c] = autostereogram[r, c - pattern.shape[1] + shift]

さて、最後の難関。 問題は ”c - pattern.shape[1] + shift”の部分ですね。

  ●depthmapの黒い背景部分だとshift=0で

     c - 64 + 0

  例えばr=200,c=100なら

               autostereogram[r, c]= autostereogram[200, 46]

  autostereogram[200, 46]の部分には12行目のpatternの値が入っているので

  それが参照されるので、patternが連続しているように見える。

 

  ●depthmapの白い円の部分だとshift=6で、

    c - 64 + 6

  depthmapの黒い背景部分までは連続していたパターンが+6ピクセル分ずれて

  入ることになる。

 

ほぉぉ!

 それだけで立体的に見えるものなんですね。視覚って不思議。

 

ともあれ、これでステレオグラムの作り方の基本をマスターしました!!

ここからはこれをベースに好きな画像で作っていきたいと思います。

 

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

Pythonステレオグラムを作りたい

 ”ステレオグラムの作り方_基本編1”の続きになります。

 

②3Dで見せたい画像(深度マップ)を作る

次は浮き出てくる方の画像を作りましょう。

参考から引っ張ってきたコードを実行すると縦400ピクセル×横600ピクセルの白い円が中心に描かれた画像が作られます。
<参考サイト>
 

def make_depthmap(shape=(400, 600)):
    depthmap = np.zeros(shape, dtype=np.float64)
    cv2.circle(depthmap, (int(shape[1]/2), int(shape[0]/2)), 100, (255 ,255, 255), -1)
    return depthmap
    
depthmap = make_depthmap()
plt.imshow(depthmap, cmap='gray')

 

これは黒地に白の模様が入った画像なら何でもいいのかな?

コードを見ていきましょう。

1行目、”make_depthmap”という名前で何かしらの関数を作ってますね。
関数の定義と実行は以下のように書かれます。
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
関数の定義
 def 関数名(引数1, 引数2, 引数3, …) :
    処理内容1
    処理内容2      
       …
    return 戻り値
 
関数の実行
  関数名(引数1, 引数2, 引数3, …)
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐

 

2行目の”np.zeros”は以下の働きをする関数です。

<np.zeros(shape, dtype=np.float64)>
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
shape:サイズ(ここでは画像サイズ)
dtype:データの型(デフォルトfloat64。理解できないため受け入れるだけ)
戻り値:要素がすべてゼロで返ってくる
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐

変数”depthmap”が深度マップの情報になります。depthmapの画像サイズと

どんなデータが入るかを決め、ゼロで初期化するということをしているんですね。

全部ゼロってことは画像は黒で塗りつぶされているってことかな。

 

3行目の”cv2.circle”では円を作っていますね。

<cv2.circle(img, center, radius, color, thickness, lineType, shift)>

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐

img         :円を描く対象となる画像

center       :描画する円の中心座標

radius       :描画する円の半径

color           :円の線の色

thickness :円の線の太さ

lineType      :描画アルゴリズム

shift      :謎

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐

今の場合だと、

 第1引数img :depthmap

 第2引数center:(int(shape[1]/2), int(shape[0]/2))。

        depthmapの画像サイズが横600×縦400なので、縦横をそれぞれ

        2で割って中心座標を(300,200)にしてますね。

 第3引数radius:100ピクセル

 第4引数color:(255, 255, 255)で青・緑・赤のデータがMAXなので白

   第5引数thikness:-1 。マイナス設定だと円の内部を塗りつぶす

 

ということで、全体が真っ黒なdepthmap画像の中心に100 ピクセルの白く塗りつぶされた円が出来上がりました。

 

次はいよいよ

 ③背景画像を深度マップの画像サイズに合うように並べていく 

です!

 

ステレオグラムの作り方_基本編1(背景画像を作る)

Pythonステレオグラムを作りたい

 最近、目が悪くなったと感じています。どうもピント調節機能が落ちているみたい。

そこで、ピント調節をする筋肉の『毛様体筋』の筋トレに使えるステレオグラム画像を作ろうと思い立ちました。(↓こういう画像)

 

ステレオグラム画像を立体視しようとすると、何度もピント調節を繰り返すので毛様体筋の筋トレになるそうなのです。

画像を作るのにはPythonを使います。Pythonは少し前に勉強を始めた言語で、それ以外の言語は一切使ったことがありません。技術レベルは初心者と表現するのもおこがましい状態です。

スマートに作れる気がしませんが、試行錯誤や足掻いた履歴など完成までの過程を残していきたいと思います。

 

■まずは何かを作ってみよう

Python,ステレオグラム”のキーワードで検索してみると、下記の方のサイトがまず出てきました。ふむふむ。これはありがたい。

Pythonでオリジナルのランダムドットステレオグラム(RDS)を作る。 - Qiita

Making Your Own Autostereograms using Python | Frolian's blog (flothesof.github.io)

こちらを参考に作ってみたいと思います。

詳細理解できていませんが、ざっくりと以下の手順で作っていくようです。

 

ステレオグラム作り方>

  ①背景画像を作る(とても小さいサイズで)

  ②3Dで見せたい画像(深度マップ)を作る

  ③背景画像を深度マップの画像サイズに合うように並べていく

   ※この際、深度マップの画像情報に合わせて背景画像をずらす&密度を変える

 

それでは、順番に取り組んでいきましょう。

 

①背景画像を作る

以下は参考サイトから引っ張ってきたコードです。これを実行すると

縦128ピクセル×横64ピクセルの砂嵐の画像を作れます。

 

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

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

pattern = make_pattern(shape=(128,64))
plt.imshow(pattern, cmap='gray')

 
では、中身を見ていきましょう。
 
1行目、”make_pattern”という名前で何かしらの関数を作ってますね。
関数の定義と実行は以下のように書かれます。
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
関数の定義
 def 関数名(引数1, 引数2, 引数3, …) :
    処理内容1
    処理内容2      
       …
    return 戻り値
 
関数の実行
  関数名(引数1, 引数2, 引数3, …)
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
 
2行目の処理内容の部分の”np.random.radient” というのは任意の範囲の整数を
ランダムに返してくれる関数だそう。
<np.random.radient(low, high , size)>
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
  low :乱数の最小値
  high:乱数の最大値。haigh-1の値が実際の最大値
  size :出力する配列のshape。デフォルトはNone。
  戻り値:low からhihg-1の範囲でランダムな値。整数
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
 
つまり、変数"pattern”によって
関数”make_pattern”が呼び出されて、
関数”np.random.radient”が0~63の間でランダムな数値を出力し、
それを64で割って正規化。
それをshapeの(128,64)ピクセルサイズ分実行して
最終的にグレースケールの画像データで表示するんですね。ほうほう。
 
これで背景画像(の小さいサイズ)ができました!
 
 
 
★疑問点①
なぜ16×16で定義してから128×64に設定するんでしょう。
make_patternのshapeを指定しないと16×16ピクセルの画像ができます。
 

これを最初から128×64で作ると都合が悪いことがあるんでしょうか。お作法?
負荷が増えると効いてくる?
悩んではみたものの、これは考えても分からなさそうなので今は流されておくことにします。
 
 
★疑問点②

もう一つ気になっているのは1行目の「levels=64」。np.random.radientの最大値の引数に使われています。

 

      np.random.radient(0, levels-1 , shape)/levels

 

乱数の最大値-1が実際の最大値ということなので、64-1-1=62が実際の最大値になるのかな?

0~62の間でランダムに値を出してから、それを64で割っていますが、なぜこの方法をしているのか分からないのです。

という訳で、試しに値を色々と振ってみました。

 

<乱数の最大値を1にして、割ることをしない場合>

 真っ黒になりました。

 乱数の最大値-1なのでランダムに出される値は1-1=0で、”0”しか選択肢がないので真っ黒になるのは想像通りの動作です。

 

 <乱数の最大値を2にして、割ることをしない場合>

 こちらは白と黒の2値ですね。

 乱数の最大値-1なのでランダムに出される値は2-1=1で、ランダムに出される値は”0”か”1”しか選択肢がないのでこちらも想像通りの動作です。

 

 <乱数の最大値を256にして、割ることをしない場合>

256-1=255で、ランダムに出される値は”0”から”255”で。。。patternに入る値は最大255が入ってるんでしょうけど。
あれ?
乱数の最大値を2にしたとき出力画像が白と黒になっていたので、256だとほぼ白に張り付くと予想していました。だから正規化するために最後は割らないといけないのかと思っていたのに。
 
うん、やっぱりわからない。割らなくても期待通りに出るじゃないですか。
割る必要って…なに。
これも、継続調査扱いにします。
 
疑問が何一つ解決していませんが、とりあえず今回はここまで。
次回は②3Dで見せたい画像(深度マップ)を作るについて記載したいと思います。