Darkside(https対応しました) |
2022年6月5日(日) 21:56
静止画 VR180 3D のステッチソフトを、自作で完成させてみた。
DUAL FISHEYE で撮影した静止画は、jpeg 撮って出しでなければ EOS VR Utility
でステッチできない。そのため、ステッチ前に jpeg 圧縮されてしまっているので最高画質は狙えないし、画質調整も行えない。
Digital Photo Professinal でRAW現像し、場合によっては更に画質調整も行った上で、ステッチしたい。そこで役立つのが、自作ソフトだ。
LX、LY、RX、RY は左右魚眼映像の中心座標であり、自分の DUAL FISHEYE の値が入っている。製品レンズの個体差により、ベストな値は少しずつ異なっているはずである。調査して最適値を設定するのが望ましい。
コマンドラインから、python vrst.py d:\1234.tif out.png のような感じで実行する。
コマンドライン用なので、ファイル入出力のエラー処理などは省略。入力ファイルが無かったりしても、そのままコマンドラインでエラー吐いてくれて実用上は別に困らない。気になるなら、処理を追加してみて欲しい。
import sys import numpy as np import cv2 import argparse parser = argparse.ArgumentParser(description='VR180 3D 撮影した静止画のステッチ') parser.add_argument('infile', help='入力画像ファイル名') parser.add_argument('outfile', help='出力画像ファイル名') args = parser.parse_args() dm_w = 4096 dm_h = 4096 dfish = 3684 arr_sin_dm_phi = [np.pi] * (dm_w * 2) arr_cos_dm_phi = [np.pi] * (dm_w * 2) xmap = np.zeros((dm_h, dm_w), np.float32) ymap = np.zeros((dm_h, dm_w), np.float32) try: # try to load map file xmap = np.load("xmap.npy") ymap = np.load("ymap.npy") except IOError: # if the map files are not exist, generate new map files. print("Generating map files...") for x in range(dm_w * 2): dm_x = float(x) dm_phi = np.pi * 2.0 * dm_x / dm_w / 2.0 arr_sin_dm_phi[x] = np.sin(dm_phi) arr_cos_dm_phi[x] = np.cos(dm_phi) for y in range(dm_h): dm_y = float(y) dm_theta = np.pi * dm_y / (dm_h-1) sin_dm_theta = np.sin(dm_theta) cos_dm_theta = np.cos(dm_theta) xst = int(dm_w / 2) xen = int(dm_w * 3 / 2) for x in range(xst,xen): dm_x = float(x) sin_dm_phi = arr_sin_dm_phi[x] cos_dm_phi = arr_cos_dm_phi[x] tmp2_x = sin_dm_theta * cos_dm_phi tmp2_y = sin_dm_theta * -sin_dm_phi tmp2_z = cos_dm_theta tmp_x = tmp2_x tmp_y = tmp2_y tmp_z = tmp2_z tmp2_x = tmp_x tmp2_y = tmp_y tmp2_z = tmp_z car_x = tmp2_x car_y = tmp2_y car_z = tmp2_z tmp_x = car_x tmp_y = car_y tmp_z = car_z car_x = tmp_z; car_y = tmp_y; car_z = -tmp_x; dm_theta = np.arccos(car_z) if (car_y > 0): dm_phi = np.arctan(car_x / car_y) elif (car_y < 0): dm_phi = np.pi - np.arctan(car_x / -car_y) elif (car_y == 0 and car_x > 0): dm_phi = np.pi / 2.0 elif (car_y == 0 and car_x < 0): dm_phi = -np.pi / 2.0 else: dm_phi = 0 xmap[y,x-xst] = (dm_theta / (np.pi) * np.cos(-dm_phi)) * float(dfish) + float(dfish/2) ymap[y,x-xst] = (dm_theta / (np.pi) * np.sin(-dm_phi)) * float(dfish) + float(dfish/2) np.save("xmap.npy", xmap) np.save("ymap.npy", ymap) xmapL = np.zeros((dm_h, dm_w), np.float32) ymapL = np.zeros((dm_h, dm_w), np.float32) xmapR = np.zeros((dm_h, dm_w), np.float32) ymapR = np.zeros((dm_h, dm_w), np.float32) LX = 6210 LY = 2595 RX = 1982 RY = 2609 CX = LX - dfish / 2 CY = LY - dfish / 2 for i in range(len(xmapL)): xmapL[i] = xmap[i] + CX for i in range(len(ymapL)): ymapL[i] = ymap[i] + CY CX = RX - dfish / 2 CY = RY - dfish / 2 for i in range(len(xmapR)): xmapR[i] = xmap[i] + CX for i in range(len(ymapR)): ymapR[i] = ymap[i] + CY im = cv2.imread(args.infile) left = cv2.remap(im, xmapL, ymapL, cv2.INTER_LINEAR, cv2.BORDER_CONSTANT) right = cv2.remap(im, xmapR, ymapR, cv2.INTER_LINEAR, cv2.BORDER_CONSTANT) om = cv2.hconcat([left, right]) cv2.imwrite(args.outfile, om)
読み書きは OpenCV 任せなので、読める画像フォーマットと書ける画像フォーマットも OpenCV 次第。
現像と画質調整をしっかり行い、8bit 無圧縮TIFF で出力。それを読ませるのがお薦め。16bit TIFF を読ませたいなどと考えても、たぶん OpenCV は over 8bit を生かしてくれない。だから読ませる前に、画質調整は完璧に済ませておくこと。
出力は、png が最高画質だ。
Quest 2 で表示させる場合は、どうやら jpeg にしないと駄目な模様。適当なツールで、png を jpeg に変換する。OpenCV 出力画像で拡張子を jpg にしても良いが、最高画質の jpeg にしてくれるかどうか怪しい。それよりは、素性の分かっているソフトで最高画質の jpeg に変換した方が良い。
動画化素材にするなら、png のまま Davinci Resolve などに読ませる。
written by higashino [Virtual Reality] [この記事のURL] [コメントを書く] [コメント(0)] [TB(0)]
Generated by MySketch GE 1.4.1
Remodelling origin is MySketch 2.7.4