|
Surface Go 2 on Ubuntu Serverでカメラを使ったモーションセンサー
Surface Go 2 on Ubuntu Serverでカメラを使った擬似モーションセンサー
概要
更新履歴
目次
はじめに
-
このドキュメントは,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🆑
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"
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
サービスを停止したり起動したり
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:~$
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:~$
- カメラを使うアプリを使う場合は,干渉しない様に停止した方が良いのだと思った.
|
|