Разрез вблизи: нашёл причину ближней потери, лечение не окупилось
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 м почти нет |
|
| тихий | `nov_fade_from: 0` | 3.5 | за 90 м почти нет |
|
||||||
| средний | `min_rays_far: 0` | 6.5 | человек стоя есть, мелочь хуже |
|
| средний | `min_rays_far: 0` | 6.5 | человек стоя есть, мелочь хуже |
|
||||||
| **дальнобойный (умолчание)** | — | 8.0 | полная |
|
| **дальнобойный (умолчание)** | — | 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,
|
dilate_cols: int = 2, max_depth: float = 15.0,
|
||||||
ctx_up: float = 4.0, split_adv: float = 0.0,
|
ctx_up: float = 4.0, split_adv: float = 0.0,
|
||||||
split_gap: float = 0.0, split_near: float = 0.0,
|
split_gap: float = 0.0, split_near: float = 0.0,
|
||||||
|
near_long: float = 0.0,
|
||||||
split_top: int = 0,
|
split_top: int = 0,
|
||||||
advance: np.ndarray | None = None,
|
advance: np.ndarray | None = None,
|
||||||
masks: tuple | None = None) -> list[Candidate]:
|
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)
|
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:
|
if split_near > 0:
|
||||||
# Ближе этой дальности покадровый тракт видит предмет и без разреза:
|
# Ближе этой дальности разрез был выключен целиком, и причина
|
||||||
# компонента со стеной там не слипается, лучей много, форма читается.
|
# стояла в комментарии неверная: считалось, что вблизи предмет со
|
||||||
# Резать там незачем, а обстановки, дающей контраст, в ближнем поле
|
# стеной не слипается. Замер показал обратное — на 6…30 м предмет,
|
||||||
# на порядок больше — оттуда и берутся лишние кандидаты.
|
# смещённый от оси на 0.9 м, теряется в 34 % наблюдений против 1 %
|
||||||
fig = np.where(tf.d > split_near, fig, 0.0)
|
# на самой оси, и хуже всего там, где есть платформа. Он уходит
|
||||||
|
# вместе с её кромкой.
|
||||||
|
#
|
||||||
|
# Резать вблизи всё подряд дорого: обстановки, дающей контраст, в
|
||||||
|
# ближнем поле на порядок больше. Поэтому режем только компоненты,
|
||||||
|
# СЛИШКОМ РАСТЯНУТЫЕ по глубине, чтобы быть предметом: человек
|
||||||
|
# занимает по дальности десятки сантиметров, кромка платформы и
|
||||||
|
# стена — десятки метров. Мерой служит разброс дальности внутри
|
||||||
|
# компоненты (две свёртки по меткам, доли миллисекунды).
|
||||||
|
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,
|
labels, n = split_by_figure(labels, n, tf.r, fig, max_depth=max_depth,
|
||||||
thr=split_gap, col_reach=dilate_cols + 1,
|
thr=split_gap, col_reach=dilate_cols + 1,
|
||||||
top=split_top)
|
top=split_top)
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,12 @@ class Params:
|
||||||
# вытаскивает предмет из компоненты, растёкшейся вдоль стены круглого
|
# вытаскивает предмет из компоненты, растёкшейся вдоль стены круглого
|
||||||
# тоннеля, где покадровый тракт слеп целиком. 0 — выключено.
|
# тоннеля, где покадровый тракт слеп целиком. 0 — выключено.
|
||||||
split_gap: float = 6.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 # сколько фигур выносить из одной компоненты
|
split_top: int = 1 # сколько фигур выносить из одной компоненты
|
||||||
# Накопление слабых улик в координатах пути (см. fan_body.py). Заменяет
|
# Накопление слабых улик в координатах пути (см. fan_body.py). Заменяет
|
||||||
# недоступный на большой дальности контраст к фону: на 140…185 м `gap`
|
# недоступный на большой дальности контраст к фону: на 140…185 м `gap`
|
||||||
|
|
@ -317,6 +322,7 @@ class FlyGuard:
|
||||||
split_adv=self.p.split_adv,
|
split_adv=self.p.split_adv,
|
||||||
split_gap=self.p.split_gap,
|
split_gap=self.p.split_gap,
|
||||||
split_near=self.p.split_near,
|
split_near=self.p.split_near,
|
||||||
|
near_long=self.p.near_long,
|
||||||
split_top=self.p.split_top, advance=advance,
|
split_top=self.p.split_top, advance=advance,
|
||||||
d_min=self.p.d_min,
|
d_min=self.p.d_min,
|
||||||
d_max=self.p.d_max, min_rays=self.p.min_rays,
|
d_max=self.p.d_max, min_rays=self.p.min_rays,
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,8 @@ def main() -> None:
|
||||||
help="с какой дальности порог начинает падать, м")
|
help="с какой дальности порог начинает падать, м")
|
||||||
ap.add_argument("--novelty-floor", type=float, default=None,
|
ap.add_argument("--novelty-floor", type=float, default=None,
|
||||||
help="ниже этой новизны трек не считается")
|
help="ниже этой новизны трек не считается")
|
||||||
|
ap.add_argument("--near-long", type=float, default=None,
|
||||||
|
help="разброс дальности, выше которого компоненту режут и вблизи; 0 — не резать вблизи")
|
||||||
ap.add_argument("--min-rays", type=int, default=None,
|
ap.add_argument("--min-rays", type=int, default=None,
|
||||||
help="сколько лучей минимум образуют кандидата")
|
help="сколько лучей минимум образуют кандидата")
|
||||||
ap.add_argument("--min-rays-far", type=int, default=None,
|
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
|
over["warn_far_from"] = args.warn_far_from
|
||||||
if args.novelty_floor is not None:
|
if args.novelty_floor is not None:
|
||||||
over["novelty_floor"] = args.novelty_floor
|
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:
|
if args.min_rays is not None:
|
||||||
over["min_rays"] = args.min_rays
|
over["min_rays"] = args.min_rays
|
||||||
if args.min_rays_far is not None:
|
if args.min_rays_far is not None:
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,8 @@ def main() -> None:
|
||||||
help="с какой дальности порог начинает падать, м")
|
help="с какой дальности порог начинает падать, м")
|
||||||
ap.add_argument("--novelty-floor", type=float, default=None,
|
ap.add_argument("--novelty-floor", type=float, default=None,
|
||||||
help="ниже этой новизны трек не считается")
|
help="ниже этой новизны трек не считается")
|
||||||
|
ap.add_argument("--near-long", type=float, default=None,
|
||||||
|
help="разброс дальности, выше которого компоненту режут и вблизи; 0 — не резать вблизи")
|
||||||
ap.add_argument("--min-rays", type=int, default=None,
|
ap.add_argument("--min-rays", type=int, default=None,
|
||||||
help="сколько лучей минимум образуют кандидата")
|
help="сколько лучей минимум образуют кандидата")
|
||||||
ap.add_argument("--min-rays-far", type=int, default=None,
|
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
|
over["warn_far_from"] = args.warn_far_from
|
||||||
if args.novelty_floor is not None:
|
if args.novelty_floor is not None:
|
||||||
over["novelty_floor"] = args.novelty_floor
|
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:
|
if args.min_rays is not None:
|
||||||
over["min_rays"] = args.min_rays
|
over["min_rays"] = args.min_rays
|
||||||
if args.min_rays_far is not None:
|
if args.min_rays_far is not None:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue