Skip to content

Commit

Permalink
improve merge guesser on long segments and self-overlaps
Browse files Browse the repository at this point in the history
  • Loading branch information
breunigs committed Feb 11, 2025
1 parent cd7de2b commit 96bf8b4
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 1 deletion.
27 changes: 26 additions & 1 deletion lib_cli/joiner/gps_tracks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ defmodule Joiner.GpsTracks do
if(s, do: [s | o], else: o)
|> merge_overlaps(opts)
|> Enum.reject(&(&1.duration_ms < opts.fade_duration_ms))
|> Enum.flat_map(&split_long_segments(&1, opts))
|> Enum.sort_by(& &1.duration_ms, :desc)
end

Expand Down Expand Up @@ -46,7 +47,8 @@ defmodule Joiner.GpsTracks do
Enum.reduce(with_bearing(poly1), [], fn p1, matches ->
Enum.reduce(with_bearing(poly2), matches, fn p2, matches ->
# i.e. find all matching points
if within_dist?(p1, p2, opts) && within_bearing?(p1, p2, opts) do
if within_dist?(p1, p2, opts) && within_bearing?(p1, p2, opts) &&
valid_self_overlap?(segment, p1, p2, opts) do
[{p1, p2} | matches]
else
matches
Expand Down Expand Up @@ -81,6 +83,29 @@ defmodule Joiner.GpsTracks do
end)
end

defp split_long_segments(segment, opts)

defp split_long_segments(%{duration_ms: dur} = seg, opts)
when dur < opts.geo_max_segment_length_ms,
do: [seg]

defp split_long_segments(segment, opts) do
with {:ok, f1, f2} <- Joiner.Video.split(segment.from),
{:ok, t1, t2} <- Joiner.Video.split(segment.to),
{:ok, s1} <- Joiner.Segment.new(f1, t1),
{:ok, s2} <- Joiner.Segment.new(f2, t2) do
Enum.flat_map([s1, s2], &split_long_segments(&1, opts))
else
{:error, _reason} -> segment
end
end

defp valid_self_overlap?(%{from: x, to: x}, p1, p2, opts) do
abs(p1.time_offset_ms - p2.time_offset_ms) > opts.geo_min_time_diff_self_join_ms
end

defp valid_self_overlap?(_segment, _p1, _p2, _opts), do: true

@spec merge_overlaps([Joiner.Segment.t()], Joiner.Options.t()) :: [Joiner.Segment.t()]
defp merge_overlaps(segments, opts, merged \\ [])
defp merge_overlaps([], _opts, merged), do: merged
Expand Down
13 changes: 13 additions & 0 deletions lib_cli/joiner/options.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ defmodule Joiner.Options do
fade_duration_ms: non_neg_integer(),
geo_max_dist_m: float(),
geo_max_bearing_deg: float(),
geo_min_time_diff_self_join_ms: non_neg_integer(),
geo_max_segment_length_ms: non_neg_integer(),
visual_image_height: non_neg_integer(),
visual_compare_metric: Joiner.FfmpegMetrics.metric(),
visual_prune_below: float(),
Expand All @@ -29,6 +31,8 @@ defmodule Joiner.Options do
:fade_duration_ms,
:geo_max_dist_m,
:geo_max_bearing_deg,
:geo_min_time_diff_self_join_ms,
:geo_max_segment_length_ms,
:visual_image_height,
:visual_compare_metric,
:visual_prune_below,
Expand Down Expand Up @@ -59,6 +63,15 @@ defmodule Joiner.Options do
# like a +. The lower, the more closely the tracks must share the same
# direction.
geo_max_bearing_deg: 25.0,
# How many milliseconds the track needs to be apart to be considered for
# self joins. Should be large enough so that the previous or next few
# frames are not considered to be overlapping, but short enough to not
# exclude "quick fixes" like a double U-turn.
geo_min_time_diff_self_join_ms: 10_000,
# How long a segment is allowed to be before being split up into multiple
# segments. This avoids a long segment comparing the start of video1 with
# the end of video2, even if those two are very far apart.
geo_max_segment_length_ms: 3_000,

### visual candidate search
# To conserve computing power, videos will be downscaled to this size
Expand Down
16 changes: 16 additions & 0 deletions lib_cli/joiner/video.ex
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ defmodule Joiner.Video do
%{v1 | start: start, stop: stop, polyline: polyline}
end

@spec split(t()) :: {:ok, t(), t()} | {:error, binary()}
def split(video) when length(video.polyline) < 2, do: {:error, "polyline too short"}

def split(video) do
half = round(duration_ms(video) / 2 + video.start.time_offset_ms)
# TODO: reduce instead so "prev" is reversed?
{prev, next} = Enum.split_while(video.polyline, fn pt -> pt.time_offset_ms < half end)
mid = interpol(List.last(prev), hd(next), half)

{
:ok,
%{video | stop: mid, polyline: prev ++ [mid]},
%{video | start: mid, polyline: [mid | next]}
}
end

@spec overlap?(t(), t()) :: boolean()
def overlap?(v1, v2) when v1.source != v2.source, do: false

Expand Down

0 comments on commit 96bf8b4

Please sign in to comment.