iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0

在前一篇文章中,我們建立了一個空白的視窗,並且加入了一個紅色的正方體。這一篇文章我們要來畫出相機模型,讓我們可以看到相機的位置和方向。

一般來說,我們可以用各種3D物體放在空間中,代表相機的位置和朝向,也可以簡單的畫一個箭頭、或是一個 3D 座標軸來代表相機。不過實務上,我們常用的是視錐體(frustum)來代表相機的位置和方向,因為這樣可以直觀的看到相機的視野範圍和朝向。

視錐其實就是一個四面體,有一個頂點代表相機的位置,另外四個頂點代表相機的視野範圍,我們可以用一條條線段,畫出這個四面體的邊。假設我們的中心在 (0, 0, 0) 的位置,然後朝向 z=+1 的方向,每個頂點分別在 (±0.5, ±0.5, 1) 的位置,把線畫出來,以下是一個簡單的範例:

import sys
from vispy import app, scene

# Create canvas
canvas = scene.SceneCanvas(title="vispy tutorial", keys="interactive", show=True)
# Make color white
canvas.bgcolor = "white"

# Create view and set the viewing camera
view = canvas.central_widget.add_view()
view.camera = "turntable"
view.camera.fov = 50
view.camera.distance = 10

def create_frustum():
    center = np.array([0, 0, 0])
    points = np.array([
        [0.5, 0.5, 1],
        [0.5, -0.5, 1],
        [-0.5, -0.5, 1],
        [-0.5, 0.5, 1],
    ])
    
    for i in range(4):
        line = scene.visuals.Line(pos=np.array([center, points[i]]), color="red", antialias=True, width=2, parent=view.scene)
        line = scene.visuals.Line(pos=np.array([points[i], points[(i + 1) % 4]]), color="red", antialias=True, width=2, parent=view.scene)

create_frustum()


if __name__ == "__main__":
    if sys.flags.interactive != 1:
        app.run()

就會得到以下的畫面:

frustum1

而大部分的圖片都是不是方形的,因此我們加上 aspect_ratio 這個參數調整,再加上一個半透明的平面代表相機的成像平面:

def create_frustum(aspect_ratio=1.3):
    center = np.array([0, 0, 0])
    points = np.array([
        [0.5, 0.5, 1],
        [0.5, -0.5, 1],
        [-0.5, -0.5, 1],
        [-0.5, 0.5, 1],
    ])
    points[:, 0] *= aspect_ratio
    
    for i in range(4):
        line = scene.visuals.Line(pos=np.array([center, points[i]]), color="red", antialias=True, width=2, parent=view.scene)
        line = scene.visuals.Line(pos=np.array([points[i], points[(i + 1) % 4]]), color="red", antialias=True, width=2, parent=view.scene)
    
    # Create the semi-transparent plane
    plane = scene.visuals.Polygon(pos=points, color=(1, 0, 0, 0.5), parent=view.scene)
    # Here the z-axis of the plane is ignored, so we need to translate it
    plane.transform = scene.transforms.MatrixTransform()
    plane.transform.translate([0, 0, 1])

create_frustum()

frustum2

如果我們有圖片的檔案,我們也可以把圖片投影到這個平面上,這樣就可以看到相機的成像效果。在vispy中,我們可以透過 scene.visuals.Image 來加入圖片:

def create_frustum_with_image(image_path):
    # Read image and convert to numpy array. Make values in [0, 1]
    image = Image.open(image_path)
    width, height = image.size
    aspect_ratio = width / height
    image = np.array(image).astype(np.float32) / 255.0
    # Add alpha channel
    alpha = np.ones((height, width, 1), dtype=np.float32) * 0.5
    image = np.concatenate([image, alpha], axis=2)
    
    # Create the image visual
    image_visual = scene.visuals.Image(image, parent=view.scene)
    
    # Scale and move the image to fit the frustum plane
    image_scaling = 1.0 / height
    image_translate = (-width / 2.0 * image_scaling, -height / 2.0 * image_scaling)
    image_visual.transform = scene.transforms.STTransform(scale=(image_scaling, image_scaling), translate=image_translate)
    z_transform = scene.transforms.MatrixTransform()
    z_transform.translate([0, 0, 1.0])
    image_visual.transform = z_transform * image_visual.transform
    
    center = np.array([0, 0, 0])
    points = np.array([
        [0.5, 0.5, 1],
        [0.5, -0.5, 1],
        [-0.5, -0.5, 1],
        [-0.5, 0.5, 1],
    ])
    points[:, 0] *= aspect_ratio
    
    for i in range(4):
        line = scene.visuals.Line(pos=np.array([center, points[i]]), color="red", antialias=True, width=2, parent=view.scene)
        line = scene.visuals.Line(pos=np.array([points[i], points[(i + 1) % 4]]), color="red", antialias=True, width=2, parent=view.scene)
    

create_frustum_with_image("lena.png")

主要的步驟是讀取圖片,然後把圖片加入到 scene.visuals.Image 中,最後透過 scene.transforms.STTransform 來縮放和移動圖片(這部分有點繁複,但主要是把圖片塞到視錐的平面上)。最後的結果如下:

frustum3


上一篇
Day4: vispy 視覺化 (一)
下一篇
Day6: 相機姿態變化練習
系列文
3D 重建實戰:使用 2D 圖片做相機姿態估計與三維空間重建30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言