Theta S の動画をPythonを使ってリアルタイムスティッチングしたかった.

Thetaも次世代機が現れて安価に手に入るようになりました.中古だと2万円を切るんですね.
今更ながら,このTheta SをUSBカメラとしてライブストリーミングして,スティッチングしてみました.

コードは以下のブログを参考にしています.
https://ensekitt.hatenablog.com/entry/2017/12/19/200000
http://blog.livedoor.jp/tmako123-programming/archives/50769806.html

上記からの変更点は,以下の二点

  • 一番重なり方が自然な立体射影を採用
  • Numpyを利用して高速化  
# python3
import cv2
import numpy as np
import matplotlib.pyplot as plt
import random
# %matplotlib inline
import itertools
import math

# def cv_imshow(image_bgr):
#     image_rgb = cv2.cvtColor(image_bgr,cv2.COLOR_BGR2RGB)
#     plt.imshow(image_rgb)
#     plt.show()

vertex = 640
src_cx = 319
src_cy = 319
src_r = 283
src_cx2 = 1280 - src_cx


def align_imshow(image):
    image_s = cv2.resize(image, (1280, 720))
    dst_map = np.array(
        list(itertools.product(range(vertex), range(vertex * 2))))

    map_x = np.zeros((vertex, vertex * 2))
    map_y = np.zeros((vertex, vertex * 2))
    phi1 = math.pi * np.arange(vertex * 2) / vertex
    theta1 = math.pi * np.arange(vertex) / vertex
    X = np.matrix(np.cos(phi1)).T * np.sin(theta1)
    Y = np.matrix(np.sin(phi1)).T * np.sin(theta1)
    Z = np.cos(theta1)
    phi2 = np.arccos(-X)
    phi_cond = phi2 < np.pi / 2
    # tmp = np.sqrt(np.multiply(Y,Y) + np.multiply(Z,Z))
    # theta2 = np.multiply(np.sign(Y),np.arccos(-np.divide(Z,tmp)))
    theta2 = np.multiply(np.sign(
        Y), np.arccos(-np.divide(Z, np.sqrt(np.multiply(Y, Y) + np.multiply(Z, Z)))))
    # 立体射影
    r_ = np.where(phi_cond, np.tan((phi2) / 2), np.tan((np.pi - phi2) / 2))

    cos_theta2 = np.cos(np.where(phi_cond, theta2, math.pi - theta2))
    sin_theta2 = np.sin(np.where(phi_cond, theta2, math.pi - theta2))
    map_x = (src_r * r_ * cos_theta2 + np.where(phi_cond, src_cx, src_cx2)).T
    map_y = (src_r * r_ * sin_theta2 + src_cy).T
    map_x = map_x.astype('float32')
    map_y = map_y.astype('float32')

    return cv2.remap(image_s, map_x, map_y, cv2.INTER_LINEAR, cv2.BORDER_CONSTANT)


cap = cv2.VideoCapture(-1)

while True:
    # VideoCaptureから1フレーム読み込む
    frame = cap.read()

    edframe = align_imshow(frame)
    cv2.imshow('Edited Frame', edframe)

    # キー入力を1ms待って、k が27(ESC)だったらBreakする
    k = cv2.waitKey(1)
    if k == 27:
        break

# キャプチャをリリースして、ウィンドウをすべて閉じる
cap.release()
cv2.destroyAllWindows()

結果

リアルタイム...?という感じですね.
自分が写り込んでしまうので,公開できる全天球動画を録画するのは難しい...

追記 (2020/08/23)

上記の処理のうち,map_x, map_yはリアルタイム計算には不要な処理でした. 下記のようにするとほぼリアルタイムに動作させることができます.

# python3
import cv2
import numpy as np
import random
import itertools
import math

vertex = 640
src_cx = 319
src_cy = 319
src_r = 283
src_cx2 = 1280 - src_cx

dst_map = np.array(
    list(itertools.product(range(vertex), range(vertex * 2))))

map_x = np.zeros((vertex, vertex * 2))
map_y = np.zeros((vertex, vertex * 2))
phi1 = math.pi * np.arange(vertex * 2) / vertex
theta1 = math.pi * np.arange(vertex) / vertex
X = np.matrix(np.cos(phi1)).T * np.sin(theta1)
Y = np.matrix(np.sin(phi1)).T * np.sin(theta1)
Z = np.cos(theta1)
phi2 = np.arccos(-X)
phi_cond = phi2 < np.pi / 2
theta2 = np.multiply(np.sign(
    Y), np.arccos(-np.divide(Z, np.sqrt(np.multiply(Y, Y) + np.multiply(Z, Z)))))

# 立体射影
r_ = np.where(phi_cond, np.tan((phi2) / 2), np.tan((np.pi - phi2) / 2))

cos_theta2 = np.cos(np.where(phi_cond, theta2, math.pi - theta2))
sin_theta2 = np.sin(np.where(phi_cond, theta2, math.pi - theta2))
map_x = (src_r * r_ * cos_theta2 + np.where(phi_cond, src_cx, src_cx2)).T
map_y = (src_r * r_ * sin_theta2 + src_cy).T
map_x = map_x.astype('float32')
map_y = map_y.astype('float32')


def align_imshow(image):
    return cv2.remap(cv2.resize(image, (1280, 720)), map_x, map_y, cv2.INTER_LINEAR, cv2.BORDER_CONSTANT)


cap = cv2.VideoCapture(-1)

while True:
    # VideoCaptureから1フレーム読み込む
    _, frame = cap.read()

    edframe = align_imshow(frame)
    cv2.imshow('Edited Frame', edframe)

    # キー入力を1ms待って、k が27(ESC)だったらBreakする
    k = cv2.waitKey(1)
    if k == 27:
        break

# キャプチャをリリースして、ウィンドウをすべて閉じる
cap.release()
cv2.destroyAllWindows()