Skip to content

M-1: 顔を自動で見つけて追従モザイクで隠す - #28

Merged
github-actions[bot] merged 3 commits into
mainfrom
claude/checkin-ahrmvx
Aug 4, 2026
Merged

github-actions[bot] merged 3 commits into
mainfrom
claude/checkin-ahrmvx

Conversation

@rahiseko-alt

@rahiseko-alt rahiseko-alt commented Aug 4, 2026 •

Copy link
Copy Markdown
Owner

何をしたか

ロードマップ M-1(顔を自動で見つけて隠せる)の4葉すべてを実装しました。PR #27 で凍結した criteria/verify は一切変えていません。

葉 実装
M-1-A 顔の位置が分かる YuNet(MIT)を src/models/ に同梱し、opencv 同梱の cv2.FaceDetectorYN で検出
M-1-B 動いても追いかける 顔幅で正規化した中心距離でトラックへ束ね、補間は必ずトラックIDで対応付け
M-1-C 見失っても素顔が出ない 検出が途切れたコマは直前の位置で保持。保持したフレーム番号を記録
M-1-D 本当に判別できない block = max(顔高さ × 1/8, 12px)

追加依存は opencv-python-headless のみ。PyTorch・InsightFace・onnxruntime は不要です。

実装中に受入条件が実際に落ちた(M-1-D)

当初 M-1-D を「顔サイズ比のみ」で実装したところ、テストが落ちました。そこで顔サイズを変えて閾値を実測しました。

顔の高さ 顔として検出されなくなる最小ブロック 顔高さとの比
65px 7px 1/9
111px 9px 1/12
181px 10px 1/18
290px 10px 1/29

小さい顔ほど必要な「比」が大きくなり、大きい顔では絶対値が10px前後で頭打ちになります。比率だけでも絶対値だけでも足りないため、両方の max を取る形に是正しました。実測表は M-1-D の detail に記録しています。

なおこの表は「機械が顔として検出できなくなる」境界であって「人が本人と分からなくなる」境界ではありません。大きく写った顔ほど比を効かせて粗くしているのは、人の目に対する保護を優先しているためです。

テスト(16件・全PASS)

tests/face-mosaic-check.py を追加しました。各テストは葉と1対1で対応します。

  • 固定素材は NASA の public domain 写真から顔を切り出したもの(出典は tests/fixtures/README.md)
  • video-shorts/.gitignore が *.mp4 を除外しているため動画はコミットせず、動きのある場面(M-1-B / M-1-C)は静止画をテスト内で移動合成して再現しています。素材が無くても誰でも同じ結果を再現できます
  • M-1-D は「モザイク後のフレームを顔検出器にかけ直して0件」を機械で確認しています

CI の quality ジョブに opencv の導入ステップを足しました。テストをスキップさせる逃げは AGENTS.md が禁じる「偽の緑」にあたるため取っていません。

配布への影響

build-dist.mjs は src/ を丸ごとコピーするため、モデルとライセンス本文が自動で配布物に入ることを実行して確認済みです(M-4-A / M-4-B の下地)。今回同梱した YuNet は 227KB なので配布サイズへの影響はごくわずかです(SFace 37MB は M-2 で追加します)。

顧客の導入手順は増えていません(既存の pip install -r requirements.txt に1行足しただけ)。

あわせて

PR #27 の CodeRabbit 指摘に対応し、meta.handoff をノードの detail と重複しない進捗のみへ絞りました。

検証

pnpm -r --if-present typecheck      # 対象なし
pnpm -r --if-present lint           # 対象なし
pnpm -r test                        # 全パッケージ PASS(顔モザイク 16 PASS / 0 FAIL)
pnpm -r --if-present build          # 対象なし
node scripts/verify-roadmap-evidence.mjs   # OK(ノード84件)
node video-shorts/build-dist.mjs           # 配布物にモデル+ライセンスが入ることを確認

この PR で done にしていないもの

M-1-A〜M-1-D の status はまだ todo のままです。evidence には CI の run URL(外部事実)を書く規律のため、このPRのCIが緑になってから status と evidence を更新して追記します。自己申告を証拠にしません。


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added automatic face detection and mosaic masking for video frames.
    • Mosaic size adapts to face size for more consistent privacy protection.
    • Added tracking and interpolation to maintain masking during brief detection gaps.
  • Bug Fixes
    • Improved masking for moving faces and temporary detection failures.
  • Tests
    • Added comprehensive validation for detection, tracking, masking coverage, and adaptive mosaic sizing.
  • Documentation
    • Updated roadmap progress and documented face-mosaic test fixtures and expected results.

ロードマップ M-1 の4葉を実装した。

- M-1-A 顔の位置が分かる: YuNet(MIT)を src/models/ に同梱し、
  opencv 同梱の cv2.FaceDetectorYN で検出する。PyTorch/onnxruntime は不要
- M-1-B 顔が動いても追いかける: 顔幅で正規化した中心距離でトラックへ束ね、
  キーフレーム間の補間は必ずトラックIDで対応付ける。検出順で対応付けると
  人物の並びが入れ替わった瞬間に枠が別人へ飛び素顔が露出するため
- M-1-C 見失っても素顔が出ない: 検出が途切れたコマは直前の位置で保持する。
  保持で埋めたフレーム番号は記録し、後段(M-3-A)の確認対象抽出に使う
- M-1-D 本当に判別できなくなっている: モザイクの粗さを
  block = max(顔高さ x 1/8, 12px) で決める

M-1-D は当初「顔サイズ比のみ」で実装したがテストが落ちた。顔サイズを変えて
閾値を実測したところ、必要なブロックサイズは比率だけでも絶対値だけでも決まらず
(顔高さ65px->7px/1-9、111px->9px/1-12、181px->10px/1-18、290px->10px/1-29)、
小さい顔ほど必要な比が大きく、大きい顔では絶対値が頭打ちになると判明したため、
両方の max を取る形へ是正した。実測表は M-1-D の detail に記録した。

テストは tests/face-mosaic-check.py に16件。固定素材は NASA の public domain
写真から顔を切り出したもの(出典は tests/fixtures/README.md)。動画は
.gitignore 対象でコミットできないため、動きのある場面は静止画をテスト内で
移動合成して再現している。

CI の quality ジョブに opencv の導入を足した。テストをスキップさせる逃げは
AGENTS.md が禁じる「偽の緑」にあたるため取らない。

あわせて meta.handoff を進捗のみへ絞った(ノードの detail と二重管理しない。
PR #27 の CodeRabbit 指摘に対応)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EcWtwTVCvSgjg9puWtdWvg
@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rahiseko-alt, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 15 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cbccc6fb-ffca-42e9-93d1-4e062c3af094

📥 Commits

Reviewing files that changed from the base of the PR and between 1fe0a8b and 8dedb06.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • video-shorts/src/face_mosaic.py
  • video-shorts/tests/face-mosaic-check.py
📝 Walkthrough

Walkthrough

Added a YuNet-based face mosaic pipeline with tracking, missed-detection handling, interpolation, frame processing, automated tests, CI setup, fixture documentation, and roadmap updates.

Changes

Face mosaic pipeline

Layer / File(s) Summary
Detection and mosaic primitives
video-shorts/src/face_mosaic.py, video-shorts/src/models/*, video-shorts/requirements.txt
Added YuNet detection, face-box expansion, face-size-relative block sizing, and nearest-neighbor mosaic masking.
Tracking and frame orchestration
video-shorts/src/face_mosaic.py
Added persistent track IDs, normalized matching, missed-detection retention, interpolation, and sequence processing.
Automated validation and integration
video-shorts/tests/*, video-shorts/package.json, .github/workflows/ci.yml, video-shorts/.gitignore, docs/roadmap.html
Added detection, tracking, concealment, and mosaic regression tests. Updated CI, media ignore rules, fixture documentation, and roadmap status.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Frames
  participant mosaic_frames
  participant FaceTracker
  participant YuNet
  Frames->>mosaic_frames: Submit frame sequence
  mosaic_frames->>YuNet: Detect faces in each frame
  YuNet-->>mosaic_frames: Return face boxes
  mosaic_frames->>FaceTracker: Update tracks
  FaceTracker-->>mosaic_frames: Return tracked and held boxes
  mosaic_frames-->>Frames: Return mosaiced frames
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the pull request's main change: automatic face detection and tracking mosaic obfuscation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/checkin-ahrmvx

Comment @coderabbitai help to get the list of available commands.

M-1-A/B/C/D の criteria は全て tests/face-mosaic-check.py で機械判定できるため、
CI(quality ジョブ)を審判とし、その run URL を evidence に書いた。自己申告は
証拠にしない(AGENTS.md「検証の規律」)。

  evidence: https://github.com/rahiseko-alt/ai-editer/actions/runs/30875193419

あわせて .gitignore に webm/mov/mkv/m4a を追加した。実素材での見落とし率計測
(M-5-C)で顧客提供の動画を扱う際、mp4 以外の形式が誤ってコミットされるのを防ぐ。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EcWtwTVCvSgjg9puWtdWvg
@rahiseko-alt
rahiseko-alt marked this pull request as ready for review August 4, 2026 03:38

Copy link
Copy Markdown
Owner Author

@coderabbitai review

レビュー枠の上限で自動レビューが走らなかったため、枠の回復を待って再依頼します。本PRは文書ではなく実装コード(顔検出・追従・モザイク)を含むためレビューを受けたいものです。


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown

@rahiseko-alt 承知しました。PR #28 の実装コードをレビューします。

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
video-shorts/tests/face-mosaic-check.py (2)

111-115: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the unused loop variable.

f is never used inside the loop body. Iterate over truth alone. Static analysis flags this as B007.

♻️ Proposed refactor
-for i, (f, t) in enumerate(zip(frames, truth)):
+for i, t in enumerate(truth):
     tracks = tracker.update(t, frame_index=i)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@video-shorts/tests/face-mosaic-check.py` around lines 111 - 115, Update the
loop over frames and truth in the face-mosaic check to iterate over truth alone,
removing the unused f variable while preserving enumerate’s index and the
existing tracker.update and overlap logic.

Source: Linters/SAST tools


60-63: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Compute the detection result once per assertion.

Lines 60 and 61 call detect_faces twice each: once for the condition and once for the failure message. detect_faces builds a new detector and reloads the ONNX model on every call, because no detector is passed. Line 63 repeats the same detection a third time. Store each result in a variable.

♻️ Proposed refactor
-check("M-1-A: 1人の固定素材から顔が1件検出される", len(detect_faces(one)) == 1, f"got {len(detect_faces(one))}")
-check("M-1-A: 2人の固定素材から顔が2件検出される", len(detect_faces(two)) == 2, f"got {len(detect_faces(two))}")
-
-boxes = detect_faces(one)
+boxes = detect_faces(one)
+boxes_two = detect_faces(two)
+check("M-1-A: 1人の固定素材から顔が1件検出される", len(boxes) == 1, f"got {len(boxes)}")
+check("M-1-A: 2人の固定素材から顔が2件検出される", len(boxes_two) == 2, f"got {len(boxes_two)}")
+
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@video-shorts/tests/face-mosaic-check.py` around lines 60 - 63, Update the
assertions around detect_faces to compute each detection result once and reuse
it for both the condition and failure message. Store the results for one and two
in variables before their corresponding check calls, then reuse the one-result
variable where boxes is initialized instead of calling detect_faces(one) again.
video-shorts/src/face_mosaic.py (1)

142-158: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Matching is order dependent and can swap identities.

The loop assigns each detection to the nearest free track in detection order. An earlier detection can claim the track that is closer to a later detection. MATCH_DISTANCE_RATIO is 1.5 face widths, so two adjacent faces fall inside each other's search radius. The module docstring identifies this identity swap as the exact failure mode of M-1-B.

Consider computing all detection/track distance pairs first and assigning them in ascending distance order. That keeps the code small and removes the ordering dependency.

♻️ Proposed refactor sketch
-        for b in boxes:
-            cx, cy = b[0] + b[2] / 2, b[1] + b[3] / 2
-            best, best_d = None, MATCH_DISTANCE_RATIO
-            for t in self.tracks:
-                if t.seen:
-                    continue
-                tx, ty = t.box[0] + t.box[2] / 2, t.box[1] + t.box[3] / 2
-                d = ((cx - tx) ** 2 + (cy - ty) ** 2) ** 0.5 / max(b[2], 1.0)
-                if d < best_d:
-                    best, best_d = t, d
-            if best is None:
-                self._next_id += 1
-                best = Track(id=self._next_id, box=tuple(b))
-                self.tracks.append(best)
-            best.box = tuple(b)
-            best.seen = True
-            best.missed = 0
+        pairs = []
+        for bi, b in enumerate(boxes):
+            cx, cy = b[0] + b[2] / 2, b[1] + b[3] / 2
+            for t in self.tracks:
+                tx, ty = t.box[0] + t.box[2] / 2, t.box[1] + t.box[3] / 2
+                d = ((cx - tx) ** 2 + (cy - ty) ** 2) ** 0.5 / max(b[2], 1.0)
+                if d < MATCH_DISTANCE_RATIO:
+                    pairs.append((d, bi, t))
+        matched: dict[int, Track] = {}
+        for _d, bi, t in sorted(pairs, key=lambda p: p[0]):
+            if bi in matched or t.seen:
+                continue
+            t.seen = True
+            matched[bi] = t
+        for bi, b in enumerate(boxes):
+            t = matched.get(bi)
+            if t is None:
+                self._next_id += 1
+                t = Track(id=self._next_id, box=tuple(b))
+                t.seen = True
+                self.tracks.append(t)
+            t.box = tuple(b)
+            t.missed = 0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@video-shorts/src/face_mosaic.py` around lines 142 - 158, Update the matching
logic around the detection loop in the track-update method to compute all
eligible detection/track distance pairs first, sort them by ascending distance,
and assign each detection and track at most once. Preserve the existing
MATCH_DISTANCE_RATIO threshold, create new Track instances for unmatched
detections, and keep the existing box, seen, missed, and _next_id updates.
.github/workflows/ci.yml (1)

37-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pin the Python interpreter and avoid --break-system-packages.

The job has no actions/setup-python step, so this command uses whatever python3 the ubuntu-latest image provides. That version changes when GitHub rotates the image. --break-system-packages then writes into the externally managed system Python. ci-green is a required merge check, so an image change can break it without a repository change.

Add actions/setup-python before this step and install into that interpreter. The flag becomes unnecessary.

The version constraint opencv-python-headless>=4.9.0 is also declared in video-shorts/requirements.txt line 9. Two declarations can drift. Consider installing from a small dedicated requirements file instead, so one file stays authoritative.

♻️ Proposed change
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: '3.12'
+          cache: pip
+
       # 顔モザイクのテスト(tests/face-mosaic-check.py)が cv2 を使う。
       # requirements.txt 全体は文字起こしの重い依存を含み CI では不要なので、
       # ここでは opencv だけを入れる。入れずにテストをスキップさせるのは
       # AGENTS.md が禁じる「偽の緑」にあたるため行わない。
       - name: Install Python deps for face tests (opencv only)
-        run: python3 -m pip install --break-system-packages 'opencv-python-headless>=4.9.0'
+        run: python -m pip install 'opencv-python-headless>=4.9.0'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 37 - 43, Add an actions/setup-python
step before “Install Python deps for face tests (opencv only)” with a pinned
Python version, then install dependencies through that configured interpreter
without --break-system-packages. Reuse the existing opencv-python-headless
constraint from video-shorts/requirements.txt or introduce a dedicated
requirements file so the version is declared in one authoritative location.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/roadmap.html`:
- Around line 966-971: Replace the evidence at docs/roadmap.html lines 966-971,
981-986, 995-1000, and 1009-1015 with CI run evidence whose head_sha is commit
1fe0a8b4df4e810e4df057c91f50d1cb6c1f544a, then keep the corresponding status
fields marked done.

In `@video-shorts/src/face_mosaic.py`:
- Around line 97-105: Update expand_box to compute separate horizontal and
vertical margins: derive the horizontal margin from w and the vertical margin
from h using margin, then apply the horizontal value to x bounds and vertical
value to y bounds while preserving clamping and returned dimensions.

In `@video-shorts/tests/face-mosaic-check.py`:
- Around line 226-230: Update the M-1-D check in face-mosaic-check.py to assert
both that the size-ratio result detects no faces and that the fixed 8px result
still detects at least one face. Preserve the existing diagnostic message
showing both detection counts.
- Around line 200-212: Update the scale loop in the face detection test to
record a failed detection for every scale instead of continuing silently when
found is empty, ensuring the test fails or reports that scale according to the
existing assertion pattern used by load(). Also clean up the apply_mosaic call
by removing the stray trailing comma.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 37-43: Add an actions/setup-python step before “Install Python
deps for face tests (opencv only)” with a pinned Python version, then install
dependencies through that configured interpreter without
--break-system-packages. Reuse the existing opencv-python-headless constraint
from video-shorts/requirements.txt or introduce a dedicated requirements file so
the version is declared in one authoritative location.

In `@video-shorts/src/face_mosaic.py`:
- Around line 142-158: Update the matching logic around the detection loop in
the track-update method to compute all eligible detection/track distance pairs
first, sort them by ascending distance, and assign each detection and track at
most once. Preserve the existing MATCH_DISTANCE_RATIO threshold, create new
Track instances for unmatched detections, and keep the existing box, seen,
missed, and _next_id updates.

In `@video-shorts/tests/face-mosaic-check.py`:
- Around line 111-115: Update the loop over frames and truth in the face-mosaic
check to iterate over truth alone, removing the unused f variable while
preserving enumerate’s index and the existing tracker.update and overlap logic.
- Around line 60-63: Update the assertions around detect_faces to compute each
detection result once and reuse it for both the condition and failure message.
Store the results for one and two in variables before their corresponding check
calls, then reuse the one-result variable where boxes is initialized instead of
calling detect_faces(one) again.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 678fe919-cda9-448f-b341-ce232ff32392

📥 Commits

Reviewing files that changed from the base of the PR and between f0e86c3 and 1fe0a8b.

⛔ Files ignored due to path filters (2)
  • video-shorts/tests/fixtures/face-one.png is excluded by !**/*.png
  • video-shorts/tests/fixtures/face-two.png is excluded by !**/*.png
📒 Files selected for processing (10)
  • .github/workflows/ci.yml
  • docs/roadmap.html
  • video-shorts/.gitignore
  • video-shorts/package.json
  • video-shorts/requirements.txt
  • video-shorts/src/face_mosaic.py
  • video-shorts/src/models/LICENSE-yunet.txt
  • video-shorts/src/models/face_detection_yunet_2023mar.onnx
  • video-shorts/tests/face-mosaic-check.py
  • video-shorts/tests/fixtures/README.md

Comment thread docs/roadmap.html
Comment on lines +966 to +971
"status": "done",
"criteria": [
{
"text": "顔が写った固定テスト素材を処理すると、写っている人数と同じ数の顔位置が返る",
"verify": "既知の人数の顔を含む固定テスト素材で顔検出を実行し、検出数が期待値と一致することをテストで確認する",
"evidence": ""
"evidence": "https://github.com/rahiseko-alt/ai-editer/actions/runs/30875193419"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect the cited CI run and the roadmap evidence verifier.
set -euo pipefail

# Show the run metadata: status, conclusion, head SHA, workflow.
gh api repos/rahiseko-alt/ai-editer/actions/runs/30875193419 \
  --jq '{status, conclusion, head_sha, head_branch, event, workflow_id, created_at}' || true

# List the jobs and their steps to confirm the face-mosaic test ran.
gh api repos/rahiseko-alt/ai-editer/actions/runs/30875193419/jobs \
  --jq '.jobs[] | {name, conclusion, steps: [.steps[] | {name, conclusion}]}' || true

# Show what the evidence verifier enforces.
fd -t f 'verify-roadmap-evidence.mjs' | xargs -r cat -n

Repository: rahiseko-alt/ai-editer

Length of output: 11402


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sha=9be409128567299b91ef23bbaf901853ca9be658
repo=rahiseko-alt/ai-editer

printf '%s\n' '--- commit files ---'
gh api "repos/$repo/commits/$sha" \
  --jq '{sha: .sha, parents: [.parents[].sha], files: [.files[].filename]}'

printf '%s\n' '--- workflow test commands at the run commit ---'
gh api "repos/$repo/contents/.github/workflows/ci.yml?ref=$sha" \
  --jq '.content' | base64 -d | sed -n '1,180p'

printf '%s\n' '--- face test source at the run commit ---'
gh api "repos/$repo/contents/video-shorts/tests/face-mosaic-check.py?ref=$sha" \
  --jq '.content' | base64 -d | cat -n

printf '%s\n' '--- roadmap M-1 evidence at the run commit ---'
gh api "repos/$repo/contents/docs/roadmap.html?ref=$sha" \
  --jq '.content' | base64 -d |
  rg -n -A8 -B2 '30875193419|M-1|顔が写った固定|顔が移動する固定|顔検出が途切れた|出力動画の顔部分'

Repository: rahiseko-alt/ai-editer

Length of output: 19889


🏁 Script executed:

#!/bin/bash
set -euo pipefail

gh run view 30875193419 --repo rahiseko-alt/ai-editer --log |
  rg -n 'face-mosaic|16|顔|mosaic|Test|Verify roadmap evidence' || true

gh api repos/rahiseko-alt/ai-editer/commits/9be409128567299b91ef23bbaf901853ca9be658 \
  --jq '{sha: .sha, files: [.files[].filename]}'

Repository: rahiseko-alt/ai-editer

Length of output: 26698


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo=rahiseko-alt/ai-editer
run=30875193419
head=9be409128567299b91ef23bbaf901853ca9be658
merge=ab38f9bc9bd3c49f59c0a92da3e3799febf1d673

printf '%s\n' '--- run and pull-request linkage ---'
gh api "repos/$repo/actions/runs/$run" \
  --jq '{head_sha, head_branch, event, pull_requests, head_repository: .head_repository.full_name, base_ref}'

gh api "repos/$repo/pulls/28" \
  --jq '{number, state, head: .head.sha, base: .base.sha, merge_commit_sha, merged_at}'

printf '%s\n' '--- roadmap states at head and merge commits ---'
for ref in "$head" "$merge"; do
  echo "REF=$ref"
  gh api "repos/$repo/contents/docs/roadmap.html?ref=$ref" --jq '.content' |
    base64 -d |
    rg -n -A6 '"id": "M-1-[ABCD]"|"status":|"evidence":' |
    sed -n '1,100p'
done

printf '%s\n' '--- merge commit metadata and changed files ---'
gh api "repos/$repo/commits/$merge" \
  --jq '{sha: .sha, parents: [.parents[].sha], files: [.files[].filename]}'

Repository: rahiseko-alt/ai-editer

Length of output: 11176


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo=rahiseko-alt/ai-editer
for ref in 9be409128567299b91ef23bbaf901853ca9be658 ab38f9bc9bd3c49f59c0a92da3e3799febf1d673; do
  echo "REF=$ref"
  gh api "repos/$repo/contents/docs/roadmap.html?ref=$ref" --jq '.content' |
    base64 -d |
    rg -n -A6 '"id": "M-1-[ABCD]"|"status":|"evidence":' |
    sed -n '1,100p'
done

Repository: rahiseko-alt/ai-editer

Length of output: 9602


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo=rahiseko-alt/ai-editer
head=1fe0a8b4df4e810e4df057c91f50d1cb6c1f544a
run=30875193419

printf '%s\n' '--- current PR head commit ---'
gh api "repos/$repo/commits/$head" \
  --jq '{sha: .sha, parents: [.parents[].sha], files: [.files[].filename]}'

printf '%s\n' '--- M-1 states at the current PR head ---'
gh api "repos/$repo/contents/docs/roadmap.html?ref=$head" --jq '.content' |
  base64 -d |
  python3 -c '
import sys, json, re
s=sys.stdin.read()
m=re.search(r"<script type=\"application/json\" id=\"roadmap-data\">(.*?)</script>", s, re.S)
data=json.loads(m.group(1))
def walk(n):
    if n.get("id") in {"M-1-A","M-1-B","M-1-C","M-1-D"}:
        print(json.dumps({"id": n["id"], "status": n.get("status"),
                          "evidence": [c.get("evidence") for c in n.get("criteria", [])]},
                         ensure_ascii=False))
    for c in n.get("children", []):
        walk(c)
for n in data["nodes"]:
    walk(n)
'

printf '%s\n' '--- cited run versus current PR head ---'
gh api "repos/$repo/actions/runs/$run" \
  --jq '{head_sha, event, status, conclusion, created_at}'

Repository: rahiseko-alt/ai-editer

Length of output: 1067


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo=rahiseko-alt/ai-editer
head=1fe0a8b4df4e810e4df057c91f50d1cb6c1f544a

gh api "repos/$repo/commits/$head" \
  --jq '{sha: .sha, parents: [.parents[].sha], files: [.files[].filename]}'

gh api "repos/$repo/contents/docs/roadmap.html?ref=$head" --jq '.content' |
  base64 -d |
  rg -n -A8 -B2 '"id": "M-1-[ABCD]"' |
  tail -n 80

Repository: rahiseko-alt/ai-editer

Length of output: 2756


Run CI for commit 1fe0a8b4df4e810e4df057c91f50d1cb6c1f544a before marking M-1 leaves done.

Run 30875193419 passed all 16 face-mosaic checks, but its head_sha is the parent commit 9be409128567299b91ef23bbaf901853ca9be658. The four done transitions were added in 1fe0a8b4df4e810e4df057c91f50d1cb6c1f544a. Use a CI run for the current commit as evidence.

📍 Affects 1 file
  • docs/roadmap.html#L966-L971 (this comment)
  • docs/roadmap.html#L981-L986
  • docs/roadmap.html#L995-L1000
  • docs/roadmap.html#L1009-L1015
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/roadmap.html` around lines 966 - 971, Replace the evidence at
docs/roadmap.html lines 966-971, 981-986, 995-1000, and 1009-1015 with CI run
evidence whose head_sha is commit 1fe0a8b4df4e810e4df057c91f50d1cb6c1f544a, then
keep the corresponding status fields marked done.

Source: Coding guidelines

Comment thread video-shorts/src/face_mosaic.py
Comment thread video-shorts/tests/face-mosaic-check.py
Comment thread video-shorts/tests/face-mosaic-check.py
レビュー指摘7件は全て妥当と判断し是正した。

【正しさ】
- トラック割り当てが検出順に依存し、隣り合う2人で人物が入れ替わりうる問題を修正。
  検出ごとに「空いている中で最も近いトラック」を取る貪欲法は、先に処理された検出が
  後の検出にとってより近いトラックを奪う。探索半径は顔幅1.5個ぶんで隣人は互いの
  半径に入るため、これは M-1-B が防ごうとしている当の失敗そのものだった。
  全ての(検出,トラック)距離を先に出し近い順に一意割り当てする方式へ変更し、
  順序を入れ替えてもIDが変わらないことを検証する項目を追加した。
- expand_box の縦マージンを高さ由来にした。顔枠は横より縦が長く、幅由来の余白を
  上下に使うと縦の拡張率が足りず髪と顎が枠から出ていた。

【検証の質】
- 倍率ループで顔が検出できなかった場合に黙って飛ばしていたのを、記録して落とすようにした
  (その倍率が何の根拠も出さないまま緑になるのを防ぐ=サイレント失敗禁止)。
- 「固定8pxでは隠しきれない」という比率設計の根拠を、対照として実際に検証するようにした。
  なお縦マージン修正で隠す範囲が広がった結果、素材そのままのサイズ(顔112px)では
  固定8pxでも隠れてしまうと判明したため、フレームサイズを揃えた合成キャンバス上で
  倍率を変えて測る形に統一した(顔111px以上で固定8pxは破綻する)。
- detect_faces の重複呼び出し(呼ぶたびONNXを読み直す)と未使用ループ変数を整理した。

【CI】
- actions/setup-python で版を固定し --break-system-packages を廃止した。ランナー
  イメージ更新でリポジトリ側の変更なしに ci-green(必須チェック)が壊れるのを防ぐ。
- opencv の版指定は requirements.txt を唯一の正とし、CIはそこから抜き出して使う。

【顧客の実環境を想定した補強】
- opencv 未導入時に、スタックトレースではなく対処を示すメッセージで止まるようにした
  (transcribe.py の作法に合わせた)。
- 実素材で必ず起きる状況の回帰テストを追加: 顔が写らない場面/顔が画面端で切れる場面/
  スマホの縦動画/顔がモザイク1ブロックより小さい場合。

テストは 18件 → 22件。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EcWtwTVCvSgjg9puWtdWvg
@github-actions
github-actions Bot merged commit cd673ad into main Aug 4, 2026
13 checks passed
@github-actions
github-actions Bot deleted the claude/checkin-ahrmvx branch August 4, 2026 04:07
rahiseko-alt pushed a commit that referenced this pull request Aug 4, 2026
M-2-A/B/C の criteria は全て tests/face-mosaic-check.py で機械判定できるため、
CI(quality ジョブ)を審判とし、その run URL を evidence に書いた。

あわせて PR #28 のレビュー指摘に対応する。M-1 の evidence が指していた run は
head_sha が実装コミットの親を指しており、done を立てたコミットの検証になっていなかった。
本コミットの run は M-1 のコード・done 表記・M-2 のコードを全て含む状態を検証するため、
M-1 の4葉もこの run を指すよう差し替えた。

  evidence: https://github.com/rahiseko-alt/ai-editer/actions/runs/30876998631

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EcWtwTVCvSgjg9puWtdWvg
github-actions Bot pushed a commit that referenced this pull request Aug 4, 2026
* M-2: 特定の人だけ隠し方を変えられるようにする

ロードマップ M-2 の3葉を実装した。

- M-2-A 登録した人を見分ける: SFace(Apache 2.0)を src/models/ へ同梱し、
  cv2.FaceRecognizerSF で顔の特徴(128次元)を取り出して照合する
- M-2-B 別人と取り違えない: コサイン類似度が閾値(0.363)未満なら別人として扱う
- M-2-C 人によって粗さを変える: mosaic_frames(people=..., ratio_for=...) で
  登録者と未登録者に別々のモザイク比を適用する

安全側の設計として、見分けに失敗した顔にも必ず既定のモザイクをかける。
取り逃しはプライバシー事故に直結するため、閾値を下げて誤認を招くのではなく
「未登録でも隠す」で守る(この性質もテストで確認している)。

照合はトラックごとに最大3回までとし、多数決で確定させる。毎フレーム照合しても
結論はほとんど変わらないのに費用だけ増えるため(実測: 60秒の動画で7秒→0.13秒)。

検出とトラックの対応付けは FaceTracker.last_assignment で受け渡す。位置の
突き合わせで推測すると、同じ座標に別人が来た場合に取り違えうるため。

固定素材を2枚追加した。同一人物の照合には撮影年が13年違う2枚を使い、簡単すぎる
条件で緑にならないようにしている。参照写真に顔が写っていない場合は、黙って
「登録できた」ことにせず理由を示して止まる。

テストは 22件 → 29件。配布物にモデルとライセンス本文が入ることも確認済み
(SFace の SHA256 は配布元と一致: 0ba9fbfa01b5270c96627c4ef784da859931e02f04419c829e83484087c34e79)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EcWtwTVCvSgjg9puWtdWvg

* M-2: 3葉を done にし、M-1 の evidence を本コミットの CI run へ差し替える

M-2-A/B/C の criteria は全て tests/face-mosaic-check.py で機械判定できるため、
CI(quality ジョブ)を審判とし、その run URL を evidence に書いた。

あわせて PR #28 のレビュー指摘に対応する。M-1 の evidence が指していた run は
head_sha が実装コミットの親を指しており、done を立てたコミットの検証になっていなかった。
本コミットの run は M-1 のコード・done 表記・M-2 のコードを全て含む状態を検証するため、
M-1 の4葉もこの run を指すよう差し替えた。

  evidence: https://github.com/rahiseko-alt/ai-editer/actions/runs/30876998631

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EcWtwTVCvSgjg9puWtdWvg

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants