1. 程式人生 > 其它 >AI 讓觀眾成為 3D 版《老友記》的導演了?

AI 讓觀眾成為 3D 版《老友記》的導演了?

《老友記》上線 3D 版了?

允許使用者旋轉鏡頭,且從近景切換到全景觀看故事?

今年出爐的 3D 方向 AI 專案 SitCom3D,能夠自動補齊《老友記》原劇中的三維拍攝空間,使用者可以選擇主檢視、側檢視等不同角度欣賞劇集。鏡頭的主導權在觀眾手中,彷彿親臨拍攝片場。

https://www.bilibili.com/video/BV1p84y1r7ye/?aid=606073788&cid=917566564&page=1

舉個栗子,在原劇中只出現了以下兩個畫面。

看了《老友記》後,AI直接還原出畫面的3D場景,構造不同角度下的鏡頭故事。

https://www.bilibili.com/video/BV1p84y1r7ye/?aid=606073788&cid=917566640&page=2

這流暢的運鏡,彷彿就是新上線的 3D 版老友記,並且你就是導演!

專案介紹:情景喜劇的三維重構

這個專案由 UC 伯克利的 Georgios Pavlakos 等研究人員在 ECCV 2022 “The One Where They Reconstructed 3D Humans and Environments in TV Shows” 論文中提出,旨在藉助 AI 完成影視劇集中的3D重建。

3D建模在很多領域都有廣泛的應用,然而傳統的重建方式耗時巨大,比如在製造業,一個工業級模型需要專業建模師花費數週時間。目前快速獲得3D模型的方式有兩種,一種是靠儀器掃描獲得三維形狀資料,例如點雲;另一種則是基於深度學習,使用AI建模。後者更直觀,且成本更低。想象一下用手機拍幾張、甚至一張照片就能獲得高精度的3D模型,既可以為元宇宙線上生活提供基礎設施,又能在傳統工業領域加速研發流程。

提到2D影象轉3D模型,繞不開近兩年大火的NeRF神經網路。自2020年發表以來眾多高校和業界公司基於它研發了各自版本的NeRF:英偉達推出了極速版的instant NeRF;蘋果的NeuMan框架等等。

NeRF為2D轉3D提供了新的思路,其背後的機制,相關論文解讀有很多,這裡僅做簡述:NeRF通過對數張2D照片的學習,使用神經輻射場的方式建立起畫素點位置(x, y, z)和相機引數(θ, φ)對應影象的volume density體積密度和RGB顏色值的關係,訓練完成後以此生成新的視角。通過這種方式,NeRF相較傳統的3D建模方式能夠生成更精細的還原。

回到TV Show的三維重建專案,如下圖(fig2)所示,研究人員正是基於NeRF模型,通過分析整個劇集裡的三維資訊,精確感知和重建3D人體姿勢和演員位置,並生成新的不存在劇集裡的2D角度影象。

生成3D模型的原理如下圖所示,首先從劇集輸入中通過SfM(Structure-from-Motion)方法估計出攝像機的位置,並通過NeRF重建出精確的環境3D場景資訊。接著根據多鏡頭和單鏡頭的情形,進行人體3D重建。最後基於這些資訊進行更多的編輯和開發,比如在電視劇中刪除一個人,或者插入一隻兔子。


這個專案目前已在github上開源。

專案地址:https://github.com/ethanweber/sitcoms3D
論文地址:https://ethanweber.me/sitcoms3D

感興趣的小夥伴可以直接在矩池雲上覆現這個專案,具體操作方式如下:

專案復現:用矩池雲快速實現三維重構

1、開啟矩池雲官網,進入主機市場,找到合適的機器




等待機器啟動,啟動完成介面如下,點選進入 Jupyter Notebook

2、進入命令列,進行程式碼下載與安裝


依次輸入以下命令

cd /mnt
git clone https://hub.fastgit.xyz/ethanweber/sitcoms3D.git

如果下載慢可以用進入 https://hub.fastgit.xyz/ethanweber/sitcoms3D 手動下載後上傳

https://github.com/CMUAbstract/cote.git

進入資料夾

cd sitcoms3D/

安裝依賴

pip install -r requirements.txt
pip install tqdm

解壓資料
矩池雲已經為大家準備好了 sitcoms3D 資料,大家直接將 /public/data/image/sitcoms3D_data.zip 解壓到 sitcoms3D-master 專案資料夾中的 data 下即可

unzip /public/data/videos_and_music/sitcoms3D_data.zip -d /mnt/sitcoms3D/data

3、進入Jupyter notebook




開啟demo,在Jupyter中“Run all cell”即可執行官方的例程。

4、調整版程式碼

官方 Demo 碼有演示性質,直接執行有可能一些變數會受到干擾,因此我們對程式碼進行了一定的精簡,可以根據需要用以下方式進一步進行使用。

這一模型匯入的資料檔案有以下七個目錄,應該是不同的資料,比如預設的sitcom_location = "Friends-monica_apartment" 表示從老友記裡面選取資料

在第2個cell中更改sitcom_location 可以改變資料。

sitcom_location = sit_locs[0] 
# sitcom_location = "Friends-monica_apartment"

下一步為是選擇影象,原文中使用了romdom隨機選擇一個影象,我們可以加一行程式碼來指定自己的影象。

# choose a random image to work with
image_name = random.choice(list(nerf_image_name_to_info.keys()))
image_name = "ELR_S09E01_00007186.jpg"
print("Showing camera information for image:", image_name)
pprint(nerf_image_name_to_info[image_name])

更改後的程式碼如下:在不指定影象的情況下,每次run all cell 即可隨機抽取影象。

# import various modules
%load_ext autoreload
%autoreload 2
import copy
import json
import os
import random
from pprint import pprint

import mediapy as media
import numpy as np
import smplx
import torch
import trimesh
from tqdm import tqdm
# some custom code
# gross import... maybe put into a python module later
import sys
sys.path.append("..")
from utils.dataloader import human_to_nerf_space, load_colmap_cameras_from_sitcom_location
from utils.render_utils import render_human
from utils.io import load_from_json

這一段定位了資料的路徑並抽取出影象

sit_locs = sorted(os.listdir("../data/sparse_reconstruction_and_nerf_data"))
print(sit_locs)

sitcom_location = sit_locs[3]
print("load.....",sitcom_location)

cameras = load_from_json(f"../data/sparse_reconstruction_and_nerf_data/{sitcom_location}/cameras.json")

nerf_image_name_to_info = {}
for dict_ in cameras["frames"]:
    nerf_image_name_to_info[dict_["image_name"]] = {
        "intrinsics": np.array(dict_["intrinsics"]),
        "camtoworld": np.array(dict_["camtoworld"]),
    }
    
image_name = random.choice(list(nerf_image_name_to_info.keys()))
print("image name: ", image_name)
    
basedir = f"../data/sparse_reconstruction_and_nerf_data/{sitcom_location}"
colmap_image_name_to_info = load_colmap_cameras_from_sitcom_location(basedir)

point_cloud_transform = np.array(cameras["point_cloud_transform"])
scale_factor = np.array(cameras["scale_factor"])
# colmap_rescale = float(smpl_data["colmap_rescale"])

根據抽取的影象讀取其中的人物資料

# the set of image names that we used for nerf
# these images are included in the sparse_reconstruction_and_nerf_data/ folder
nerf_image_names = set(nerf_image_name_to_info.keys())

# the set of image names that we have smpl parameters for
# this is wherever our method "calibrated multi-shot" was run
human_pairs = load_from_json(f"../data/human_pairs/{sitcom_location}.json")
image_name_to_shot_change_image_name = {}
calibrated_multishot_image_names = set()
for image_name_a, human_idx_a, image_name_b, human_idx_b in human_pairs:
    calibrated_multishot_image_names.add(image_name_a)
    calibrated_multishot_image_names.add(image_name_b)
    image_name_to_shot_change_image_name[image_name_a] = image_name_b
    image_name_to_shot_change_image_name[image_name_b] = image_name_a

image_names = nerf_image_names.intersection(calibrated_multishot_image_names)
print("Found {} images that are used for nerf and contain smpl parameters".format(len(image_names)))

# choose a random image name to work with and visualize
# image_name = random.choice(list(image_names))
# image_name = "Friends_S08E20_00001431.jpg"

# read data for the image and a human...
human_data = load_from_json(f"../data/human_data/{sitcom_location}.json")

image_human_data = human_data[image_name]
print("going to visualize image {} with {} humans".format(image_name, len(image_human_data)))

顯示讀取的圖片

image = media.read_image(f"../data/sparse_reconstruction_and_nerf_data/{sitcom_location}/images/{image_name}")
media.show_image(image, height=200)

匯入SMPL模型

model_folder = "../data/smpl_models"
model_type = "smpl"
gender = "neutral"

body_model = smplx.create(model_folder,
                     model_type=model_type,
                     gender=gender)

用於將人物資料載入到模型,提取出mesh的函式

def get_human_obj_mesh(image_name: str, human_idx: int):
    if "smpl" not in human_data[image_name][human_idx]:
        print(f"smpl values don't exist for {image_name} and human_idx {human_idx}")
        return None
    smpl_data = human_data[image_name][human_idx]["smpl"]
    print(smpl_data.keys())

    camera_translation = torch.tensor(smpl_data["camera_translation"])[None]
    betas = torch.tensor(smpl_data["betas"])[None]
    global_orient = torch.tensor(smpl_data["global_orient"])[None]
    body_pose = torch.tensor(smpl_data["body_pose"])[None]
    colmap_rescale = float(smpl_data["colmap_rescale"])

    output = body_model(
        betas=betas,
        global_orient=global_orient,
        body_pose=body_pose,
        return_verts=True)

    vertices = output.vertices + camera_translation
    pose_colmap = torch.from_numpy(colmap_image_name_to_info[image_name]["camtoworld"]).float()
    pose_colmap[:3,3] *= colmap_rescale
    # homogeneous coordinates
    vertices = torch.cat([vertices, torch.ones_like(vertices[..., 0:1])], dim=-1)
    vertices = vertices @ pose_colmap.T
    vertices = vertices[...,:3]

    out_mesh = trimesh.Trimesh(vertices[0].detach().numpy(), body_model.faces, process=False)
    human_obj_filename = "temp.obj"
    out_mesh.export(human_obj_filename);

    # specify the human to render
    obj_mesh_original = trimesh.load(human_obj_filename, process=False)
    obj_mesh = human_to_nerf_space(obj_mesh_original, point_cloud_transform, scale_factor, colmap_rescale)
    return obj_mesh

應用get_human_obj_mesh函式,獲取影象的mesh資料

human_obj_meshes = []
for human_idx in range(len(image_human_data)):
    print("human_idx", human_idx)
    obj_mesh = get_human_obj_mesh(image_name, human_idx)
    if obj_mesh:
        human_obj_meshes.append(obj_mesh)

現實提取出模型的結果

def show_humans(human_obj_meshes, pose, K, image_name):
    image = media.read_image(f"../data/sparse_reconstruction_and_nerf_data/{sitcom_location}/images/{image_name}")
    color_h, depth_h, alpha_h = render_human(human_obj_meshes, pose, K)
    media.show_image(image, height=200, title="Image we use for camera pose and intrinsics")
    media.show_image(color_h, height=200, title="Image of humans rendered from this camera")
    composited = (color_h * alpha_h[...,None] + image * (1 - alpha_h[...,None])).astype("uint8")
    media.show_image(composited, height=200, title="Composited image")

pose = nerf_image_name_to_info[image_name]["camtoworld"]
K = nerf_image_name_to_info[image_name]["intrinsics"]
show_humans(human_obj_meshes, pose, K, image_name) # image_name to read the background image

模型的侷限性

當然,Sitcoms 也受到訓練模型的的一些侷限性,在我們執行的案例中,有相對成功的結果,也有相對混亂的結果。

相對失敗的圖片可以完整地提取出三維資訊,但有可能圖片僅能顯示出畫面的一部分人,甚至會將一些物體誤判為人物,Sitcom 仍存在一些魯棒性的問題。

參考資料

論文地址:https://arxiv.org/abs/2207.14279
GitHub地址:https://github.com/ethanweber/sitcoms3D
專案主頁:https://ethanweber.me/sitcoms3D/
``