Разрез вблизи: нашёл причину ближней потери, лечение не окупилось
This commit is contained in:
parent
8a0badbc20
commit
af317803ab
5 changed files with 98 additions and 6 deletions
|
|
@ -1514,3 +1514,59 @@ P@150 0.32), а на всём, что меньше, — заметно хуже:
|
|||
| тихий | `nov_fade_from: 0` | 3.5 | за 90 м почти нет |
|
||||
| средний | `min_rays_far: 0` | 6.5 | человек стоя есть, мелочь хуже |
|
||||
| **дальнобойный (умолчание)** | — | 8.0 | полная |
|
||||
|
||||
### 12.7. Ближняя потеря: причина найдена, лечение отвергнуто
|
||||
|
||||
В воронке оставалась последняя необъяснённая дыра: на 6–30 м, где предмет
|
||||
даёт сотни лучей, кандидат не появлялся в 18 % видимых наблюдений. Прямая
|
||||
трассировка вставки на 6.5, 8, 10, 12, 20 и 28 м ничего не воспроизвела —
|
||||
кандидат находился в **100 %** случаев (616 из 616). Ответ нашёлся в самих
|
||||
данных полигона:
|
||||
|
||||
| смещение предмета от оси | наблюдений | без кандидата |
|
||||
|---|---:|---:|
|
||||
| 0.0 м | 198 | 0.01 |
|
||||
| +0.9 м | 198 | **0.34** |
|
||||
|
||||
Хуже всего там, где есть платформа: `doubleT_platform` и
|
||||
`squareT_platform_squareT_switch` по 0.27 против 0.11–0.14 у остальных.
|
||||
Предмет у края габарита слипается с кромкой платформы в одну связную
|
||||
компоненту и отбрасывается вместе с ней — **то же самое, что и в полосе
|
||||
30–50 м**. Причина общая: разрез по контрасту запрещён ближе 55 м.
|
||||
|
||||
Комментарий в коде объяснял этот запрет тем, что вблизи предмет со стеной
|
||||
не слипается. Замер показал, что это неверно, и объяснение исправлено.
|
||||
|
||||
Лечение пробовалось не «в лоб» (резать везде — уже отвергнуто в п. 12.1), а
|
||||
избирательно: резать вблизи только компоненты, **слишком растянутые по
|
||||
глубине**, чтобы быть предметом. Мерой служит разброс дальности внутри
|
||||
компоненты: человек занимает десятки сантиметров, кромка платформы —
|
||||
десятки метров. Две свёртки по меткам, доли миллисекунды.
|
||||
|
||||
| порог растянутости | ложных/км, знакомая | незнакомая |
|
||||
|---|---:|---:|
|
||||
| выключено | **8.0** | **20.3** |
|
||||
| > 8 м | 10.0 | 25.2 |
|
||||
| > 4 м | 12.0 | — |
|
||||
| > 2 м | 12.0 | — |
|
||||
|
||||
Воронка при самом строгом пороге (решений от видимых наблюдений):
|
||||
|
||||
| полоса | без разреза | с разрезом |
|
||||
|---|---:|---:|
|
||||
| 6–30 м | 0.83 | **0.90** |
|
||||
| 30–50 м | 0.59 | **0.68** |
|
||||
| 50–70 м | **0.81** | 0.71 |
|
||||
| 70–90 м | **0.62** | 0.54 |
|
||||
|
||||
**Отвергнуто.** Ближние полосы вылечились, но сломались средние: человек
|
||||
стоя P@50 0.70 → 0.67, рабочая дальность «как есть» 80 → 62 м, посторонних
|
||||
тревог на кадр 0.071 → 0.102. Механизм тот же, что и у тройки лучей вблизи:
|
||||
разрез на 50–55 м дробит предмет на конкурирующие обрывки, и настоящий трек
|
||||
проигрывает им в торможении по соседству.
|
||||
|
||||
Итог размена: мы вылечили бы полосу, которая при служебном торможении с
|
||||
60 км/ч и так за тормозным путём, сломав ту, что внутри него, и заплатили бы
|
||||
четвертью ложных треков на обеих сценах. Механизм оставлен в коде выключенным
|
||||
(`near_long: 0`): на линии без платформ у самого габарита он может оказаться
|
||||
дешевле — но проверять это надо замером на той линии.
|
||||
|
|
|
|||
|
|
@ -234,6 +234,7 @@ def find_candidates(tf: TrackFrame, lam: LaminaOutput, corridor: Corridor, *,
|
|||
dilate_cols: int = 2, max_depth: float = 15.0,
|
||||
ctx_up: float = 4.0, split_adv: float = 0.0,
|
||||
split_gap: float = 0.0, split_near: float = 0.0,
|
||||
near_long: float = 0.0,
|
||||
split_top: int = 0,
|
||||
advance: np.ndarray | None = None,
|
||||
masks: tuple | None = None) -> list[Candidate]:
|
||||
|
|
@ -272,11 +273,32 @@ def find_candidates(tf: TrackFrame, lam: LaminaOutput, corridor: Corridor, *,
|
|||
# и торчать из своего окружения.
|
||||
fig = gap_img if (split_adv <= 0 or advance is None) else np.where(advance > split_adv, gap_img, 0.0)
|
||||
if split_near > 0:
|
||||
# Ближе этой дальности покадровый тракт видит предмет и без разреза:
|
||||
# компонента со стеной там не слипается, лучей много, форма читается.
|
||||
# Резать там незачем, а обстановки, дающей контраст, в ближнем поле
|
||||
# на порядок больше — оттуда и берутся лишние кандидаты.
|
||||
fig = np.where(tf.d > split_near, fig, 0.0)
|
||||
# Ближе этой дальности разрез был выключен целиком, и причина
|
||||
# стояла в комментарии неверная: считалось, что вблизи предмет со
|
||||
# стеной не слипается. Замер показал обратное — на 6…30 м предмет,
|
||||
# смещённый от оси на 0.9 м, теряется в 34 % наблюдений против 1 %
|
||||
# на самой оси, и хуже всего там, где есть платформа. Он уходит
|
||||
# вместе с её кромкой.
|
||||
#
|
||||
# Резать вблизи всё подряд дорого: обстановки, дающей контраст, в
|
||||
# ближнем поле на порядок больше. Поэтому режем только компоненты,
|
||||
# СЛИШКОМ РАСТЯНУТЫЕ по глубине, чтобы быть предметом: человек
|
||||
# занимает по дальности десятки сантиметров, кромка платформы и
|
||||
# стена — десятки метров. Мерой служит разброс дальности внутри
|
||||
# компоненты (две свёртки по меткам, доли миллисекунды).
|
||||
near = tf.d <= split_near
|
||||
if near_long > 0.0 and n > 0:
|
||||
lab = labels.ravel()
|
||||
rr = np.where(np.isfinite(tf.r), tf.r, 0.0).ravel()
|
||||
cnt = np.bincount(lab, minlength=n + 1).astype(np.float64)
|
||||
s1 = np.bincount(lab, weights=rr, minlength=n + 1)
|
||||
s2 = np.bincount(lab, weights=rr * rr, minlength=n + 1)
|
||||
c = np.maximum(cnt, 1.0)
|
||||
var = np.maximum(s2 / c - (s1 / c) ** 2, 0.0)
|
||||
stretched = np.sqrt(var) > near_long
|
||||
stretched[0] = False
|
||||
near = near & ~stretched[labels]
|
||||
fig = np.where(near, 0.0, fig)
|
||||
labels, n = split_by_figure(labels, n, tf.r, fig, max_depth=max_depth,
|
||||
thr=split_gap, col_reach=dilate_cols + 1,
|
||||
top=split_top)
|
||||
|
|
|
|||
|
|
@ -50,7 +50,12 @@ class Params:
|
|||
# вытаскивает предмет из компоненты, растёкшейся вдоль стены круглого
|
||||
# тоннеля, где покадровый тракт слеп целиком. 0 — выключено.
|
||||
split_gap: float = 6.0
|
||||
split_near: float = 55.0 # ближе этого не резать: там и так видно
|
||||
split_near: float = 55.0 # ближе этого резать только растянутое
|
||||
# Разброс дальности внутри компоненты, выше которого её режут и в
|
||||
# ближней зоне: предмет по дальности занимает десятки сантиметров,
|
||||
# кромка платформы и стена — десятки метров. 0 — не резать вблизи
|
||||
# вовсе. Замер — EXPERIMENTS п. 12.7.
|
||||
near_long: float = 0.0
|
||||
split_top: int = 1 # сколько фигур выносить из одной компоненты
|
||||
# Накопление слабых улик в координатах пути (см. fan_body.py). Заменяет
|
||||
# недоступный на большой дальности контраст к фону: на 140…185 м `gap`
|
||||
|
|
@ -317,6 +322,7 @@ class FlyGuard:
|
|||
split_adv=self.p.split_adv,
|
||||
split_gap=self.p.split_gap,
|
||||
split_near=self.p.split_near,
|
||||
near_long=self.p.near_long,
|
||||
split_top=self.p.split_top, advance=advance,
|
||||
d_min=self.p.d_min,
|
||||
d_max=self.p.d_max, min_rays=self.p.min_rays,
|
||||
|
|
|
|||
|
|
@ -119,6 +119,8 @@ def main() -> None:
|
|||
help="с какой дальности порог начинает падать, м")
|
||||
ap.add_argument("--novelty-floor", type=float, default=None,
|
||||
help="ниже этой новизны трек не считается")
|
||||
ap.add_argument("--near-long", type=float, default=None,
|
||||
help="разброс дальности, выше которого компоненту режут и вблизи; 0 — не резать вблизи")
|
||||
ap.add_argument("--min-rays", type=int, default=None,
|
||||
help="сколько лучей минимум образуют кандидата")
|
||||
ap.add_argument("--min-rays-far", type=int, default=None,
|
||||
|
|
@ -177,6 +179,8 @@ def main() -> None:
|
|||
over["warn_far_from"] = args.warn_far_from
|
||||
if args.novelty_floor is not None:
|
||||
over["novelty_floor"] = args.novelty_floor
|
||||
if args.near_long is not None:
|
||||
over["near_long"] = args.near_long
|
||||
if args.min_rays is not None:
|
||||
over["min_rays"] = args.min_rays
|
||||
if args.min_rays_far is not None:
|
||||
|
|
|
|||
|
|
@ -138,6 +138,8 @@ def main() -> None:
|
|||
help="с какой дальности порог начинает падать, м")
|
||||
ap.add_argument("--novelty-floor", type=float, default=None,
|
||||
help="ниже этой новизны трек не считается")
|
||||
ap.add_argument("--near-long", type=float, default=None,
|
||||
help="разброс дальности, выше которого компоненту режут и вблизи; 0 — не резать вблизи")
|
||||
ap.add_argument("--min-rays", type=int, default=None,
|
||||
help="сколько лучей минимум образуют кандидата")
|
||||
ap.add_argument("--min-rays-far", type=int, default=None,
|
||||
|
|
@ -186,6 +188,8 @@ def main() -> None:
|
|||
over["warn_far_from"] = args.warn_far_from
|
||||
if args.novelty_floor is not None:
|
||||
over["novelty_floor"] = args.novelty_floor
|
||||
if args.near_long is not None:
|
||||
over["near_long"] = args.near_long
|
||||
if args.min_rays is not None:
|
||||
over["min_rays"] = args.min_rays
|
||||
if args.min_rays_far is not None:
|
||||
|
|
|
|||
Loading…
Reference in a new issue