UJP - 技術情報1

Life is fun and easy!

不正IP報告数

Okan Sensor
 
メイン
ログイン
ブログ カテゴリ一覧

Surface Go 2 on Ubuntu Serverでカメラを使ったモーションセンサー

Surface Go 2 on Ubuntu Serverでカメラを使った擬似モーションセンサー


概要

更新履歴

  • 2026/05/06 初版

目次

はじめに

  • このドキュメントは,Microsoft Surface Go 2にUbuntu Serverをインストールした状態で,フロントカメラを使い擬似モーションセンサー化して,人がSurface Go 2の近くに来たら液晶ディスプレイが明るくなり,人がいない時は暗くなるサービスプログラムを導入する.
  • 擬似モーションセンサーとは,カメラの前の映像が大きく動いた時に「人が居る」と認識し,映像に変化がない=動きがない場合は人が居ないと判断することであり,人間を検知したり赤外線などで体温を計測するような仕組みではない.

環境設定

  • パッケージのインストール
sudo apt update🆑
sudo apt install -y python3-pip libcap-dev v4l-utils libcamera-tools \
  libcamera-dev python3-libcamera brightnessctl ddcutil drm-info🆑
sudo pip3 install --break-system-packages opencv-python-headless picamera2🆑
  • libcameraのPythonパスを修正 .
sudo ln -s /usr/lib/x86_64-linux-gnu/python3.12/site-packages/libcamera \
           /usr/lib/python3/dist-packages/libcamera🆑
  • カーネルパラメータの設定
sudo vi /etc/default/grub🆑
  • GRUB_CMDLINE_LINUX_DEFAULTを以下に変更
GRUB_CMDLINE_LINUX_DEFAULT="ro acpi_backlight=native consoleblank=0"
  • カーネルパラメータ変更を反映させてOS再起動.
sudo update-grub🆑
sudo reboot🆑

スクリプトの配置

  • エディタでファイル作成.
sudo vi /usr/local/bin/presence_brightness.py🆑
  • 以下のコードをペースト.
#!/usr/bin/env python3
import cv2, subprocess, numpy as np
import time, os, sys, tempfile, json

CAMERA_INDEX     = 2
CAPTURE_WIDTH    = 640
CAPTURE_HEIGHT   = 480
BRIGHTNESS_HIGH  = 80
BRIGHTNESS_LOW   = 5
TIMEOUT_SECONDS  = 30
MOTION_THRESHOLD = 25
MOTION_MIN_AREA  = 1500
STATE_FILE       = "/tmp/presence_state.json"
BG_FILE          = "/tmp/presence_bg.npy"
FRAME_BYTES      = CAPTURE_WIDTH * CAPTURE_HEIGHT * 3 // 2

def set_brightness(percent):
    subprocess.run(["brightnessctl", "set", f"{percent}%"],
                   check=True, capture_output=True)

def load_state():
    try:
        with open(STATE_FILE) as f:
            return json.load(f)
    except Exception:
        return {"last_seen": time.time(), "is_bright": False}

def save_state(state):
    with open(STATE_FILE, "w") as f:
        json.dump(state, f)

def capture_frame():
    with tempfile.TemporaryDirectory() as tmpdir:
        result = subprocess.run(
            ["cam", "-c", str(CAMERA_INDEX),
             f"--stream=width={CAPTURE_WIDTH},height={CAPTURE_HEIGHT}",
             "--capture=5", f"--file={tmpdir}/"],
            capture_output=True, timeout=15,
        )
        files = sorted([f for f in os.listdir(tmpdir) if f.endswith(".bin")])
        if not files:
            return None
        raw = np.fromfile(os.path.join(tmpdir, files[-1]), dtype=np.uint8)
    if raw.size < FRAME_BYTES:
        return None
    nv12 = raw[:FRAME_BYTES].reshape((CAPTURE_HEIGHT * 3 // 2, CAPTURE_WIDTH))
    return cv2.cvtColor(nv12, cv2.COLOR_YUV2BGR_NV12)

def detect_motion(frame):
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray, (21, 21), 0)
    if not os.path.exists(BG_FILE):
        np.save(BG_FILE, gray.astype("float"))
        return False
    bg = np.load(BG_FILE)
    cv2.accumulateWeighted(gray, bg, 0.1)
    np.save(BG_FILE, bg)
    diff = cv2.absdiff(gray, cv2.convertScaleAbs(bg))
    _, thresh = cv2.threshold(diff, MOTION_THRESHOLD, 255, cv2.THRESH_BINARY)
    thresh = cv2.dilate(thresh, None, iterations=2)
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL,
                                    cv2.CHAIN_APPROX_SIMPLE)
    return any(cv2.contourArea(c) >= MOTION_MIN_AREA for c in contours)

def main():
    state = load_state()
    now = time.time()
    frame = capture_frame()
    if frame is None:
        print("ERROR: フレーム取得失敗", file=sys.stderr)
        sys.exit(1)
    motion = detect_motion(frame)
    if motion:
        state["last_seen"] = now
        print("動体検知")
    else:
        print(f"動きなし ({now - state['last_seen']:.0f}s前に最後に検知)")
    should_bright = (now - state["last_seen"]) < TIMEOUT_SECONDS
    if should_bright and not state["is_bright"]:
        print(f"輝度 → {BRIGHTNESS_HIGH}%")
        set_brightness(BRIGHTNESS_HIGH)
        state["is_bright"] = True
    elif not should_bright and state["is_bright"]:
        print(f"輝度 → {BRIGHTNESS_LOW}%")
        set_brightness(BRIGHTNESS_LOW)
        state["is_bright"] = False
    save_state(state)

if __name__ == "__main__":
    main()

systemdサービスとタイマーの設定

  • 作成したプログラムをサービスとして登録する.
sudo vi /etc/systemd/system/presence-brightness.service🆑
  • 以下のスクリプトをペーストして保存.
[Unit]
Description=Presence brightness check (one-shot)

[Service]
Type=oneshot
User=root
ExecStart=/usr/bin/python3 /usr/local/bin/presence_brightness.py
StandardOutput=journal
StandardError=journal
  • タイマーの設定ファイルの作成.
sudo vi /etc/systemd/system/presence-brightness.timer🆑
  • 以下のコードをペースト.
[Unit]
Description=Presence brightness timer
After=multi-user.target

[Timer]
OnBootSec=30
OnUnitActiveSec=3
AccuracySec=1

[Install]
WantedBy=timers.target
  • サービスとタイマーを有効化.
sudo systemctl daemon-reload🆑
sudo systemctl enable --now presence-brightness.timer🆑
  • 動作確認
journalctl -u presence-brightness.service -f🆑
  • 正しく動作すると,次の様なログが表示される.
ujpadmin@okachimachi:~$ journalctl -u presence-brightness.service -n 10 --no-pager🆑
May 05 19:57:48 okachimachi python3[4097]: 動体検知
May 05 19:57:48 okachimachi systemd[1]: presence-brightness.service: Deactivated successfully.
May 05 19:57:48 okachimachi systemd[1]: Finished presence-brightness.service - Presence brightness check (one-shot).
May 05 19:57:48 okachimachi systemd[1]: presence-brightness.service: Consumed 1.552s CPU time.
May 05 19:57:51 okachimachi systemd[1]: Starting presence-brightness.service - Presence brightness check (one-shot)...
May 05 19:57:52 okachimachi python3[4113]: 動体検知
May 05 19:57:52 okachimachi systemd[1]: presence-brightness.service: Deactivated successfully.
May 05 19:57:52 okachimachi systemd[1]: Finished presence-brightness.service - Presence brightness check (one-shot).
May 05 19:57:52 okachimachi systemd[1]: presence-brightness.service: Consumed 1.547s CPU time.
May 05 19:57:55 okachimachi systemd[1]: Starting presence-brightness.service - Presence brightness check (one-shot)...
ujpadmin@okachimachi:~$

Xサーバ用のスクリーンセイバーを無効化する設定

  • コマンドライン状態だと,カメラがモーションセンサーの役割で輝度が変更されるけど,Xサーバだと別の(スクリーンセイバーの)制御になってしまう.
  • Xのスクリーンブランクを無効にする設定を追加する.
  • /etc/X11/xorg.conf.d/10-no-blank.conf を作成
sudo mkdir -p /etc/X11/xorg.conf.d 🆑
sudo vi /etc/X11/xorg.conf.d/10-no-blank.conf🆑
  • 以下をペーストとしてファイルを保存.
Section "ServerFlags"
    Option "BlankTime"    "0"
    Option "StandbyTime"  "0"
    Option "SuspendTime"  "0"
    Option "OffTime"      "0"
EndSection
  • X Serverを起動していたら再起動する.

サービスを停止したり起動したり

  • 現在ステータスを確認.
ujpadmin@okachimachi:~$ sudo systemctl status presence-brightness.timer🆑
● presence-brightness.timer - Presence brightness timer
     Loaded: loaded (/etc/systemd/system/presence-brightness.timer; enabled; preset: enabled)
     Active: active (waiting) since Wed 2026-05-06 00:55:52 JST; 4s ago
    Trigger: Wed 2026-05-06 00:55:58 JST; 2s left
   Triggers: ● presence-brightness.service
May 06 00:55:52 okachimachi systemd[1]: Started presence-brightness.timer - Presence brightness timer.
ujpadmin@okachimachi:~$
  • Activeになっている.
  • 停止する.
ujpadmin@okachimachi:~$ sudo systemctl stop presence-brightness.timer🆑
ujpadmin@okachimachi:~$
  • ステータスを確認.
ujpadmin@okachimachi:~$ sudo systemctl status presence-brightness.timer🆑
○ presence-brightness.timer - Presence brightness timer
     Loaded: loaded (/etc/systemd/system/presence-brightness.timer; enabled; preset: enabled)
     Active: inactive (dead) since Wed 2026-05-06 00:56:05 JST; 3s ago🈁
   Duration: 12.695s
    Trigger: n/a
   Triggers: ● presence-brightness.service
May 06 00:55:52 okachimachi systemd[1]: Started presence-brightness.timer - Presence brightness timer.
May 06 00:56:05 okachimachi systemd[1]: presence-brightness.timer: Deactivated successfully.
May 06 00:56:05 okachimachi systemd[1]: Stopped presence-brightness.timer - Presence brightness timer.
ujpadmin@okachimachi:~

  • inactive(dead)になっている.
  • 起動してステータスを確認する.
ujpadmin@okachimachi:~$ sudo systemctl start presence-brightness.timer🆑
ujpadmin@okachimachi:~$ sudo systemctl status presence-brightness.timer🆑
● presence-brightness.timer - Presence brightness timer
     Loaded: loaded (/etc/systemd/system/presence-brightness.timer; enabled; preset: enabled)
     Active: active (waiting) since Wed 2026-05-06 00:59:11 JST; 2s ago🈁
    Trigger: Wed 2026-05-06 00:59:14 JST; 145ms left
   Triggers: ● presence-brightness.service
May 06 00:59:11 okachimachi systemd[1]: Started presence-brightness.timer - Presence brightness timer.
ujpadmin@okachimachi:~$
  • カメラを使うアプリを使う場合は,干渉しない様に停止した方が良いのだと思った.

広告スペース
Google