Loading [MathJax]/extensions/tex2jax.js
MENU

360度カメラの全天球画像ビューアーを作る その4

360度画像

 これまでの記事を元に、Pythonで全天球画像(equirectangular/CubeMap)を変換して表示するGUIを作ってみました。ちゃんとビューアーになりました。

 こんな感じ。

スポンサーリンク

コード

 前回の記事で360度画像のコンバータを作りましたが、いまいちどんなパラメータを入れるとどんな画像に変換されるか直感的ではなかったので、GUIをつけることにしました。CubeMapからの変換もできます。

 前回の記事のコードを呼んでやる形で作っただけなので、GUI部は基本的に表示系しか仕事をしていません。一応それっぽくぐりぐり動かせるようにしました。

 前回の記事のコードをio360_converter.pyって名前で保存しておいて、そのわきに今回のコードを置いたイメージです。動きはコメント見てください…。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import tkinter as tk
import tkinter.filedialog
import cv2
from PIL import ImageTk, Image
import numpy as np
 
import io360_converter as io360 # 前回の記事のコード
 
WIDTH = 960
HEIGHT = 540
SCALE = 1   # 表示用(2だったらWIDTH/HEIGHTを半分にして表示)
 
# 表示用GUIクラス
class laps_viewerGUI(tk.Frame):
     
    def __init__(self, master=None):
        super().__init__(master)
        self.img = np.zeros((32, 64, 3), np.uint8)  # 初期表示用画像
 
        # ----------------------# 上側のフレーム(画像表示部)#----------------------#
        fm_upper = tk.Frame(master)
        fm_upper.pack(fill=tk.X, side=tk.TOP)
 
        # 表示
        self.canvas = tk.Canvas(fm_upper, width=WIDTH//SCALE, height=HEIGHT//SCALE)
        self.canvas.bind("<Button-1>", self.click)
        self.canvas.bind("<B1-Motion>", self.drag)
        self.canvas.bind("<Control-MouseWheel>", self.ctrl_wheel)
        self.canvas.pack(side=tk.LEFT)
 
        self.image_Tk = ImageTk.PhotoImage(Image.fromarray(self.img), master=self.canvas)  # 表示画像の生成
 
        # ----------------------#  真ん中のフレーム # ---------------------- #
        fm_mid = tk.Frame(master)
        fm_mid.pack(fill=tk.X, side=tk.TOP)
 
        # グリッド表示・非表示
        self.Chkbox = tk.BooleanVar()
        self.Chkbox.set(True)
        chkbtn = tk.Checkbutton(fm_mid, variable=self.Chkbox, text='グリッドを表示する', command=self.draw)
        chkbtn.pack(side=tk.RIGHT)
 
        # ----------------------# 下側のフレーム #----------------------#
        fm_ctrl = tk.Frame(master)
        fm_ctrl.pack(fill=tk.X, side=tk.TOP)
 
        # 拡大縮小・回転
        self.Var_yow = tk.Scale(fm_ctrl, label='yaw', orient="v", from_=-180, to=180, command=self.angle_callback)
        self.Var_pitch = tk.Scale(fm_ctrl, label='pitch', orient="v", from_=-180, to=180, command=self.angle_callback)
        self.Var_roll = tk.Scale(fm_ctrl, label='roll', orient="v", from_=-180, to=180, command=self.angle_callback)
        self.Var_scale = tk.Scale(fm_ctrl, label='senser_size', orient="v", from_=0.1, to=2.0, resolution=0.1, command=self.scrl_callback)
        self.Var_point = tk.Scale(fm_ctrl, label='view_point', orient="v", from_=-1.8, to=1.0, resolution=0.05, command=self.scrl_callback)
        self.Var_focus = tk.Scale(fm_ctrl, label='senser_point', orient="v", from_=0.01, to=0.81, resolution=0.05, command=self.scrl_callback)
        self.Var_yow.pack(fill=tk.Y, side=tk.LEFT)
        self.Var_pitch.pack(fill=tk.Y, side=tk.LEFT)
        self.Var_roll.pack(fill=tk.Y, side=tk.LEFT)
        self.Var_scale.pack(fill=tk.Y, side=tk.LEFT)
        self.Var_point.pack(fill=tk.Y, side=tk.LEFT)
        self.Var_focus.pack(fill=tk.Y, side=tk.LEFT)
        self.Var_scale.set(0.7)
        self.Var_point.set(-1.0)
        self.Var_focus.set(0.2)
         
        # ----------------------# メニュー #----------------------#
        men = tk.Menu(master)
        menu_file = tk.Menu(master, tearoff=0)
        men.add_cascade(label='ファイル', menu=menu_file)
        menu_file.add_command(label='ファイルを開く', command=self.openfile)
        menu_file.add_command(label='CubeMapを開く', command=self.opencube)
        menu_file.add_separator()
        menu_file.add_command(label='名前を付けて保存',command=self.savefile)
        master.config(menu=men)
 
 
    # メインキャンパスでのマウスダウン
    def click(self, event):
        self.posx = event.x
        self.posy = event.y
 
    # メインキャンパスでのマウスムーブ
    def drag(self, event):
        dx = event.x - self.posx
        dy = event.y - self.posy
        self.Var_yow.set(self.Var_yow.get() + dx)
        self.Var_pitch.set(self.Var_pitch.get() + dy)
        if self.Var_yow.get() == 180:
            self.Var_yow.set(-180)
        elif self.Var_yow.get() == -180:
            self.Var_yow.set(180)
        self.click(event)
 
    # メインキャンパスでのマウスホイール(拡大縮小)
    def ctrl_wheel(self, event):
        if event.delta > 0:     # 向きのみ検出
            self.Var_scale.set(self.Var_scale.get() + 0.1)
        else:
            self.Var_scale.set(self.Var_scale.get() - 0.1)
 
 
    # 表示画像の生成
    def remap(self):
        roll = self.Var_roll.get()
        pitch = self.Var_pitch.get()
        yaw = self.Var_yow.get()
        xd, yd, zd = io360.rotate_3dmap(self.x, self.y, self.z, roll, pitch, yaw)
 
        return io360.remap_from_equirectangular(self.img, xd, yd, zd) # , borderMode=cv2.BORDER_CONSTANT)
 
 
    # 描画   
    def draw(self):
 
        w = WIDTH//SCALE
        h = HEIGHT//SCALE       
         
        # 表示はリサイズをかける
        if SCALE != 1:
            image_rgb = cv2.resize(self.remap(), (w, h))
        else:
            image_rgb = self.remap()
             
        self.image_Tk = ImageTk.PhotoImage(Image.fromarray(image_rgb), master=self.canvas)
        self.canvas.create_image(0, 0, image=self.image_Tk, anchor='nw')
 
        # グリッドの表示
        if self.Chkbox.get():
            self.canvas.create_line(w / 2, 0, w / 2, h, fill='red')
            self.canvas.create_line(0, h / 2, w, h / 2, fill='red')       
         
 
    # 角度が変化したときに呼ばれるコールバック関数
    def angle_callback(self, val):
        self.draw()
 
 
    # 角度以外が変化したときに呼ばれるコールバック関数
    def scrl_callback(self, val):
        img_w = WIDTH
        img_h = HEIGHT
        senser_size = self.Var_scale.get()
        view_point = self.Var_point.get()
        senser_point = self.Var_focus.get()
        self.x, self.y, self.z = io360.create_3dmap_from_viewpoint(img_w, img_h, senser_size, view_point, senser_point)
 
        self.draw()
     
 
    # ファイルを開く
    def openfile(self):
        fileName = tk.filedialog.askopenfilename(
            title = "全天球画像を開く",
            filetypes = [("Image file", ".bmp .png .jpg .tif"), ("Bitmap", ".bmp"), ("PNG", ".png"), ("JPEG", ".jpg"), ("Tiff", ".tif") ],)
        if fileName != None:
            self.img = cv2.cvtColor(cv2.imread(fileName), cv2.COLOR_BGR2RGB)
            self.draw()
 
 
    # CubeMapを開く
    def opencube(self):
        fileName = tk.filedialog.askopenfilename(
            title = "CubeMapを開く",
            filetypes = [("Image file", ".bmp .png .jpg .tif"), ("Bitmap", ".bmp"), ("PNG", ".png"), ("JPEG", ".jpg"), ("Tiff", ".tif") ],)
        if fileName != None:
            tmp = cv2.imread(fileName)
            width = tmp.shape[1]
            tmp = io360.cube_to_equirectangular(tmp, width) #面倒なのでいきなり変換
            self.img = cv2.cvtColor(tmp, cv2.COLOR_BGR2RGB)
            self.draw()
 
             
    # 保存メニュー
    def savefile(self):
        fileName = tk.filedialog.asksaveasfilename(title = "画像の保存")
        if fileName != None:
            tmp = self.remap()
            cv2.imwrite(fileName, cv2.cvtColor(tmp, cv2.COLOR_BGR2RGB))
 
             
# main
if __name__ == '__main__':
    laps_viewerGUI(master=tk.Tk()).mainloop()

 こんな文字だけのリソースなしコードで、

 こんなのできちゃうんですね。

 ここのバーの値をそのまま前回の記事の関数に渡して描画しているだけです。どんな数字入れたらどんな画像になるかGUIの方がイメージしやすいです。

 視点のイメージ。

 いつまでたってもどっちがどの角度か覚えられない。

まとめ

 やっとこさビューアーと呼べる代物になったかな。ただエラー処理の類は何もしていないのであしからず。

コメント

タイトルとURLをコピーしました