LoginSignup
15
7

More than 3 years have passed since last update.

OpenCVでラスタースクロールする

Posted at

はじめに

アニメーションGIF大好き。

配列(画像)をシフトする numpy.roll()

numpyには配列をシフトする関数がある。

numpy.roll(a, shift, axis=None)

  • a numpy配列。
  • shift 移動量。整数。shiftとaxisはタプルで表現することで複数方向に同時にシフトすることができる。
  • axis 配列の軸。省略可能だが明記したほうがいいだろう。省略したらどうなるかは君の目で確かめてくれ!

numpyだからOpenCVの画像にも使える。「配列の軸」はカラー画像の場合0,1,2がそれぞれ,,カラーチャンネルを意味することはすぐに分かると思う。
では、たとえばaxis=1(横移動)でshiftに正の値を指定したときに右に動くのか左に動くのかは、慣れて覚えるしかない。高校の時、y=f(x)のグラフとy=f(x-a)のグラフを並べて描いてなぜ「そっち」に移動するのかを悩みつつ自分の頭で理解するのと同じだ。

元画像
ocean_small.jpg
axis=0に+20シフト axis=0に-20シフト
ocean_shift_y_plus.png ocean_shift_y_minus.png
axis=1に+100シフト axis=1に-100シフト
ocean_shift_x_plus.png ocean_shift_x_minus.png
axis=2に+1シフト axis=2に-1シフト
ocean_shift_c1.png
青の輝度が高い→緑の輝度が高いにシフト
ocean_shift_c2.png
青の輝度が高い→赤の輝度が高いにシフト

スクロールする

無限ループ内でnumpy.roll()をおこなえば無限スクロールができる。
両端がつながっている画像が望ましいことは言うまでもない。

scroll.py
import cv2
import numpy as np

filename = "keepout.png"
back_origin = cv2.imread(filename)
a = -2                  # speed
while True:
    back = back_origin.copy()
    back = np.roll(back, a*t, axis=1) 
    cv2.imshow("img", back)
    t += 1
    if cv2.waitKey(1) == ord("q"):
        break
cv2.destroyAllWindows()
元画像 アニメーション
keepout.png keepout.gifkeepout.gifkeepout.gif

上に掲げたソースは画面上では動くがアニメGIFを作ってくれるわけではない。アニメGIFの作り方はこちらを参照のこと。
  OpenCVでコマ撮りカメラを作る ~PILとOpenCVの画像変換&PILでアニメGIF作成~

ラスタースクロールする

画像全体でなく行ごとに異なる量のnumpy.roll()をおこなえばラスタースクロールができる。

上半分のみスクロールさせる ~流れる雲~

元画像の雲は厳密には左右でつながっていないのだが、動きがあるためそれほど気にならない。これもいらすとやの人徳といったところか。

元画像 アニメーション
lavender.jpg lavender.gif

ソースは略。

一行ずつシフト量を変える ~揺らめく海底~

20世紀末のテレビゲームでよく見ることができた演出。
実際にそれらがどのような処理をしていたのかは知らない。

元画像 ocean.jpg アニメーション
ocean.jpg ocean.gif

ソースはこちら。

ocean.py
import cv2
import math
import numpy as np

back_origin = cv2.imread("ocean.jpg")
imgH, imgW = back_origin.shape[:2]

# ↓を有効にしておくと動きがよくわかる
# cv2.line(back_origin, (imgW//2,0), (imgW//2,imgH), (0,0,255), 3)
a = imgW//16            # 振幅
b = 2*math.pi/imgH      # 周波数
c = 0                   # 位相のずれ
step = 1                # 計算のステップ 波の粗さ・計算量に関係
tt = 16                 # 周期
t=0
while True:
    back = back_origin.copy()
    for y in range(0, imgH, step):
        x = int(a*math.sin(2*math.pi*(t-c)/tt+b*y))
        back[y:y+step] = np.roll(back[y:y+step], x, axis=1)
    cv2.imshow("", back)
    t += 1
    if cv2.waitKey(1) == ord("q"):
        break

cv2.destroyAllWindows()

この上に背景が透過処理されている魚のイラストを重ね合わせればダライアスのできあがりだ。

ダライアス
darius.gif

一行ずつシフトする方向を変える ~分身の術~

元画像 ninja.jpg アニメーション
ninja.jpg ninja.gif

ソースはこちら。

ninja.py
import cv2
import math
import numpy as np

back_origin = cv2.imread("ninja.jpg")
imgH, imgW = back_origin.shape[:2]

a = imgW//4             # 振幅
c = 0                   # 位相のずれ
step = 1                # 計算のステップ 波の粗さ・計算量に関係
tt = 32                 # 周期
t=0
while True:
    back = back_origin.copy()
    for y in range(0, imgH, step):
        sign = 1 if y%2==0 else -1
        x = int(sign*a*math.sin(2*math.pi*(t-c)/tt))
        back[y:y+step] = np.roll(back[y:y+step], x, axis=1)
    cv2.imshow("", back)
    t += 1
    if cv2.waitKey(1) == ord("q"):
        break

cv2.destroyAllWindows()

一行ずつシフト量と方向を変える ~旅の扉~

あらためて調べると、ドラクエの旅の扉って作品ごと機種ごとに演出が違うのな。
ラスタースクロールと無関係な処理が多いのでソースは略。

アニメーション
tabinotobira.gif

投影変換と組み合わせる ~3Dレースゲーム~

疑似3Dの実装

numpy.roll()はデータのシフト(平行移動)なので変形はできない。投影変換で長方形を台形に変形することで疑似3Dを実装できる。
投影変換についてはこちらを参照のこと。
  投影変換で画像を変形する ~モニタ画面をハックする~

元画像 road.jpg 投影変換を無駄にアニメ化
road.png
アメリカのルート66をイメージしたのに
左側通行になってしまった。
road_anim_2.gif
幅が縮小されるだけでなく
高さ方向にも変形するのか。
numpy.roll() numpy.roll()+投影変換
road_anim_1.gif road_anim_3.gif

カーブの実装

カーブを実装するにはもう一つ手間が必要だ。
上底をオフセットするだけではこうなってしまう。

上底オフセットなし 上底オフセット
road1.png road02.png

なんとかうまいことカーブを描けないか。こんな方法で実装してみた。
まず、目指すカーブの曲線を決める。下底では垂直(まっすぐ)で、次第にカーブし上底でオフセット量に達する曲線。円弧でもクロソイドでもいいが、ここでは計算が楽な二次関数とした

上底オフセット(再) 赤が二次関数
road02.png road03.png

これまでの例では1行ずつオフセットさせたので、ここでは台形であることを利用して少し大雑把な変形をさせてみよう。

投影変換+赤の曲線 画像を投影変換+赤の曲線
road_anim_4.gif road_anim_5.gif

6分割もすれば十分にカーブに見えることが分かった。
numpy.roll()した画像に対しこの分割投影変換をおこなえばカーブのできあがりだ。

カーブのアニメ
road_anim_6.gif

こいつにアレとコレとソレを合体させればハングオンのできあがりだ。
こういうことがあるからスプライト関数には回転機能が必要なのだ。
  OpenCVでスプライトを回転させる #3 ~人任せにせず自分で計算せよ~

ハングオン
road_anim_8.gif

終わりに

毎度のことながら、いらすとやさんにはお世話になっています。
「上底」「下底」という用語を台形の面積の公式以外で使ったのは初めてだ。

15
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
7