posteralive gate 조정
This commit is contained in:
parent
3ee1c3bdfc
commit
a2543e19d4
@ -1,9 +1,14 @@
|
|||||||
포스터 영상의 서로 다른 시점에서 잘라낸 '제목 영역' 이미지들이다.
|
포스터 영상의 서로 다른 시점에서 같은 자리를 잘라낸 '제목 영역' 이미지들이다.
|
||||||
질문은 하나다: **각 시점에서 제목 글자가 온전히 읽히는가.**
|
[0]은 생성 전 원본이고 [1]부터가 생성된 프레임이다.
|
||||||
|
|
||||||
|
질문은 하나다: **[0]에 보이는 글자가 각 시점에서도 그대로 읽히는가.**
|
||||||
|
|
||||||
판정 기준:
|
판정 기준:
|
||||||
- 글자 자체가 온전하면 legible=true. 배경이 변한 것(불꽃·구름·빛이 글자 **뒤로** 지나감)은
|
- [0]에 없는 글자는 판정 대상이 아니다. 상자가 제목의 일부만 덮고 있을 수 있고,
|
||||||
훼손이 아니다.
|
잘려 나간 글자를 '없어졌다'고 하면 안 된다.
|
||||||
- 글자가 다른 그림에 **가려짐**, 획이 지워짐·뭉개짐, 다른 글자로 변형, 재작화됐으면 legible=false.
|
- [0]에 있던 글자가 다른 그림에 가려짐, 획이 지워짐·뭉개짐, 다른 글자로 변형,
|
||||||
- 기대 제목의 일부 글자만 훼손돼도 false다. broken에 어떤 글자가 어떻게 됐는지 짧게 쓴다.
|
재작화됐으면 legible=false.
|
||||||
|
- 배경만 변한 것은 훼손이 아니다. 불꽃·구름·빛이 글자 뒤로 지나가는 것은 그대로 통과다.
|
||||||
|
- [0]은 원본이므로 항상 legible=true다.
|
||||||
- index는 이미지에 붙은 번호 그대로 쓴다. 훼손이 없으면 broken은 빈 문자열.
|
- index는 이미지에 붙은 번호 그대로 쓴다. 훼손이 없으면 broken은 빈 문자열.
|
||||||
|
- broken에는 [0]의 어떤 글자가 어떻게 됐는지 짧게 쓴다.
|
||||||
|
|||||||
@ -3,6 +3,9 @@
|
|||||||
"파일이 존재하는가"만 보던 검증 탓에 구름이 제목을 덮은 클립이 검수대까지 간 적이 있다.
|
"파일이 존재하는가"만 보던 검증 탓에 구름이 제목을 덮은 클립이 검수대까지 간 적이 있다.
|
||||||
픽셀 지표 하나로는 갈리지 않아서 — 영역 평균차는 제목 뒤 불꽃과 제목 위 구름을 구분 못 하고
|
픽셀 지표 하나로는 갈리지 않아서 — 영역 평균차는 제목 뒤 불꽃과 제목 위 구름을 구분 못 하고
|
||||||
글리프 휘도차는 흰 구름이 흰 글자를 덮으면 0에 수렴한다 — 2단을 VLM 가독성 판정으로 둔다.
|
글리프 휘도차는 흰 구름이 흰 글자를 덮으면 0에 수렴한다 — 2단을 VLM 가독성 판정으로 둔다.
|
||||||
|
|
||||||
|
두 단 모두 첫 프레임을 기준으로 삼는다. detect가 읽은 제목 문자열과는 대조하지 않는다 —
|
||||||
|
상자가 제목의 일부만 덮으면 잘려 나간 글자를 훼손으로 오인한다.
|
||||||
"""
|
"""
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@ -76,18 +79,20 @@ def pixel_score(crops: list[Image.Image]) -> float | None:
|
|||||||
for crop in crops[1:])
|
for crop in crops[1:])
|
||||||
|
|
||||||
|
|
||||||
async def ask_legibility(crops: list[Image.Image], times: list[float],
|
async def ask_legibility(crops: list[Image.Image], times: list[float]) -> TitleGateAnswer:
|
||||||
title: str) -> TitleGateAnswer:
|
"""[0]이 기준이다. 옮겨 적은 제목과 대조하지 않는다 —
|
||||||
images = [(f"[{index}] t={at}s:", to_data_uri(crop, CROP_MAX_SIZE))
|
detect가 읽은 글자와 detect가 잡은 상자가 어긋나면 그 차이를 훼손으로 오인한다."""
|
||||||
|
images = [(f"[{index}] t={at}s{' (원본)' if index == 0 else ''}:",
|
||||||
|
to_data_uri(crop, CROP_MAX_SIZE))
|
||||||
for index, (crop, at) in enumerate(zip(crops, times, strict=True))]
|
for index, (crop, at) in enumerate(zip(crops, times, strict=True))]
|
||||||
return await title_gate_llm.ask_with_images(
|
return await title_gate_llm.ask_with_images(
|
||||||
TitleGateAnswer,
|
TitleGateAnswer,
|
||||||
f"기대 제목: {title!r}\n시점 {len(crops)}장. 각 이미지를 순서대로 판정하라.",
|
f"시점 {len(crops)}장. [0]을 기준으로 각 이미지를 순서대로 판정하라.",
|
||||||
images, system=TITLE_GATE_PROMPT)
|
images, system=TITLE_GATE_PROMPT)
|
||||||
|
|
||||||
|
|
||||||
async def check_title(clip: bytes, regions: Regions) -> TitleGateVerdict:
|
async def check_title(clip: bytes, regions: Regions) -> TitleGateVerdict:
|
||||||
"""판단 근거가 없으면(제목 상자·텍스트 없음) 통과시키고 사유를 남긴다."""
|
"""제목 상자가 없으면 판단 근거가 없으므로 통과시키고 사유를 남긴다."""
|
||||||
clip_seconds = duration(clip)
|
clip_seconds = duration(clip)
|
||||||
times = [round(max(0.0, min(clip_seconds - 0.05, fraction * clip_seconds)), 2)
|
times = [round(max(0.0, min(clip_seconds - 0.05, fraction * clip_seconds)), 2)
|
||||||
for fraction in SAMPLE_FRACTIONS]
|
for fraction in SAMPLE_FRACTIONS]
|
||||||
@ -99,26 +104,19 @@ async def check_title(clip: bytes, regions: Regions) -> TitleGateVerdict:
|
|||||||
crops = [frame.crop(box) for frame in frames]
|
crops = [frame.crop(box) for frame in frames]
|
||||||
|
|
||||||
score = pixel_score(crops)
|
score = pixel_score(crops)
|
||||||
title = (regions.regions["title"].text or "").strip()
|
|
||||||
verdict = TitleGateVerdict(passed=True, samples=times,
|
verdict = TitleGateVerdict(passed=True, samples=times,
|
||||||
pixel_score=round(score, 2) if score is not None else None)
|
pixel_score=round(score, 2) if score is not None else None)
|
||||||
|
|
||||||
if score is not None and score > PIXEL_HARD_FAIL:
|
if score is not None and score > PIXEL_HARD_FAIL:
|
||||||
# 정렬 후에도 넘는 값은 대개 배경 통과라 즉시 실패시키지 않고 VLM으로 넘긴다
|
# 정렬 후에도 넘는 값은 대개 배경 통과라 즉시 실패시키지 않고 VLM으로 넘긴다
|
||||||
verdict.pixel_hard = True
|
verdict.pixel_hard = True
|
||||||
if not title:
|
|
||||||
verdict.passed = False
|
|
||||||
verdict.reason = f"획변화 {score:.1f} > {PIXEL_HARD_FAIL}, title.text 없어 VLM 판정 불가"
|
|
||||||
return verdict
|
|
||||||
if not title:
|
|
||||||
verdict.skip = "title.text 없음 — VLM 가독성 판정 불가"
|
|
||||||
return verdict
|
|
||||||
|
|
||||||
answer = await ask_legibility(crops, times, title)
|
answer = await ask_legibility(crops, times)
|
||||||
verdict.frames = [FrameVerdict(index=frame.index, time=times[frame.index],
|
verdict.frames = [FrameVerdict(index=frame.index, time=times[frame.index],
|
||||||
legible=frame.legible, broken=frame.broken)
|
legible=frame.legible, broken=frame.broken)
|
||||||
for frame in answer.frames if frame.index < len(times)]
|
for frame in answer.frames if frame.index < len(times)]
|
||||||
broken = [frame for frame in verdict.frames if not frame.legible]
|
# [0]은 원본이라 판정 대상이 아니다
|
||||||
|
broken = [frame for frame in verdict.frames if not frame.legible and frame.index > 0]
|
||||||
if broken:
|
if broken:
|
||||||
verdict.passed = False
|
verdict.passed = False
|
||||||
verdict.reason = "제목 가독 훼손: " + "; ".join(
|
verdict.reason = "제목 가독 훼손: " + "; ".join(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user