擺爛仙君小課堂開課了,本期將介紹如何手搓VR眼鏡,并將隨手拍的電影變成3D視頻。
一、3DGS模型介紹
3D 高斯模型是基于高斯函數構建的用于描述三維空間中數據分布概率的模型,高斯函數在數學和物理領域有著廣泛應用,其在 3D 情境下的拓展為我們理解和處理三維數據提供了強大工具。在三維空間中,高斯模型的概率密度函數形式較為復雜,涉及多個參數。中心點坐標 (μ_x, μ_y, μ_z) 決定了分布的中心位置,即數據最可能聚集的點。而協方差矩陣則描述了數據在各個方向上的分布范圍和相關性。協方差矩陣是一個對稱的 3×3 矩陣,其對角線元素 (σ_x2, σ_y2, σ_z2) 分別代表數據在 x、y、z 軸上的方差,方差越大,說明數據在該軸方向上的分散程度越高;非對角線元素 σ_xy、σ_xz、σ_yz 反映了不同坐標軸之間的協方差,衡量了兩個變量之間的線性相關程度,比如 σ_xy 為正,表明 x 和 y 方向的數據變化呈正相關趨勢。
該模型在諸多領域都有重要應用,其中最重要的就是可以惡搞自己的室友,如圖。
但是我們本期要講的是如何通過3D濺射模型把普通視頻變成3D視頻。
二、VR眼鏡盒子
VR眼鏡盒子是一種較為基礎且便捷的虛擬現實設備。其外觀通常小巧輕便,便于攜帶和使用,主體部分多為一個類似眼鏡的框架結構,中間有可容納顯示屏的區域,兩側設有綁帶或夾子等固定裝置,用于將設備穩固地佩戴在頭上,確保使用者在使用過程中不會輕易滑落或移位。它的工作原理在于借助智能手機等具備顯示功能的設備來作為顯示屏,將手機放入盒子中特定的位置后,通過盒子內部的光學鏡片等元器件對手機屏幕上的畫面進行放大和聚焦,營造出一種身臨其境的立體視覺效果。當使用者轉動頭部時,借助手機內部的陀螺儀等傳感器,可實時感知頭部的運動方向和角度變化,并將這些信息反饋給正在運行的VR應用,從而使得畫面能夠相應地進行切換或調整,讓使用者仿佛置身于一個全方位、沉浸式的虛擬場景之中,無論是觀看3D電影、體驗虛擬旅游還是進行一些簡單的VR游戲,都能提供較為出色的沉浸感體驗。不過,由于其成本相對較低,技術也較為基礎,在畫面的清晰度、視場角以及幀率等方面可能不如一些高端的VR頭顯設備,但其親民的價格和便捷的操作方式使其在普通消費者群體中有著較為廣泛的應用,為大眾接觸和體驗虛擬現實技術提供了一個入門級的便捷選擇。
假設透鏡的焦距為f,透鏡與顯示屏之間的距離為d,顯示屏上的圖像高度為h,經過透鏡放大后的像高度為H。根據透鏡成像公式:
f1?=u1?+v1?
其中,u 為物體到透鏡的距離(即顯示屏到透鏡的距離),v 為像到透鏡的距離。對于VR眼鏡盒子來說,透鏡通常被設計為靠近顯示屏的一側,因此 u 是一個較小的正值。根據成像公式,可以求出像距 v。
像的放大率 M 可以表示為:
M=hH?=uv?
通過調整透鏡的焦距 f 和物體距 u,可以控制像的放大率和成像位置,從而優化用戶的視覺體驗。VR眼鏡盒子利用雙眼視覺和視差原理來營造三維立體效果。人類的雙眼分別位于頭部的兩側,兩眼之間的距離(稱為瞳距)導致兩眼看到的物體圖像存在細微差異,這種差異稱為視差。大腦通過對兩眼圖像的融合和處理,能夠感知物體的深度和立體感。在VR眼鏡盒子中,左右兩個透鏡分別將兩幅略有差異的圖像(通常由VR應用生成)投射到用戶的左右眼中,模擬了人眼觀察真實世界時的視差效果。這種視差信息被大腦處理后,用戶就會感受到虛擬場景的深度和立體感。
三、博主的實操記錄
1.手搓VR眼鏡
VR眼鏡的技術原理在之前已經講的很清楚了,無非就是雙目視差罷了,所以實現起來也很簡單,找個空紙殼然后折疊成下圖這個樣子,然后放上兩個放大鏡片就好了,是不是有手就行。
什么?說沒有放大鏡片,沒事兒,仙君教你:在塑料瓶的上半部分中裁出兩個圓片,然后將這兩個圓片用膠水粘起來,里面放滿水,一個放大鏡片這不就做出來了嗎?還不會的同學就自己上網買一個吧,也就十來塊的樣子,沒錢的可以在評論區找博主報銷。
2.普通視頻轉成VR視頻
這種VR視頻比較普通,就是將一個視頻變成兩個分視角的圖片,多的不說,上干貨:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt# 加載預訓練的深度估計模型和相關文件
def load_depth_model():# 模型和配置文件路徑(假設已下載對應的模型文件)model_path = "models/midas_v2_1_small.onnx"# 加載模型depth_model = cv.dnn.readNet(model_path)return depth_model# 深度估計函數
def estimate_depth(frame, depth_model):# 將圖片轉換為模型需要的格式img = cv.cvtColor(frame, cv.COLOR_BGR2RGB)img = cv.resize(img, (256, 256), interpolation=cv.INTER_AREA)# 創建blobblob = cv.dnn.blobFromImage(img, 1/255., (256, 256), (123.675, 116.28, 103.53), swapRB=True, crop=False)# 設置輸入并前向傳播depth_model.setInput(blob)depth_map = depth_model.forward()depth_map = depth_map.reshape((256, 256))# 歸一化深度圖depth_map = cv.normalize(depth_map, None, 0, 1, cv.NORM_MINMAX)return depth_map# 根據深度圖計算視差圖
def compute_disparity(depth_map, max_disparity=50):# 計算視差圖,簡單的將深度圖轉換為視差圖# 實際應用中可能需要更復雜的計算disparity_map = (1.0 - depth_map) * max_disparityreturn disparity_map.astype(np.int32)# 根據視差圖生成右視圖
def generate_right_view(left_view, disparity_map):height, width = left_view.shape[:2]# 將視差圖擴展到與圖像相同的尺寸disparity_map_img = cv.resize(disparity_map, (width, height), interpolation=cv.INTER_NEAREST)# 創建右視圖right_view = np.zeros_like(left_view)# 沿x軸移動每個像素,根據視差圖的值(簡單的水平位移)for y in range(height):for x in range(width):disp = disparity_map_img[y, x]new_x = x - dispif new_x >= 0 and new_x < width:right_view[y, new_x] = left_view[y, x]else:# 處理邊界情況,使用原圖的邊緣像素填充if new_x < 0:right_view[y, 0] = left_view[y, x]elif new_x >= width:right_view[y, width - 1] = left_view[y, x]return right_view# 讀取視頻文件
input_video_path = 'input_video.mp4'
output_video_path = 'output_3d_video.mp4'# 打開視頻文件
cap = cv.VideoCapture(input_video_path)
if not cap.isOpened():print("Error: Could not open video.")exit()# 獲取視頻屬性
frame_width = int(cap.get(cv.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv.CAP_PROP_FPS)# 定義視頻輸出
fourcc = cv.VideoWriter_fourcc(*'MP4V')
out = cv.VideoWriter(output_video_path, fourcc, fps, (frame_width * 2, frame_height))# 加載深度估計模型
depth_model = load_depth_model()# 處理視頻的每一幀
while cap.isOpened():ret, frame = cap.read()if not ret:break# 估計深度depth_map = estimate_depth(frame, depth_model)# 計算視差圖disparity_map = compute_disparity(depth_map)# 生成右視圖right_view = generate_right_view(frame, disparity_map)# 創建左右分屏的3D視頻幀stereo_frame = cv.hconcat([frame, right_view])# 寫入輸出視頻文件out.write(stereo_frame)# 顯示處理后的幀(可選)cv.imshow('3D Video', cv.resize(stereo_frame, (frame_width * 2 // 2, frame_height // 2)))# 按'q'鍵退出循環if cv.waitKey(1) & 0xFF == ord('q'):break# 釋放資源
cap.release()
out.release()
cv.destroyAllWindows()
以上代碼使用深度估計模型來估計每一幀圖像的深度,然后根據深度圖計算視差圖,最后根據視差圖生成右視圖。需要注意的是,這種方法的效果可能不是非常好,實際應用中可能需要更復雜的視差計算方法,例如使用立體匹配算法等。