hirax.net::Keywords::「手持ち」のブログ



2017-01-29[n年前へ]

「手持ちスマホ撮影動画からの超巨大開口レンズ撮影」に挑戦してみよう!? 

 かつて、スマホに搭載されているカメラのレンズはとても小さく、綺麗なボケとは無縁の存在でした。しかし、今や最新のスマホには特殊処理によるボケ生成機能などが備えられています。カメラレンズの光学開口径が小さくとも、たとえば2眼カメラなどを備えて距離情報を取得して、距離情報などからボケを人工的に合成するといった仕組みです。

 そんな最新スマホを持たずとも、大レンズのボケ味を手に入れるために、「手持ちスマホ撮影動画からの超巨大開口レンズ撮影」に挑戦してみました。スマホ動画から巨大開口レンズ撮影の手順はとても簡単、まずは目の前の風景にスマホを向けて・なるべくスマホが平面上を動くように意識しながら(スマホを動かしつつ)動画撮影します。そして、動画の各コマから画像ファイル群を生成し、それぞれの画像が撮影された位置や方向にもとづいて撮影された光情報を加算合成する、というものになります。

 細かい手順は、スマホ撮影動画(の展開画像をもとに) Bundler: Structure from Motion (SfM) から出力された刻々のカメラ位置・方向や特徴点情報ファイル(bundler.out)を読み込み、それらのカメラ情報にもとづいて、刻々の撮影画像をレンズ開口面に沿った(同じ方向を向く平行カメラが存在していた場合の)光線画像を位置・角度ズレを踏まえて重ねることで、任意のピント位置に焦点を合わせた超巨大開口レンズ撮影画像を生成する…というものです。

 試しに、iPhoneを約1.5m×1.0mの範囲で動かしつつ動画撮影し、つまり、レンズ直径約1.5mに相当する範囲で動かしつつ動画撮影し、その画像群から開口合成により超巨大カメラの撮影画像を作り出してみた結果が右上の画像です。

 右上画像を眺めてみても、良好なボケ味どころか、全くピントが合っていない画像にしか見えません。直径が1mを超える開口を持つカメラレンズとなると焦点深度もとても浅くなるのでピントがなかなか合わない…というわけでなく、手持ち撮影動画からのカメラ位置・方向精度が低いせいか、単一カメラに平行合成した後のズレが大きいようです。

 ちなみに、試しに各画像を(撮影方向による傾きを補正しつつ)位置毎に並べてみると、下の画像のようになります。動画撮影からのカメラ位置推定精度が果たして不十分なのかどうか、次は撮影カメラ位置を精度良く知る事ができる撮影治具でも作り、また再挑戦してみたいと思います。

 上記処理のコード手順、Python/OpenCVで書いたコード処理手順は、bundler.outからカメラ位置・方向・焦点距離や歪みパラメータを読み込み、cv2.initUndistortRectifyMapにカメラ情報を渡して、各撮影画像の向き補正用のホモグラフィーマップを作成してremapで変換した後に、各撮影画像を加算合成するという手順です。

 Bundlerの出力ファイルを読み込んでライトフィールド合成を行うOpenCV/Pythonコード、まだまだ間違い含まれているような気もしますが、とりあえずここに貼り付けておくことにします。

import cv2
import numpy as np
from matplotlib import pyplot as plt
from PIL import Image
import math
%matplotlib inline

class camera:
    def __init__(self):
        self.f = 1.0
        self.k1 = 0.0
        self.k2 = 0.0
        self.R = [[0.0,0.0,0.0],[0.0,0.0,0.0],[0.0,0.0,0.0]]
        self.T = [0.0,0.0,0.0]

def readBundlerOut( filePath ):
    f = open(filePath, 'r')
    list = f.readlines()
    f.close()

    numberOfCameras = int((list[1].split())[0]) 
    cameras = []
    for i in range(numberOfCameras):
        aCamera = camera()
        fk1k2 = [float(j) for j in list[5*i+2].split()]
        aCamera.f  = fk1k2[0]
        aCamera.k1 = fk1k2[1]
        aCamera.k2 = fk1k2[2]
        rot = []
        for j in range(1,4):
            rot.append( [float(k) for k in list[ 5*i+2+j ].split()] )
        aCamera.R = rot
        aCamera.T = [float(k) for k in list[ 5*i+2+4 ].split()]
        cameras.append(aCamera)
    return cameras

def readImageList( listPath, imageDirPath ):
    f = open(listPath, 'r')
    list = f.readlines()
    list = [ i.rstrip() for i in list ]
    f.close()
    list = [imageDirPath+fileName for fileName in list]
    return list

class lightField:
    
    def __init__(self):
        self.w = 2000
        self.h = 2000
    
    def loadImageListAndMakeLightField( self, 
                    imagePathList, cameraList, workList, scaleA ):
        self.cimg  = np.zeros((self.h, self.w,3), dtype=np.uint8)
        sum = 1.0
        for i in workList:
            img = cv2.imread( imagePathList[i], cv2.IMREAD_COLOR )
            h, w = img.shape[:2]
            imageHeight = img.shape[0]
            imageWidth =  img.shape[1]
            focalLength = cameraList[i].f
            principalPointX = 0.500000
            principalPointY = 0.500000
            distCoef = np.array([ 0.0, 0.0, 0.0, 0.0, 0.0 ])
            cameraMatrix = np.array([ 
                [focalLength,   
                 0.0,          
                 imageWidth  * principalPointX], 
                [0.0,           
                 focalLength,  
                 imageHeight * principalPointY], 
                [0.0,           0.0,          1.0] 
                           ])
            newCameraMatrix, roi = cv2.getOptimalNewCameraMatrix(
                cameraMatrix,
                distCoef,
                (img.shape[1], img.shape[0]),1,
                (img.shape[1], img.shape[0]) )
            rotMatrix = np.array( cameraList[i].R )
            map = cv2.initUndistortRectifyMap( 
                newCameraMatrix,
                distCoef,
                rotMatrix,
                newCameraMatrix, 
                (img.shape[1], img.shape[0]),
                cv2.CV_32FC1)
            
            undistortedAndRotatedImg = cv2.remap( img, 
                        map[0], map[1],
                        cv2.INTER_LINEAR )
            
            scale = 1.0
            pt3 = np.array(cameraList[i].T) - np.array(cameraList[0].T)
            x =  ( self.w/2.0 - pt3[0] * scale * scaleA ) 
            y =  ( self.h/2.0 - pt3[1] * scale * scaleA ) 
            pts1 = np.float32( [[0,      0],
                                [w, 0],
                                [w, h],
                                [0, h]])
            pts2 = np.float32(  [[x,           y],
                                 [x + w*scale, y],
                                 [x + w*scale, y + h*scale],
                                 [x,           y + h*scale]] )
            M = cv2.getPerspectiveTransform( pts1, pts2 )
            img2 = cv2.warpPerspective( 
                undistortedAndRotatedImg, M, (self.w, self.h) )
            sum = sum + 1.0
            self.cimg = cv2.addWeighted(
                 self.cimg, (sum-1) / sum, 
                 img2,      (1.0) / sum, 0)
    
    def showImage(self):
        plt.figure( figsize=(14,14) )
        plt.imshow( np.array(self.cimg2) ) 
        plt.autoscale( False )

「手持ちスマホ撮影動画からの超巨大開口レンズ撮影」に挑戦してみよう!?「手持ちスマホ撮影動画からの超巨大開口レンズ撮影」に挑戦してみよう!? 






2019-02-18[n年前へ]

手持ちスマホのライトフィールド撮影で「自由視点映像」を作る! 

 「ソフトウェアデザイン 2019年3月号」に、手軽にライトフィールド撮影&再現をする遊びとして、『手持ちスマホのライトフィールド撮影で「自由視点映像」を作る!』と題して、4ページのソフトウェア製作記事を書いてみました。スマホを使って光線場を撮影する手法とPythonコードも解説記事も全部も含めて4ページに超圧縮したものです。超特急な記事になってますが、手持ちのライトフィールド撮影・表示記事が、書展に並ぶ雑誌に載っている…のは面白いかもしれません。

 周囲から差し込む光線群を表すのがライトフィールド=多次元で表現される光線場なら、それを反射する、つまりは多次元のリフレクタンスフィールドの再現については、先月号の「ソフトウェアデザイン 2019年2月号」に『誰かが江戸で手にした浮世絵を「自分の手の中」で甦らせる!』として書いてみました。

 楽しんで頂ければ幸いです。



■Powered by yagm.net