htmxとv4l2-ctlでカメラ操作画面を作る

この記事はUMITRON Advent Calendar 2023 8日目の記事です。


こんにちは、UMITRONの八木浩樹です。普段はスマート給餌機「UMITRON CELL」のソフトウェアを開発しています。主にRaspberry Pi上で動くGoのプログラムを書いています。
最近USBカメラ映像の明るさを操作する機会があったのですが、そういうときには映像を確認しながらパラメータをいじりたいものです。
というわけでカメラパラメータ操作用のWebページを作ってみます。

htmxについて

今回はhtmxというフレームワーク(?)を使います。なにやらHTMLを良い感じに拡張したものらしいですが、以下解説がありました。

導入部分だけのまとめですが、以下のようなモチベーションのようです。

  • 最近のWeb開発はJavaScriptを中心に行なわれている

  • そのような状況でHTMLはただ歴史的な経緯で使われているだけの奇妙なグラフィカルインターフェースの記述言語扱いをされている

  • 本来HTMLは「HyperMedia System」の一部であり、もっとパワフルに使えるのだ

単純なHTML+HTTP Serverの仕組みを見直して使いやすくしたってことでしょうか? なんとなくHyperMedia周りの議論はserial experiments lainっぽさがあって格好良いですね。

ストリームサーバーの用意

まずはUSBカメラの映像をブラウザで確認できるように、ストリームサーバーを立てたいと思います。設定なしで簡単に動くということで、Motionという監視カメラ用のソフトウェアを使ってみます。

yagi@raspberrypi:~ $ sudo apt install motion
yagi@raspberrypi:~ $ sudo systemctl start motion

これでlocalhost:8081で映像が見られるようになりました。

時計のようす

v4l2-ctrlについて

v4l2-ctlはLinuxでビデオデバイスを操作するためのコマンドで、接続デバイスの確認やパラメータの取得、設定などができます。
試しに露光時間(exposure)を変更してみます。その前に設定できるパラメータを確認してみましょう。

yagi@raspberrypi:~ $ v4l2-ctl --list-ctrls-menus --device /dev/video0

User Controls

                     brightness 0x00980900 (int)    : min=-64 max=64 step=1 default=0 value=0
                       contrast 0x00980901 (int)    : min=0 max=64 step=1 default=32 value=32
                     saturation 0x00980902 (int)    : min=0 max=128 step=1 default=64 value=64
                            hue 0x00980903 (int)    : min=-40 max=40 step=1 default=0 value=0
        white_balance_automatic 0x0098090c (bool)   : default=1 value=1
                          gamma 0x00980910 (int)    : min=72 max=500 step=1 default=100 value=100
                           gain 0x00980913 (int)    : min=0 max=100 step=1 default=0 value=0
           power_line_frequency 0x00980918 (menu)   : min=0 max=2 default=1 value=1
				0: Disabled
				1: 50 Hz
				2: 60 Hz
      white_balance_temperature 0x0098091a (int)    : min=2800 max=6500 step=1 default=4600 value=4600 flags=inactive
                      sharpness 0x0098091b (int)    : min=0 max=6 step=1 default=3 value=3
         backlight_compensation 0x0098091c (int)    : min=0 max=2 step=1 default=1 value=1

Camera Controls

                  auto_exposure 0x009a0901 (menu)   : min=0 max=3 default=3 value=1
				1: Manual Mode
				3: Aperture Priority Mode
         exposure_time_absolute 0x009a0902 (int)    : min=1 max=5000 step=1 default=156 value=156
     exposure_dynamic_framerate 0x009a0903 (bool)   : default=0 value=0

上のように、設定できる項目や値が確認できます。露光時間をデフォルトの156から2000に変更してみます。

yagi@raspberrypi:~ $ v4l2-ctl --set-ctrl exposure_time_absolute=2000
exposure_time_absolute=156
exposure_time_absolute=2000

明るくなった!

HTTPサーバーの用意

HTTPサーバーはGoで作っていきます。パラメータ操作のリクエストを受けとって、v4l2-ctlを呼び出します。ここで少し変わっているのが、レスポンスで部分的なHTMLを返していることです。後ほどこれをhtmx側でうまいこと表示するようにします。

http.HandleFunc("/parameters/exposure", func(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if r.Method == "PUT" {
		exposure, err := strconv.Atoi(r.FormValue("exposure"))
		if err != nil {
			errHtml := fmt.Sprintf("<span class=\"error\">値が不正です: %v</span>", err)
			http.Error(w, errHtml, http.StatusBadRequest)
			return
		}

		err = v4l2.setExposure(exposure)
		if err != nil {
			errHtml := fmt.Sprintf("<span class=\"error\">値の変更に失敗しました: %v</span>", err)
			http.Error(w, errHtml, http.StatusInternalServerError)
			return
		}
		fmt.Fprintf(w, "<span class=\"success\">値の変更に成功しました: %d</span>", exposure)
		return
	}

	http.Error(w, "<span class=\"error\">Method not allowed</span>", http.StatusMethodNotAllowed)
})

v4l2-ctlの操作はこんな感じです。

func (c v4l2Controller) setCtrl(ctrl string, value int) error {
	opts := []string{
		"--set-ctrl", fmt.Sprintf("%s=%d", ctrl, value),
		"--device", c.videoDevice,
	}
	cmd := exec.Command("v4l2-ctl", opts...)
	return cmd.Run()
}

htmxを使ったカメラ操作画面

htmxを使うためには以下を入れるだけです。

<script src="https://unpkg.com/htmx.org@1.9.9"></script>

htmxの機能を使ってフォームを作ります。/parameters/exposure にPUTメソッドを使ってexposureの値を送信するようなフォームが以下です。

<form>
  <input name="exposure" type="number" value="156"/>
  <button hx-put="/parameters/exposure"
	  hx-target="#message"
	  hx-target-error="#error-message"
	  hx-swap="innerHTML">
    Set exposure
  </button>
</form>
<div id="message"></div>
<div id="error-message"></div>

ここで "hx-" からはじまっている属性がhtmxの用意しているものです。

  • "hx-put" はボタンがクリックされたときにPUTリクエストを送るように指定できます。

  • "hx-target"はレスポンスが返ってきたときに、そのレスポンスで置き換える要素を指定できます。

  • "hx-target-error"はエラーレスポンスが返ってきたときに、そのレスポンスで置き換える要素を指定できます。これは拡張機能のresponse-targetsを使うことで利用できます。

  • "hx-swap"はレスポンスで置き換えるときのルールを指定できます。ここでは"#message"の内側の要素を置き換えます。

以下ができあがりです。

明るい!

htmxをつかうとフォームやボタンに単純な属性を追加するだけでインタラクティブなページを作成できて、とても便利ですね! レスポンスのHTMLを上手く使うという基本の仕組みは、Goのhtml/templateとの相性が良さそうです。
ここでは単純な機能の紹介だけになりましたが、上で紹介した解説書にはウェブの歴史の中での位置付けなど、読み物としても面白い内容がたくさん書かれているので興味がある方はぜひ読んでみてください。
htmxと合わて紹介されていたネイティブアプリ開発のためのHyperviewについても今後触ってみたいと思います。
全体のプログラムはこちら: https://github.com/h8gi/htmx-camera-control


ウミトロンでは一緒に働く仲間を募集しております。持続可能な水産養殖を地球に実装するというミッションの元で、私たちと一緒に水産養殖xテクノロジーに取り組みませんか?

https://umitron.com/ja/career.html

https://open.talentio.com/1/c/umitron/requisitions/746

この記事が気に入ったらサポートをしてみませんか?