編集履歴一覧に戻る
snflwr59のアイコン画像

snflwr59 が 2026年01月29日02時58分34秒 に編集

初版

タイトルの変更

+

OrbitCapture - 簡単3Dスキャンシステム

タグの変更

+

SPRESENSE

+

HDRカメラ

メイン画像の変更

メイン画像が設定されました

記事種類の変更

+

製作品

ライセンスの変更

+

(MIT) The MIT License

本文の変更

+

# OrbitCapture - 簡単3Dスキャンシステム(Spresenseコンテスト2025) ![OrbitCaptureLogo](https://camo.elchika.com/8245ed4f41348d91dc53cfd2e77579b763be9e1e/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39633461393737612d393730652d343965312d386561312d6337666331643563393938642f64643330653236322d666636652d346539362d613364332d656566393532376632653266/) ## 1. 概要/Summary 本作品は、**誰でも簡単に自分の作品やアイテムを3Dデータ化できる3Dスキャンシステム**を目指した。 "アナログなものづくりとデジタルなものづくりを、もっと自由に行き来したい"という考えのもと、360度撮影から3Dデータ化までを全自動化し、誰でも簡単に「リアルをデジタルに変換する体験」を提供することを目的としている。 ![外観](https://camo.elchika.com/ff80bb6c0f943a72003e092291c50afda24c9e3b/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39633461393737612d393730652d343965312d386561312d6337666331643563393938642f36383464646563322d363832632d346664662d616336662d383532383730383165323165/) --- ## 2. 設計/Design ### 2.1 ハードウェア構成 **主要部品**: - Spresense Main Board - Spresense HDRカメラボード - ステッピングモータ: 28BYJ-48 - モーター用フランジ - モータドライバ: DRV8835 - PC - モバイルバッテリー(モーター給電用) - USBケーブル, MDF板, フラワースタンド, 結束バンド, 等100円ショップで調達 **ハードウェア・ブロック図**: ```mermaid graph LR PC["PC<br/>(orbit_scan.py)"] USB["USB-Serial<br/>接続"] Spresense["Spresense<br/>Main Board"] Camera["HDRカメラ<br/>ボード"] Stepper["ステップ<br/>モータ<br/>28BYJ-48"] Driver["モータ<br/>ドライバ<br/>DRV8835"] Battery["モバイル<br/>バッテリー"] PC -->|シリアル通信| USB USB -->|115200 baud<br/>orbit_capture_main| Spresense Spresense -->|CSI接続<br/>V4L2 /dev/video| Camera Camera -->|JPEG撮影| Spresense Spresense -->|GPIO制御<br/>FIFO /dev/stepper0| Driver Driver -->|4相励磁<br/>パルス| Stepper Battery -->|5V給電| Driver Stepper -->|機械回転| Spresense style PC fill:#e1f5ff style Spresense fill:#fff3e0 style Camera fill:#f3e5f5 style Stepper fill:#e8f5e9 ``` **通信プロトコル**: - **Spresense ↔ PC**:シリアルバイナリプロトコル(115200 baud) - コマンド送受信:テキスト(SNAP, MOVE, EXIT) - 画像転送:バイナリ(マジックシーケンス + サイズ + JPEG) - **内部制御**: - カメラ制御:V4L2 ioctl(`/dev/video`) - モーター制御:FIFO IPC(`/dev/stepper0`) ![Detail](https://camo.elchika.com/fd593ef033e9cf0afa999d8fbbedfb1472d17b38/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39633461393737612d393730652d343965312d386561312d6337666331643563393938642f32373637666634662d313430652d343538632d393935642d343330613164633062343164/) ![カメラレンズ](https://camo.elchika.com/9f41972500d5e18cdd70f0d5624d4493630e06aa/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39633461393737612d393730652d343965312d386561312d6337666331643563393938642f66656235623535392d363937332d343536612d383164342d656230653834316135653634/) HDRカメラに付属のカメラレンズは被写界深度が浅く、背景がボケる傾向がある。 今回の用途では画角も不足気味であったため、付属レンズでの試行以外に別途M8、M12レンズでも試行した。 試行に当たり、カメラレンズホルダを3Dプリンタで作成し、M8P0.5、M12P0.5のレンズを装着した。 3Dプリンタで作成したレンズホルダはある程度IR光を透過してしまったため、そのままでは画像が赤みがかってしまう。 対策として、レンズホルダを黒ビニールテープで巻くことで改善したことが確認できた。 ### Spresense を使うメリット - **マルチコアCPU**:モータとカメラを同時制御 - **HDRカメラ対応**:照明の影響を抑え、きれいに撮影できる --- ### 2.2 ソフトウェア構成 - **開発環境**: Spresense SDK (NuttX RTOS) - **使用言語**: C (Spresense側) / Python 3.x (PC側) **撮影シーケンス** ```mermaid sequenceDiagram participant User as ユーザー participant PC as orbit_scan.py (Host) participant App as orbit_capture (Spresense) participant Cam as Camera API (V4L2) participant Stepper as stepper_api participant SD as stepperd (Daemon) participant Motor as 28BYJ-48 Note over User,Motor: 初期化フェーズ User->>PC: python orbit_scan.py 24 PC->>PC: シリアルポート接続<br/>/dev/ttyUSB0, 115200 PC->>App: \n (プロンプト要求) App->>App: camera_init()<br/>V4L2デバイス初期化 App-->>PC: OC> (プロンプト表示) Note over SD,Motor: stepperdはバックグラウンドで常駐<br/>FIFO /dev/stepper0 で待機中 Note over User,Motor: 撮影ループ開始 (24回繰り返し) loop 各撮影位置で Note over PC,Motor: === 画像撮影フェーズ === PC->>App: SNAP\n App->>Cam: camera_capture()<br/>VIDIOC_QBUF App->>Cam: VIDIOC_TAKEPICT_START Cam->>Cam: HDRカメラ撮影<br/>1600x1200 JPEG Cam-->>App: VIDIOC_DQBUF<br/>JPEG画像データ App->>App: camera_send_image()<br/>termios設定変更 Note over App,PC: バイナリ転送プロトコル App->>PC: 0xBEEFCAFE (MAGIC 4 bytes) App->>PC: image_size (uint32_t, little-endian) App->>PC: JPEG binary data (50~150 KB) PC->>PC: MAGICシーケンス検出 PC->>PC: サイズ読み取り (4 bytes) PC->>PC: 画像データ受信<br/>プログレス表示 (25%毎) PC->>PC: ファイル保存<br/>shot_XXX_YYYdeg.jpg App-->>PC: OC> (プロンプト復帰) Note over PC,Motor: === モーター回転フェーズ === PC->>PC: 回転角度計算<br/>steps = 2048 / shots PC->>App: MOVE 85 10000 1\n<br/>(steps, delay_us, direction) App->>Stepper: stepper_move(85, 10000, 1) Stepper->>Stepper: FIFO open: /dev/stepper0 Stepper->>SD: write(struct stepper_cmd)<br/>{steps:85, delay:10000, dir:1} SD->>SD: read FIFO (blocking) SD->>SD: step_motor(&cmd) loop 85ステップ SD->>Motor: GPIO制御<br/>4相励磁シーケンス SD->>SD: usleep(10000) Motor->>Motor: 0.176度回転 end SD-->>Stepper: (書き込み完了) Stepper-->>App: (return 0) App-->>PC: OC> PC->>PC: 振動収束待機<br/>sleep(0.95秒) end Note over User,Motor: 終了処理 PC->>App: EXIT\n App->>App: free(img_buf) App->>Cam: camera_uninit() App-->>PC: OrbitCapture finished. PC->>PC: シリアル接続クローズ PC-->>User: 撮影完了<br/>画像ファイル保存完了 ``` **詳細プロセス**: 1. PC側ではPythonスクリプトが撮影枚数を指定して起動される 2. Spresense側では`stepperd`デーモンがバックグラウンドで待機している 3. 撮影ループでは以下が繰り返される: - 画像撮影(HDRカメラでJPEG画像取得) - PCへ画像を転送 - モータ回転(次の角度へ移動) 4. PC側ではタイムスタンプ付きフォルダに画像が自動保存される 5. 撮影後、ホストPCまたはGoogle ColabでCOLMAPによる3D再構成や3D Gaussian Splatingなどにより3次元モデルが生成される --- ## 3. 動作結果 ### 3.1 実行結果 **撮影例**:1周64枚(5.625°刻み)で約15分である。 ![example1](https://camo.elchika.com/21ef9955a41a5f7b183ad4525d1912dd12404caf/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39633461393737612d393730652d343965312d386561312d6337666331643563393938642f32393536656263312d346239312d343062622d623931662d343766616138383232383035/) ![example2](https://camo.elchika.com/89b91eeb501cc8a482f6927ec1b5c57c61643d01/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39633461393737612d393730652d343965312d386561312d6337666331643563393938642f34376632656439632d393939612d343861372d393736622d623035353233366566343338/) ![example3](https://camo.elchika.com/5923063bfca108a3d4a012b18a73bd3c2fef023f/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39633461393737612d393730652d343965312d386561312d6337666331643563393938642f33313534613964362d343038352d343966382d383762332d666331393265313935663264/) **PC側ログ出力例**: ``` 23:45:12 [INFO] Connecting to /dev/ttyUSB0 at 115200 baud... 23:45:12 [INFO] Connection established and prompt found. 23:45:12 [INFO] Starting scan: 24 shots over 360 degrees. 23:45:12 [INFO] --- Shot 1/24 at 0.0° --- 23:45:12 [INFO] Triggering capture... 23:45:14 [INFO] Receiving image: 127456 bytes 23:45:15 [INFO] Progress: 25% 23:45:16 [INFO] Progress: 50% 23:45:17 [INFO] Progress: 75% 23:45:18 [INFO] Capture complete. Saved to 0121_2345/shot_000_0deg.jpg 23:45:18 [INFO] Moving 85 steps CW (delay: 10000us) ... ``` **保存された画像群**: ```bash 0121_2345/ ├── shot_000_0deg.jpg ├── shot_001_15deg.jpg ├── shot_002_30deg.jpg ... └── shot_023_345deg.jpg ``` **3次元モデル作成シーケンス** COLMAP、opensplatを使用した例。 ``` $DATA_PATH = "./data" $env:QT_QPA_PLATFORM_PLUGIN_PATH="C:\colmap-x64-windows-nocuda\plugins\platforms" [特徴点の抽出] colmap feature_extractor ` --image_path "$DATA_PATH/original_images" ` --database_path "$DATA_PATH/database.db" ` --ImageReader.single_camera 1 ` --ImageReader.camera_model OPENCV [特徴点のマッチング] colmap exhaustive_matcher ` --database_path "$DATA_PATH/database.db" ` --FeatureMatching.use_gpu 0 [バンドル調整] colmap mapper ` --image_path "$DATA_PATH/original_images/" ` --database_path "$DATA_PATH/database.db" ` --output_path "$DATA_PATH/sfm" [歪み補正] colmap image_undistorter ` --image_path "$DATA_PATH/original_images" ` --input_path "$DATA_PATH/sfm/0" ` --output_path "$DATA_PATH/undistort_tmp" ` --output_type COLMAP mkdir ./data/sparse/0 move ./data/undistort_tmp/sparse/*.bin ./data/sparse/0 xcopy ./data/undistort_tmp/images ./data/images /E /Y rmdir /S /Q ./data/undistort_tmp [3DGSの生成] opensplat.exe $DATA_PATH -n 2000 ``` **COLMAP結果** ![COLMAP_Frustum](https://camo.elchika.com/11eb7ecaae002fc1e459fb5052d81d22fdebbb93/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39633461393737612d393730652d343965312d386561312d6337666331643563393938642f66666563353065662d383230332d343031302d623565622d303766353564383830376634/) **3D Gaussian Splatting結果** ![LEGO](https://camo.elchika.com/d15c0d10ff1b9629669c8101d6ef7c5dc23f321b/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39633461393737612d393730652d343965312d386561312d6337666331643563393938642f66626666626264352d653563312d343562332d626338642d356137323066373364396565/) https://gsplat.org/viewer/60kqb ![frog](https://camo.elchika.com/5a2204e477f7a1453d45a898a4fa794572bb2fbd/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39633461393737612d393730652d343965312d386561312d6337666331643563393938642f35643039346162352d363832362d346437652d386136322d646131323264316134356331/) https://gsplat.org/viewer/uif7u ## 4. ソースコード 本システムはハードウェア制御層(Spresense側)とホスト制御層(PC側)に分けられている。 ### 4.1 ハードウェア制御層(Spresense SDK) #### camera_api.h / camera_api.c - HDRカメラ制御 **概要**:Spresense HDRカメラをV4L2(Video4Linux2)APIで制御する。 **主要な初期化処理**: ```c int camera_init(void) { // V4L2デバイス初期化 video_initialize(VIDEO_DEV_PATH); g_cam_fd = open(VIDEO_DEV_PATH, 0); // バッファ要求:ユーザーメモリ mode struct v4l2_requestbuffers req; req.type = V4L2_BUF_TYPE_STILL_CAPTURE; req.memory = V4L2_MEMORY_USERPTR; // アプリ側で確保 req.count = STILL_BUFNUM; req.mode = V4L2_BUF_MODE_FIFO; ioctl(g_cam_fd, VIDIOC_REQBUFS, &req); // フォーマット設定:JPEG + QuadVGA (1600x1200) struct v4l2_format fmt; fmt.type = V4L2_BUF_TYPE_STILL_CAPTURE; fmt.fmt.pix.width = CAPTURE_WIDTH; // 1600 fmt.fmt.pix.height = CAPTURE_HEIGHT; // 1200 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG; ioctl(g_cam_fd, VIDIOC_S_FMT, &fmt); // JPEG品質設定(95 = 高品質) struct v4l2_ext_controls ctrls; struct v4l2_ext_control ctrl; ctrls.ctrl_class = V4L2_CTRL_CLASS_JPEG; ctrl.id = V4L2_CID_JPEG_COMPRESSION_QUALITY; ctrl.value = JPEG_QUALITY; // 95 ioctl(g_cam_fd, VIDIOC_S_EXT_CTRLS, &ctrls); return 0; } ``` **キャプチャー処理**: ```c int camera_capture(uint8_t *buf, size_t buf_size, size_t *captured_size) { struct v4l2_buffer vbuf; // バッファキューに追加 vbuf.type = V4L2_BUF_TYPE_STILL_CAPTURE; vbuf.memory = V4L2_MEMORY_USERPTR; vbuf.m.userptr = (unsigned long)buf; vbuf.length = buf_size; ioctl(g_cam_fd, VIDIOC_QBUF, &vbuf); // 撮影開始 ioctl(g_cam_fd, VIDIOC_TAKEPICT_START, 0); // 撮影完了まで待機(ブロッキング) ioctl(g_cam_fd, VIDIOC_DQBUF, &vbuf); // 撮影完了 ioctl(g_cam_fd, VIDIOC_TAKEPICT_STOP, 0); *captured_size = vbuf.bytesused; return 0; } ``` **特徴**: - V4L2の標準API使用で再現性が高い - JPEG品質95で高品質撮影である - メモリ効率化:1MB バッファ(キャプチャ後は即座に送信される) --- #### stepper_api.h / stepper_api.c - モーター制御API **概要**:FIFO(名前付きパイプ)経由でプロセス間通信(IPC)し、`stepperd`デーモンにコマンドを送信する。 **インターフェース定義**: ```c struct stepper_cmd { int steps; // ステップ数 int delay_us; // ステップ間隔(マイクロ秒) int direction; // 方向: 1(時計回り), -1(反時計回り) }; int stepper_init(void); // 初期化:FIFOへの接続テスト int stepper_move(int steps, int delay_us, int direction); ``` **実装**: ```c int stepper_move(int steps, int delay_us, int direction) { struct stepper_cmd cmd; cmd.steps = steps; cmd.delay_us = delay_us; cmd.direction = direction; // FIFOをオープン(非ブロッキング) int fd = open(FIFO_PATH, O_WRONLY); if (fd < 0) { perror("FIFO open failed. Is stepperd running?"); return -errno; } // コマンド構造体をバイナリで送信 int wlen = write(fd, &cmd, sizeof(cmd)); close(fd); if (wlen != sizeof(cmd)) { return -1; // 書き込み失敗 } return 0; } ``` **利点**: - プロセス間通信で分離性が高い - コマンド送信は非ブロッキングである - `stepperd`はバックグラウンドで独立稼働する --- #### orbit_capture_main.c - メインアプリケーション **構造**:対話型コマンド・インタープリタで、PC側からのコマンド(SNAP, MOVE, EXIT)を受け取り処理する。 ```c int orbit_capture_main(int argc, char *argv[]) { char line[MAX_LINE]; uint8_t *img_buf; size_t captured_size; printf("OrbitCapture starting...\n"); printf("Ready for commands (MOVE, SNAP, EXIT)\n"); // カメラ初期化 camera_init(); img_buf = (uint8_t *)memalign(32, FRAMEBUFFER_SIZE); while (1) { printf("OC> "); fflush(stdout); if (fgets(line, MAX_LINE, stdin) == NULL) break; char *cmd = strtok(line, " \n\r"); if (!cmd) continue; if (strcmp(cmd, "MOVE") == 0) { // MOVE <steps> <delay_us> <direction> int steps = atoi(strtok(NULL, " ")); int delay = atoi(strtok(NULL, " ")); int dir = atoi(strtok(NULL, " ")); stepper_move(steps, delay, dir); } else if (strcmp(cmd, "SNAP") == 0) { // 画像撮影 int ret = camera_capture(img_buf, FRAMEBUFFER_SIZE, &captured_size); if (ret == 0) { fprintf(stderr, "Sending image... (%zu bytes)\n", captured_size); fflush(stdout); // テキストを送信してから... camera_send_image(img_buf, captured_size); // ...バイナリ送信 } } else if (strcmp(cmd, "EXIT") == 0) { break; } } free(img_buf); camera_uninit(); printf("OrbitCapture finished.\n"); return 0; } ``` **通信フロー**: 1. PC(orbit_scan.py)が `"SNAP\n"` を送信する 2. Spresenseがカメラ画像をキャプチャする 3. バイナリプロトコルで画像を転送される 4. PC側でプロンプト受信まで待機される --- ### 4.2 バイナリ画像転送プロトコル PC ←→ Spresense の画像転送は、以下のバイナリプロトコルで実現される。 **フォーマット**: ``` [Magic (4 bytes)] [Size (4 bytes)] [JPEG Data (variable)] 0xBEEFCAFE uint32_t LE image_size bytes ``` **Spresense側実装**(camera_api.c): ```c void camera_send_image(uint8_t *buf, size_t size) { struct termios tio, saved_tio; int fd = 1; // stdout // シリアルオプション変更:LF->CRLF変換を無効化 tcgetattr(fd, &tio); saved_tio = tio; tio.c_oflag &= ~ONLCR; // 重要:バイナリ転送時に改行展開されない tcsetattr(fd, TCSANOW, &tio); // マジックシーケンス送信 const uint8_t marker[4] = {0xBE, 0xEF, 0xCA, 0xFE}; write(fd, marker, 4); // サイズ送信(リトルエンディアン) uint32_t sz = (uint32_t)size; write(fd, &sz, 4); // 画像データ送信 write(fd, buf, size); // 設定を復元 tcdrain(fd); tcsetattr(fd, TCSANOW, &saved_tio); } ``` --- ### 4.3 ホスト制御層(Python) #### orbit_scan.py - PC側シーケンス制御 **シリアル通信の初期化**: ```python class OrbitController: def __init__(self, port, baud): self.ser = serial.Serial(port, baud, timeout=40) self.ser.reset_input_buffer() time.sleep(0.2) # プロンプト検出まで待機 self.ser.write(b"\n") self.ser.read_until(b"OC>") logger.info("Connection established and prompt found.") ``` **画像受信の実装**: ```python def capture(self, filename): logger.info("Triggering capture...") self.ser.write(b"SNAP\n") # ステップ1: マジックシーケンス検出 MAGIC = b'\xBE\xEF\xCA\xFE' buffer = b'' while True: chunk = self.ser.read(self.ser.in_waiting or 1) buffer += chunk magic_pos = buffer.find(MAGIC) if magic_pos != -1: # マジック後の残りデータを確保 extra = buffer[magic_pos + len(MAGIC):] break # ステップ2: イメージサイズ読み取り(4 bytes, little-endian) if len(extra) < 4: extra += self.ser.read(4 - len(extra)) img_size = int.from_bytes(extra[:4], byteorder='little') logger.info(f"Receiving image: {img_size} bytes") # ステップ3: 画像データ受信(プログレス表示付き) img_data = extra[4:] remaining = img_size - len(img_data) last_report = 0 while remaining > 0: chunk = self.ser.read(min(remaining, 4096)) if not chunk: logger.error("Connection lost during transfer") break img_data += chunk remaining -= len(chunk) # 25% 刻みでプログレス表示 progress = int((1 - remaining / img_size) * 100) if progress >= last_report + 25: logger.info(f"Progress: {progress}%") last_report = progress # ステップ4: ファイル保存 with open(filename, 'wb') as f: f.write(img_data) # プロンプト復帰まで待機 self.ser.read_until(b"OC>") logger.info(f"Capture complete. Saved to {filename}") return True ``` **撮影ループ**: ```python def main(): parser = argparse.ArgumentParser() parser.add_argument('shots', type=int, help='撮影枚数') parser.add_argument('--output', default=datetime.now().strftime('%m%d_%H%M')) args = parser.parse_args() ctrl = OrbitController('/dev/ttyUSB0', 115200) os.makedirs(args.output, exist_ok=True) steps_per_segment = STEPS_PER_REV // args.shots # 2048 / shots current_step_pos = 0 for i in range(args.shots): angle = (360 / STEPS_PER_REV) * current_step_pos filename = os.path.join(args.output, f"shot_{i:03d}_{int(angle)}deg.jpg") logger.info(f"--- Shot {i+1}/{args.shots} at {angle:.1f}° ---") ctrl.capture(filename) # 次の角度へ回転 if i < args.shots - 1: ctrl.move(steps_per_segment, STEP_DELAY_US, 1) current_step_pos += steps_per_segment # モーター振動収束を待つ travel_time = (steps_per_segment * STEP_DELAY_US) / 1000000.0 time.sleep(travel_time + 0.1) logger.info("Scan completed successfully.") ctrl.close() ``` **主要な工夫**: - **タイムアウト処理**:シリアルタイムアウト40秒に設定される - **マジックシーケンス**:確実な同期(0xBEEFCAFEで検出される) - **プログレス表示**:25% 刻みで進捗が確認される - **動的スリープ**:モーター回転時間を計算して待機される --- ### 4.4 実装のポイント | コンポーネント | 技術 | 目的 | |----------|------|------| | camera_api | V4L2 API | 標準デバイスドライバー経由でHDRカメラ制御 | | stepper_api | FIFO IPC | モーターコマンドをバックグラウンド処理 | | バイナリプロトコル | マジックシーケンス + サイズ | 確実な画像同期・転送 | | シリアル通信 | termios | バイナリモードで改行展開を防止 | | エラーハンドリング | errno + タイムアウト | 接続失敗・転送失敗を検出 | --- ## 5. まとめ ### 達成したこと 本作品では、**Spresense の特長を活かして**、操作が簡単な3D撮影システム「OrbitCapture」が開発された。Spresense Main BoardとHDRカメラボードを用いたハードウェア、および対話型コマンドインタープリタとPythonスクリプトを組み合わせたソフトウェアのプロトタイピングに成功した。360度自動撮影からバイナリ画像転送プロトコル、さらには3D Gaussian Splattingによる3次元モデル生成までの一連のワークフローが実装できた。 ### 今後の展開 今後、以下の点での改善を検討している: 1. **撮影条件の改善**:現在のハードウェア構成では被写体とカメラの距離がほぼ一定になってしまうため、3次元復元にはあまり適切とは言い難い。カメラの配置やターンテーブルの構成を改善し、より多角的な視点からの撮影を実現したい。 2. **ソフトウェアワークフローの改善**:ワンクリックでの完全自動化には至っていない。GUI付きアプリケーションにより、撮影から3Dモデル生成までの全自動化を進めたい。 3. **3DGSの品質向上**:現在作成した3D Gaussian Splattingは粗い品質である。また、高い解像感をローカルマシンで実現するにはマシンスペックが要求される。クラウド環境の利用により、ローカルマシンへの要求を下げながら高品質な3Dモデルを生成する仕組みを構築したい。 ## 6. 参考資料 - [Sony Spresense 公式ドキュメント](https://developer.sony.com/develop/spresense/) - [NuttX GPIO API](https://nuttx.apache.org/) - [COLMAP - Structure-from-Motion and Multi-View Stereo](https://colmap.github.io/) - [28BYJ-48 ステッピングモータ仕様書](http://www.4tronix.co.uk/arduino/Stepper-Motors.php)