2024年9月8日

anamorph鏡筒變形畫的幾何轉換原理,使用python實作

 2013年時,我玩了一個叫做鏡筒玩玩變形畫anamorph的東西






當時就想要寫一支程式來作這種影像變換,不過當時還不知道從何下手,直到最近上影像處理的課,講到了仿射變換的作法,才讓我想通可以怎麼作。

以影像的放大來舉例

原始影像在x方向放大a倍,在y方向放大b倍,新影像可以表示為:
x' = ax
y' = by

就可以用原始影像算出新影像應該長什麼樣子,
舉例來說,原始影像在x方向和y方向都放大2倍,
那麼原始影像的(1,1)和(1,2)就會變成新影像的(2,2)和(2,4)兩個點的顏色,

但是新影像的(2,3)呢?原始影像並沒有(1,1.5)這個點啊,所以那個點就會沒有顏色。所以實作上就會用inverse mapping 來處理。

原本的方程式可以改寫成
x = x' /a
y = y' /b

再透過一些內插法(Interpolation)就可以計算新影像應該是什麼顏色。例如剛剛的問題,新影像的(2,3)的顏色就是原始影像的(1,1.5)的顏色。如果使用Near Neighbor Interpolation,就用原始影像的(1,2)來代表(1,1.5),如果是用Bilinear Interpolation,就用原始影像的(1,1)和(1,2)的顏色算平均得到(1, 1.5)的顏色,然後在指定給新影像的(2,3)。

接下來再來思考這種鏡筒變形畫怎麼製作吧。

鏡筒變形畫


原始影像長這樣


新影像要長這樣



也就是要先將原始影像的座標從笛卡爾座標變成極座標,然後在極座標上進行幾何轉換。

原始影像的紅點和藍點都在同一個x座標上,經過變換後,它們的極座標都會在同樣的θ 上,而它們的r由它們的y座標決定,y座標越小,則r越大,像是變換後的紅點會移動到最外圍。以藍點為例,原始的y座標是影像高度的4/7,變換後的r就是 r0 + r1(4/7)

原始影像的紅點和綠點在同一個y座標上,變換後它們的 r 都一樣,而θ則不相同,由原始影像的x座標決定。

用數學方程式表達這種變換如下:

定義參數:

  • f(x, y):原始影像的像素值。
  • r0:定義影像的初始半徑。
  • r1:定義影像的變換範圍半徑。
  • θ:角度座標,用來決定像素的旋轉。
  • r:半徑座標,用來決定像素的距離。
  • (x0, y0):中心點的坐標。
  • nx, ny:原始影像的寬度和高度。

變換步驟:

1. 極座標轉換:

r = √((x - x₀)² + (y - y₀)²)

θ = arctan2(y - y₀, x - x₀)

其中 (x₀, y₀) 是新影像的中心,xy 是新影像的坐標點。

2. 新座標映射:

新的橫坐標 map_x 和縱坐標 map_y 是透過角度 θ 和半徑 r 映射回原始影像的座標:

map_x = nx - 1 - (nx - 1) · (θ + π) / 2π

map_y = ny - 1 - (ny - 1) · (r - r₀) / r₁

這些公式將極座標 rθ 重新映射到原始影像的笛卡爾坐標 (map_x, map_y),然後將原影像進行扭曲或展開。

3. 有效區域檢測:

使用mask來確保映射後的像素點在有效範圍內:

valid_mask = (map_x ≥ 0) ∧ (map_x < nx) ∧ (map_y ≥ 0) ∧ (map_y < ny)

4. 生成新影像:

將原影像的有效像素根據映射後的座標填充到新影像中,無效區域使用白色填充。


根據這樣的原理所撰寫的程式在github

https://github.com/ChihHsiangChien/anamorph

其中的anamorph.py 就是產生變形畫的程式
其中有兩個參數start_radian 和 spread_radian ,指定的數值是弧度,為np.pi的倍數
start_radian = np.pi時,弧形的起點從np.pi的方向開始畫,開展的弧度是0.5π = 90°



開展弧度設定成1.95π,接近2π也就是擴展成360°


start_radian則決定從哪個弧度開始畫圖,若為0則是長這樣,你可以跟前面幾張圖比較看看