2014/06/05(木) [n年前の日記]
#1 [dxruby] DXRuby上で射影変換ができた
元記事・参考記事の内容が全然理解できてないけど。写経してたらそれっぽく描画されるようになったのでアップロード。
Window.drawMorph による描画結果とは、ちょっと違うことが分かるかしら。
ソースは以下。
_projtrans.rb.txt
_lena.png (125KB)
それと、他のスクリプトからも、require すれば使えるように書いておきました。
スクリプト内でやってることを簡単に説明すると…。
インチキとは言うけれど、PS1やSSの3Dゲームも似たようなことをして誤魔化してたので、ゲームなら全然アリじゃないかなと。 *1
Window.drawMorph による描画結果とは、ちょっと違うことが分かるかしら。
ソースは以下。
_projtrans.rb.txt
require 'dxruby' # 4点を指定して射影変換描画 class ProjTrans attr_accessor :images attr_accessor :src_w attr_accessor :src_h attr_accessor :divx, :divy attr_accessor :mdivx, :mdivy attr_accessor :src_pos attr_accessor :src_idx # コンストラクタ # # @param [Object] img Imageオブジェクト # @param [Number] divx x方向の分割数 # @param [Number] divy y方向の分割数 # def initialize(img, divx = 8, divy = 8) self.divx = divx self.divy = divy self.src_w, self.src_h = img.width, img.height self.images = img.sliceTiles(divx, divy) # 画像を分割 # 頂点列を登録 self.src_pos = [] w, h = 0, 0 bx, by = 0, 0 i = 0 (0..self.divy).each do |iy| bx = 0 (0..self.divx).each do |ix| if ix < self.divx and iy < self.divy i = iy * self.divx + ix w = self.images[i].width h = self.images[i].height end puts "#{i} : (#{bx}, #{by})" if false # 元画像のサイズで座標値を割っておく self.src_pos.push([bx.to_f / self.src_w, by.to_f / self.src_h]) bx += w i += 1 end by += h end # 頂点番号を登録 self.src_idx = [] self.divy.times do |iy| self.divx.times do |ix| addy = self.divx + 1 i = ix + iy * addy self.src_idx.push([i, i + 1, i + 1 + addy, i + addy]) end end if false self.src_idx.each_with_index {|a, i| puts "#{i} : #{a}" } end end # 描画 # # @param [Number] x1 点1のx座標 # @param [Number] y1 点1のy座標 # @param [Number] x2 点2のx座標 # @param [Number] y2 点2のy座標 # @param [Number] x3 点3のx座標 # @param [Number] y3 点3のy座標 # @param [Number] x4 点4のx座標 # @param [Number] y4 点4のy座標 # @param [Number] alpha アルファ値 0-255 # @param [Number] z 描画深度 # @param [Number] blend 合成種類 # @param [Number] mdivx drawMorphに与えるx方向の分割数 # @param [Number] mdivy drawMorphに与えるy方向の分割数 # def draw(x1, y1, x2, y2, x3, y3, x4, y4, alpha = 255, z = 0, blend = :alpha, mdivx = 2, mdivy = 2) # a から h までの、8つの係数を求める sa, sb, sc, sd, se, sf, sg, sh = get_system(x1.to_f, y1.to_f, x2.to_f, y2.to_f, x3.to_f, y3.to_f, x4.to_f, y4.to_f) # 射影変換 d = [] self.src_pos.each do |x, y| m = (x * sg + y * sh + 1) u = (x * sa + y * sb + sc) / m v = (x * sd + y * se + sf) / m d.push([u, v]) end self.src_idx.each_with_index do |a, i| i0, i1, i2, i3 = a Window.drawMorph(d[i0][0], d[i0][1], d[i1][0], d[i1][1], d[i2][0], d[i2][1], d[i3][0], d[i3][1], self.images[i], :dividex => mdivx, :dividey => mdivy, :alpha => alpha, :z => z, :blend => blend) end end # 8つの係数を求める # # @note 変換式に与える座標値を元画像のサイズで割らないとこの係数は使えない # # @param [Number] x0 点1のx座標 # @param [Number] y0 点1のy座標 # @param [Number] x1 点2のx座標 # @param [Number] y1 点2のy座標 # @param [Number] x2 点3のx座標 # @param [Number] y2 点3のy座標 # @param [Number] x3 点4のx座標 # @param [Number] y3 点4のy座標 # @return [Array] 8つの係数を配列で返す # def get_system(x0, y0, x1, y1, x2, y2, x3, y3) sx = (x0 - x1) + (x2 - x3) sy = (y0 - y1) + (y2 - y3) dx1 = x1 - x2 dx2 = x3 - x2 dy1 = y1 - y2 dy2 = y3 - y2 z = (dx1 * dy2) - (dy1 * dx2) g = ((sx * dy2) - (sy * dx2))/z h = ((sy * dx1) - (sx * dy1))/z return [ x1 - x0 + g * x1, x3 - x0 + h * x3, x0, y1 - y0 + g * y1, y3 - y0 + h * y3, y0, g, h ] end end if $0 == __FILE__ # ---------------------------------------- # 動作確認 img = Image.load("lena.png") prt = ProjTrans.new(img) if false # ---------------------------------------- # 簡単な使い方の例 Window.loop do break if Input.keyPush?(K_ESCAPE) alpha = 255 z = 0 blend = :alpha divx = 2 divy = 2 # 左上の点から時計回りに4点を指定 prt.draw(160, 100, 320, 20, 600, 460, 100, 300, alpha, z, blend, divx, divy) end else # ---------------------------------------- # 動作確認 font = Font.new(14) step = 0 pos = [] poly_pos = [] morph_enable = false mdiv = 1 Window.loop do break if Input.keyPush?(K_ESCAPE) if Input.mousePush?(M_LBUTTON) # マウスの左ボタンが押されたので座標を記録 pos = [] if step == 0 pos.push([Input.mousePosX, Input.mousePosY]) step += 1 if step >= 4 # 4点が入力された step = 0 poly_pos = pos.dup end end # zキーが押されたら drawMorph で描画するかどうかを切替 morph_enable = !morph_enable if Input.keyPush?(K_Z) # 上下キーで分割数を変更 (1から64まで。128にすると落ちる…) mdiv *= 2 if (Input.keyPush?(K_UP) and mdiv < 64) mdiv /= 2 if (Input.keyPush?(K_DOWN) and mdiv >= 2) if poly_pos.size == 4 # 4点が決まってるので、画像を変形して描画 x1, y1 = poly_pos[0] x2, y2 = poly_pos[1] x3, y3 = poly_pos[2] x4, y4 = poly_pos[3] unless morph_enable # 射影変換で描画する場合 prt.draw(x1, y1, x2, y2, x3, y3, x4, y4, 255, 0, :alpha, mdiv, mdiv) else # drawMorphで描画する場合 Window.drawMorph(x1, y1, x2, y2, x3, y3, x4, y4, img, :dividex => mdiv, :dividey => mdiv) end end # 指定済みの頂点位置を描画 d = 5 pos.each do |p| x, y = p Window.drawLine(x - d, y, x + d, y, C_GREEN) Window.drawLine(x, y - d, x, y + d, C_GREEN) end i = step + 1 y = 2 msg = [ "#{Window.real_fps.to_i} FPS CPU: #{Window.getLoad.to_i} %", "zキー : drawMorph 描画との切替 [#{(morph_enable)? "drawMorph" : "射影変換"}]", "↑↓キー : drawMorph 分割数 [#{mdiv}]", "時計回りで、4回、マウスクリックしてください (#{step+1}/4)" ].each_with_index do |s, iy| Window.drawFont(4, 2 + iy * (font.size + 8), s, font) end end end end一応、グリッドを上から描き込んだlena様画像も置いときます。
_lena.png (125KB)
それと、他のスクリプトからも、require すれば使えるように書いておきました。
# ProjTransの使用例 require 'dxruby' require_relative 'projtrans' img = Image.load("lena.png") prt = ProjTrans.new(img) Window.loop do break if Input.keyPush?(K_ESCAPE) alpha = 255 z = 0 blend = :alpha divx, divy = 2, 2 # 左上の点から時計回りに4点を指定 prt.draw(160, 100, 320, 20, 600, 460, 100, 300, alpha, z, blend, divx, divy) end
スクリプト内でやってることを簡単に説明すると…。
- 元画像を 4x4 とか 8x8分割。
- 4点を指定することで、射影変換の変換式で使う8つの係数を求める。
- 分割された各画像に対して、射影変換後の4点を求める。
- その4点で、Window.drawMorph を使って描画。
インチキとは言うけれど、PS1やSSの3Dゲームも似たようなことをして誤魔化してたので、ゲームなら全然アリじゃないかなと。 *1
◎ 問題点。 :
一応できたものの、問題が。
各ポリゴン?の描画面積が小さい場合や、正方形に近い描画の場合は、Window.drawMorph のみで描画しちゃってもいいだろうと思うわけですよ。小さいポリゴンがチラチラ飛んでる分には、どうせ違いなんてパッと見では分かりませんし。おそらく、広い面積を描画する場合に限って、こういった遠近感がついてる描画を使えるのかなと。ただ、どんな演出・背景構成を思いつくかというと、うーん。
それはともかく、これを Shader でやったらどうなるんだろう…。いや、DXRuby のサンプルスクリプトの中に、既にあった気もしますが…。
- 予想より、CPU負荷が高い。1枚描画するだけで、CPU使用率が 15〜20% ぐらいになってしまう。
- 使い道が思いつかない。
各ポリゴン?の描画面積が小さい場合や、正方形に近い描画の場合は、Window.drawMorph のみで描画しちゃってもいいだろうと思うわけですよ。小さいポリゴンがチラチラ飛んでる分には、どうせ違いなんてパッと見では分かりませんし。おそらく、広い面積を描画する場合に限って、こういった遠近感がついてる描画を使えるのかなと。ただ、どんな演出・背景構成を思いつくかというと、うーん。
それはともかく、これを Shader でやったらどうなるんだろう…。いや、DXRuby のサンプルスクリプトの中に、既にあった気もしますが…。
◎ 参考ページ。 :
_射影変換(ホモグラフィ)について理解してみる - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その11 - デジタル・デザイン・ラボラトリーな日々
他の参考資料もメモ。
_平面射影変換
_重なる気持ち -台形補正- | _level0 | Kayac Front End Engineer's Blog
_射影変換(ホモグラフィ)について理解してみる その2 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その3 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その4 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その5 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その6 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その7 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その8 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その9 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その10 - デジタル・デザイン・ラボラトリーな日々
_ホモグラフィ - Shogo Computing Laboratory
_Saqoosha :: スーパー高速に射影変換するには
_Processingで射影変換(ホモグラフィ) | United Field:中西泰人研究室
_遠近法の射影変換パラメータ計算の高速化(pdf)
_Homography << HIDIHO!
_Projection Matrix - wonderfl build flash online
_射影変換 (Homography) - jsdo.it - Share JavaScript, HTML5 and CSS
_テクスチャマッピング入門 射影変換(ホモグラフィ) - jsdo.it - Share JavaScript, HTML5 and CSS
_射影変換(ホモグラフィ)について理解してみる その11 - デジタル・デザイン・ラボラトリーな日々
m2とm5は近似値なのに、それ以外は結構数値の大きさが違います。これでなぜ射影変換出来るのかと調べたところ、描画処理の部分が下記のようなっており、なんと描画サイズ(dstWidth,dstHeight)で割っておりました。昨日スクリプトを書いた時は描画がおかしくて首を捻ったけれど。この一文を目にしたので同じことをしてみたら、それっぽく描画されて「おお…」と思いました。
他の参考資料もメモ。
_平面射影変換
_重なる気持ち -台形補正- | _level0 | Kayac Front End Engineer's Blog
_射影変換(ホモグラフィ)について理解してみる その2 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その3 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その4 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その5 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その6 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その7 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その8 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その9 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その10 - デジタル・デザイン・ラボラトリーな日々
_ホモグラフィ - Shogo Computing Laboratory
_Saqoosha :: スーパー高速に射影変換するには
_Processingで射影変換(ホモグラフィ) | United Field:中西泰人研究室
_遠近法の射影変換パラメータ計算の高速化(pdf)
_Homography << HIDIHO!
_Projection Matrix - wonderfl build flash online
_射影変換 (Homography) - jsdo.it - Share JavaScript, HTML5 and CSS
_テクスチャマッピング入門 射影変換(ホモグラフィ) - jsdo.it - Share JavaScript, HTML5 and CSS
*1: PS1もSSも、ハードウェアでは、三角形 or 四角形の平面的な変形描画機能しか持ってなかったので、そのまま広い面積を描画してしまうと妙な画面になってしまうわけで。なので、必要な時はポリゴンを分割して、とかやってました。まあ、3Dゲームの場合は、z値を使って分割が必要かどうか判定してたはずですが。
[ ツッコむ ]
#2 [ruby] RubyのStructって速度的にはどうなんだろう
上記のスクリプトを書いてた際に、最初は x,y 座標だけを格納するクラスを作って座標管理してたのですけど。速度的によろしくないのかなと配列で管理するようにしちゃったのです。もしかして、クラスじゃなくて Struct を使えば良かったのだろうか…。
と思ってググっていたら、DXRuby作者様が、 _classとStruct - mirichiの日記 において速度測定していて。Ruby 1.9.1 では、Struct のほうがクラスより遅いのか…。
自分も実験。
もちろん、配列ばかり使うと、どこに何の値が入ってるか分かりづらくなるし、途中に何か要素を追加すると修正が大変だし。書きやすさ・メンテナンスと、処理速度の、トレードオフだったりするのかな。
と思ってググっていたら、DXRuby作者様が、 _classとStruct - mirichiの日記 において速度測定していて。Ruby 1.9.1 では、Struct のほうがクラスより遅いのか…。
自分も実験。
# class, Struct, Array のベンチマーク require 'benchmark' IDX = 0 class Hoge attr_accessor :data def initialize(d = 0) self.data = d end end Fuga = Struct.new(:data) Benchmark.bmbm { |x| x.report("class-new ") { 1000000.times { val = Hoge.new(0) } } x.report("Struct-new ") { 1000000.times { val = Fuga.new(0) } } x.report("Array-new ") { 1000000.times { val = [0] } } val = Hoge.new x.report("class-data= ") { 1000000.times { val.data = 0 } } val = Fuga.new x.report("Struct-data=") { 1000000.times { val.data = 0 } } a = [0, 0, 0, 0] x.report("Array-data= ") { 1000000.times { a[IDX] = 0 } } v = Hoge.new(0) x.report("=class-data ") { 1000000.times { b = v.data } } v = Fuga.new(0) x.report("=class-data ") { 1000000.times { b = v.data } } a = [0, 0, 0, 0] x.report("=Array-data ") { 1000000.times { b = a[IDX] } } }
> ruby --version ruby 1.9.3p545 (2014-02-24) [i386-mingw32] > ruby struct_bench.rb Rehearsal ------------------------------------------------ class-new 0.312000 0.000000 0.312000 ( 0.314018) Struct-new 0.265000 0.000000 0.265000 ( 0.261015) Array-new 0.109000 0.000000 0.109000 ( 0.108006) class-data= 0.172000 0.000000 0.172000 ( 0.175010) Struct-data= 0.187000 0.000000 0.187000 ( 0.175010) Array-data= 0.094000 0.000000 0.094000 ( 0.099006) =class-data 0.078000 0.000000 0.078000 ( 0.077004) =class-data 0.078000 0.000000 0.078000 ( 0.077005) =Array-data 0.062000 0.000000 0.062000 ( 0.065004) --------------------------------------- total: 1.357000sec user system total real class-new 0.312000 0.000000 0.312000 ( 0.310017) Struct-new 0.250000 0.000000 0.250000 ( 0.256015) Array-new 0.109000 0.000000 0.109000 ( 0.106006) class-data= 0.171000 0.000000 0.171000 ( 0.177010) Struct-data= 0.172000 0.000000 0.172000 ( 0.175010) Array-data= 0.109000 0.000000 0.109000 ( 0.099005) =class-data 0.078000 0.000000 0.078000 ( 0.078005) =class-data 0.078000 0.000000 0.078000 ( 0.077004) =Array-data 0.063000 0.000000 0.063000 ( 0.065004)生成時に値を渡すなら、Struct のほうが速いのか…。でも、Array を生成するほうが、もっと速いな…。値を代入するのも、読み取るのも、Array のほうが速いっぽい。
もちろん、配列ばかり使うと、どこに何の値が入ってるか分かりづらくなるし、途中に何か要素を追加すると修正が大変だし。書きやすさ・メンテナンスと、処理速度の、トレードオフだったりするのかな。
[ ツッコむ ]
以上、1 日分です。