0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

画面収録から利用者名を消す(目視で3回漏らして、最後は機械に検査させた話)

0
Last updated at Posted at 2026-08-14

デスクトップで動く AI エージェントを作っていて、デモ動画を撮った。
承認ダイアログに /Users/<利用者名>/Downloads/… がそのまま映っていた。

消せばいいだけの話だと思っていたが、目視の確認で 3 回続けて漏らした。
最後は完成品の全編を機械に走査させることになった。

結論を先に書くと、「映ってはまずいものが消えたか」を目で確かめるのは
成立しない。
消す処理と同じくらい、消えたことを機械で確かめる処理が要る。

決め打ちの枠では消えない

最初は素朴にやった。パスが出ているコマを 1 枚開いて座標を測り、
そこを delogo で潰す。

delogo=x=450:y=300:w=370:h=44:enable='between(t,99.9,102.4)'

出来上がりを確認すると、最初の 1〜2 秒だけ利用者名が出ていた。

原因を測って分かった。macOS のダイアログは飛び込んでくる。
出てから落ち着くまで、位置が動き続ける。

時刻 ダイアログの上端 左端
98.4 372 458
98.8 230 180
99.2 19 462
99.6 71 407
100.0 94 398
102.4 94 398

落ち着いた 100.0 秒で測った枠は、98.4〜99.6 秒のどこにも当たらない。
しかもその間、文字はずっと読める。

区間を切り詰めて「落ち着いてから」だけ使う手もあるが、
今回はノーカットで出す動画だったので使えなかった。追いかけるしかない。

型紙を作って畳み込む

文字の大きさはダイアログが動いても変わらない。
なので、落ち着いたコマから /Users/sa の見た目を切り出して型紙にし、
各コマの上で滑らせて一番合う場所を探す。

素直に総当たりすると、1920×1080 の各位置で 150×38 の型紙と比べることになる。
200 万か所 × 5700 画素で、Python では動かない。

畳み込みはフーリエ変換で一発にできる。

def correlate(field, kernel):
    """畳み込みをフーリエ変換で。総当たりだと動かない"""
    fh, fw = field.shape
    F = np.fft.rfft2(field)
    K = np.fft.rfft2(kernel[::-1, ::-1], s=(fh, fw))
    out = np.fft.irfft2(F * K, s=(fh, fw))
    kh, kw = kernel.shape
    return out[kh - 1:, kw - 1:]        # 左上を原点に揃える

一致の度合いは 2 つ見る。見落としと、食いつきすぎの両方を嫌う。

def find(img, tmpl):
    d = dark(img)
    hit  = correlate(d, tmpl)                    # 型紙の黒 ∩ 画の黒
    load = correlate(d, np.ones_like(tmpl))      # 窓の中の黒の総量
    ink  = tmpl.sum()

    score = hit / (load + 0.35 * ink + 1e-6)
    score[hit < 0.55 * ink] = 0                  # 見落としは足切り
    y, x = np.unravel_index(int(np.argmax(score)), score.shape)
    return int(x), int(y), float(score[y, x])

hit だけ見ると、黒が密集した別の場所(コードの抜粋など)に食いつく。
load で割って「窓の中の余計な黒」を罰する。分母の 0.35 * ink は、
黒が少ない場所でスコアが爆発しないための下駄。

これで各コマの座標が出るので、delogo の enable に流し込む。
同じ位置が続く区間はまとめないと、フィルタが 500 本を超えて組めなくなる。

3 回漏らした

1 回目 — 飛び込んでいる途中

上に書いたやつ。固定の枠が当たらない。

2 回目 — 半透明で浮かび上がる間

追跡を入れても、まだ漏れた。ダイアログは薄いところから現れる。

dark() のしきい値を 120 にしていたので、薄いコマの文字が
「暗い画素」に数えられず、追跡がそこだけ始まらない。
文字は薄くても読める。

def dark(img):
    # **しきい値は 150。** 120 だと、半透明で浮かび上がる間の
    # 薄い文字を落とす。落とすと追跡が始まらず、そこだけ素通しになる
    return (img < 150).astype(np.float32)

加えて、続いている区間の頭を 0.3 秒さかのぼり、尻を 0.2 秒延ばす。

3 回目 — ブラウザの URL 欄

これが効いた。

動画の最後で、作った HTML をブラウザで開く。
URL 欄に file:///Users/<利用者名>/… が 23 秒間出ていた。

ダイアログばかり見ていて、そこは探す場所として思いついていなかった。
思いつかないものは、目で探しても見つからない。

だから完成品を走査する

ここで方針を変えた。消す処理を信じるのをやめて、完成品を全編走査する。

TEMPLATES = [
    (100.0, (452, 302, 150, 38)),   # write_file … 大きめの字
    (148.7, (850, 512,  90, 28)),   # open_path  … 小さい字
    (152.0, (940,  12, 100, 30)),   # ブラウザの URL 欄 … また別の字
]

型紙は 1 枚では足りない。 ダイアログの種類ごとに幅が違い、
字の大きさも違う。1 枚で通そうとすると、小さい方がまったく当たらず、
0.30 前後の偽の山に食いつく。

しきい値は追跡より厳しくする。実測では本物が 0.64〜0.74、
偽の山が 0.46 以下だったので 0.55 に置いた。
0.45 にしたらブラウザの小さなアイコンに 179 コマ食いついて、
本物が埋もれた。

この検査が、さらに 1 コマの漏れを見つけた。

追跡の刻みは、出力のコマ数に合わせる

見つかったのは 113.933… 秒の 1 コマだった。

追跡を 20 コマ/秒で回していた。つまり 113.90 と 113.95 を見ている。
出力は 30 コマ/秒なので、実際に出るのは 113.933… のコマ。
そこは誰も見ていない。

# (始め, 終わり, 1 秒あたり何コマ見るか)
WINDOWS = [
    (113.4, 118.2, 30),     # 飛び込んでくる。出力と同じ刻みで見る
    (150.8, 174.8,  5),     # 位置が動かない。粗くてよい
]

位置が動く区間だけ出力と同じ刻みにして、動かない区間は粗いままにする。
全部 30 にすると、フーリエ変換が 4000 回走って終わらない。

最終的に 2,225 コマを走査して漏れなし。

まとめ

  • 動くものを固定の枠では隠せない。 ダイアログは飛び込んでくる
  • 追跡はフーリエ変換で畳み込む。総当たりは動かない
  • スコアは見落としと食いつきすぎの両方を罰する形にする
  • 目視は 3 回漏れた。 うち 1 回は、探す場所として思いついていなかった
  • 完成品を機械で走査する。 追跡の刻みは出力のコマ数に合わせる

そもそも画面に利用者名を出さなければこの作業は要らない。
アプリ側は ~/… 表記に直したので、次に撮る素材からは不要になる。
それでも検査は残す。 思いつかないものは、目では見つからないので。


踏んだ落とし穴だけを書いています。 同じところで止まった人の時間が少しでも減れば十分です。
他の記事も同じ形で、実際に測った数字と、直した手順だけを置いています。

(この話が出てきたのは Wisp という個人開発のアプリです)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?