From 2dc400ab78cf21a09cb8afa08ef77bafeadebcd5 Mon Sep 17 00:00:00 2001
From: Igor Ryabchikov <i.a.ryabchikov@gmail.com>
Date: Sun, 17 Apr 2022 13:49:29 +0300
Subject: [PATCH] Abandoned bag model

---
 abandoned_bag_model.pl | 311 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 311 insertions(+)
 create mode 100644 abandoned_bag_model.pl

diff --git a/abandoned_bag_model.pl b/abandoned_bag_model.pl
new file mode 100644
index 0000000..343f1cf
--- /dev/null
+++ b/abandoned_bag_model.pl
@@ -0,0 +1,311 @@
+% Модель опирается на следующие атомарные знания, загржаемые для каждого кадра видеозаписи на осное детектированных признаков:
+% - timeSlice(Time) - задается для каждого кадра, Time - время кадра в секундах (в примере предполагается, что видеозапись
+%  содержит только 1 кадр всекунду);
+% - person(Person, Time) - задается для каждого человека, детектированного на кадре. Person - используется для обозначения
+%  человека между разными кадрами; Time - время кадра в  секундах.
+% - bag(Bag, Time) - задается для каждой детектированной сумки, по аналогии с person(Person, Time).
+% - bounds(Object, box(TopLeftX, TopLeftY, BottomRightX, BottomRightY), Time) - обрамляющая рамка детектированного
+%  объекта - в данном примере сумки или человека. Ось X идет слева направо, Y - сверху вниз. Координаты измеряются в пикселях.
+% - position(Object, point(X, Y, Z), Time) - оценка положения объекта (человека или сумки) на поверхности земли, определенного
+%  единственной точкой. Ось X - идет слева направо, Y - сверху вниз, Z - ось глубины. Координаты измеряются в миллиметрах.
+%  Данные оценки справедливы только если объект касается земли, поэтому в случае сумки при использовании данных координат
+%  необходимо удостовериться, что сумка была положена на землю.
+
+% Определяет оставленную без присмотра сумку и время ее размещения. Описание детектируемой ситуации на естественном языке:
+% Среди тех людей, кто был поблизости (в пределах 2 метров) сумки, когда она была оставлена,
+% нет человека, который не отошел от сумки более чем на 10 секунд. (т.е. все потенциальные влдельцы отходили от сумки на длительное время)
+% Считается, что сумка была оставлена в данный момент, если за прошедшие 3 секунды она была перемещена таким рбразом, что
+% площадь пересечения обрамляющих рамок стала меньше 70% площади объединения рамок, а в следующие 3 секунды - не перемещалась.
+% Считается, что человек отошел от сумки, если расстояние между ним и сумкой превысило 2 метра.
+devBeh(abandonedBag(Bag), StartTime, FinishTime) :-
+	bagWasPlaced(Bag, PlacementTime),
+	StartTime is PlacementTime - 3000,
+	FinishTime is PlacementTime + 10000,
+	timeSlice(StartTime),
+	timeSlice(FinishTime),
+	bagConditionMet(Bag, PlacementTime, FinishTime, true),
+	\+ (possibleOwnerWhoDidntLeaveForMoreThan10Seconds(_PossibleOwner, Bag, PlacementTime)),
+	\+ (possibleOwnerWhoDidntNotIntersectForMoreThan10Seconds(_PossibleOwnerIntersect, Bag, PlacementTime)).
+
+% Определяет время, в которое сумка была размещена. Считаем, что сумка была размещена
+% в данный момент времени, если в течение прошедших 3 секунд она существенно сместилась,
+% а за последующие 3 секунд - нет.
+bagWasPlaced(Bag, Time) :-
+	% В качестве базовой точки с целью оптимизации выбираем именно bag, а не correctedBag
+	deduplicatedBag(Bag, Time),
+	PrevTime is Time - 3000, % отнимаем 3 сек
+	FutureTime is Time + 3000,
+	timeSlice(PrevTime),
+	timeSlice(FutureTime),
+	bagNotMoved(Bag, Time, FutureTime),
+	\+ (bagNotMoved(Bag, PrevTime, Time)).
+
+bagNotMoved(Bag, StartTime, FinishTime) :-
+	\+ (timeBetween(StartTime, FinishTime, Time), \+ (correctedBag(Bag, Time))),
+	\+ (timeBetween(StartTime, FinishTime, Time), moved(Bag, StartTime, Time)).
+
+% Определяет, передвинулся ли объект Obj между временем T1 и T2. Считается, что объект передвинулся,
+% если площадь пересечения обрамляющих рамок объекта в T1 и T2 меньше 70% площади объединения рамок.
+% T1 ^ T2 < 0.7 * T1 v T2 ?
+moved(Obj, T1, T2) :-
+	correctedBounds(Obj, BoxT1, T1),
+	correctedBounds(Obj, BoxT2, T2),
+	boxIntersection(BoxT1, BoxT2, BoxInt),
+	area(BoxT1, AreaT1),
+	area(BoxT2, AreaT2),
+	area(BoxInt, AreaInt),
+	AreaInt < (AreaT1 + AreaT2 - AreaInt) * 0.7.
+
+
+possibleOwnerWhoDidntLeaveForMoreThan10Seconds(PossibleOwner, Bag, PlacementTime) :-
+	PlacementStartTime is PlacementTime - 3000,
+	timeBetween(PlacementStartTime, PlacementTime, Time),
+	wasNear(PossibleOwner, Bag, Time),
+	\+ (intervalWhenPersonLeftForMoreThen10Seconds(PossibleOwner, Bag, Time, _DepartureTime, _DepartureFinalTime)).
+
+% Определяет человека, который находился поблизости объекта (в радиусе 2х метров)
+% в определенный момент времени.
+wasNear(Person, Obj, Time) :-
+	person(Person, Time),
+	correctedPosition(Person, PersonP, Time),
+	correctedPosition(Obj, ObjP, Time),
+	distanceBetweenPoints(PersonP, ObjP, Dist),
+	Dist =< 2000. % расстояние в миллиметрах
+
+% Определяет наличие интервала времени длиной более 10 сек, где человек отошел от сумки на более чем 2 метра.
+intervalWhenPersonLeftForMoreThen10Seconds(PossibleOwner, Bag, PlacementTime, DepartureTime, DepartureFinalTime) :-
+	timeStep(TimeStep),
+	wasNear(PossibleOwner, Bag, PreDepartureTime),
+	PreDepartureTime >= PlacementTime,
+	DepartureTime is PreDepartureTime + TimeStep, % со следующей секунды проверяем отсутствие
+	%\+ (wasNear(PossibleOwner, Bag, DepartureTime)), % оптимизация
+	DepartureFinalTime is DepartureTime + 10000,
+	timeSlice(DepartureFinalTime), % проверяет, что тест кейс содержит данные за требуемый интервал времени
+	wasNotNearBetween(PossibleOwner, Bag, DepartureTime, DepartureFinalTime).
+
+% Определяет отсутствие человека возле объекта в течение заданного интервала времени.
+wasNotNearBetween(Person, Bag, StartTime, EndTime) :-
+	bagConditionMet(Bag, StartTime, EndTime, true),
+	bagConditionMet(Bag, StartTime, EndTime, wasntNear(Person)).
+%	\+ (timeBetween(StartTime, EndTime, BetweenTime), \+ (correctedBounds(Obj, _Box, BetweenTime))),
+%	\+ (timeBetween(StartTime, EndTime, BetweenTime), wasNear(Person, Obj, BetweenTime)).
+
+
+possibleOwnerWhoDidntNotIntersectForMoreThan10Seconds(PossibleOwner, Bag, PlacementTime) :-
+	PlacementStartTime is PlacementTime - 3000,
+	timeBetween(PlacementStartTime, PlacementTime, Time),
+	intersects(PossibleOwner, Bag, Time),
+	\+ (intervalWhenPersonDidntIntersectForMoreThen10Seconds(PossibleOwner, Bag, Time, _DepartureTime, _DepartureFinalTime)).
+
+% Определяет человека, который находился поблизости объекта (в радиусе 2х метров)
+% в определенный момент времени.
+intersects(Person, Obj, Time) :-
+	person(Person, Time),
+	correctedBounds(Person, PersonBox, Time),
+	correctedBounds(Obj, ObjBox, Time),
+	boxIntersection(PersonBox, ObjBox, BoxInt),
+	area(BoxInt, AreaInt),
+	AreaInt > 0.01.
+
+% Определяет наличие интервала времени длиной более 10 сек, где человек не пересекает рамку сумки.
+intervalWhenPersonDidntIntersectForMoreThen10Seconds(PossibleOwner, Bag, PlacementTime, DepartureTime, DepartureFinalTime) :-
+	timeStep(TimeStep),
+	intersects(PossibleOwner, Bag, PreDepartureTime),
+	PreDepartureTime >= PlacementTime,
+	DepartureTime is PreDepartureTime + TimeStep, % со следующей секунды проверяем отсутствие
+	%\+ (intersects(PossibleOwner, Bag, DepartureTime)), % оптимизация
+	DepartureFinalTime is DepartureTime + 10000,
+	timeSlice(DepartureFinalTime), % проверяет, что тест кейс содержит данные за требуемый интервал времени
+	doesNotIntersectBetween(PossibleOwner, Bag, DepartureTime, DepartureFinalTime).
+
+% Определяет отсутствие человека возле объекта в течение заданного интервала времени.
+doesNotIntersectBetween(Person, Bag, StartTime, EndTime) :-
+	bagConditionMet(Bag, StartTime, EndTime, true),
+	bagConditionMet(Bag, StartTime, EndTime, notIntersects(Person)).
+%	\+ (timeBetween(StartTime, EndTime, BetweenTime), \+ (correctedBounds(Obj, _Box, BetweenTime))),
+%	\+ (timeBetween(StartTime, EndTime, BetweenTime), intersects(Person, Obj, BetweenTime)).
+
+% Утилитарные предикаты
+
+% Возвращает обрамляющую рамку, полученную пересечением двух других. Если рамки не пересекаются,
+% тогда либо XInt1 будет больше XInt2, либо YInt1 будет больше YInt2, либо оба.
+boxIntersection(box(Xf1, Yf1, Xf2, Yf2), box(Xs1, Ys1, Xs2, Ys2), box(XInt1, YInt1, XInt2, YInt2)) :-
+	XInt1 is max(Xf1, Xs1),
+	YInt1 is max(Yf1, Ys1),
+	XInt2 is min(Xf2, Xs2),
+	YInt2 is min(Yf2, Ys2).
+
+% Вычисляет площадь обрамляющей рамки. X1, Y1, X2, Y2 должны быть заданы.
+area(box(X1, Y1, X2, Y2), Area) :-
+	X1 < X2,
+	Y1 < Y2,
+	Area is (X2 - X1) * (Y2 - Y1),
+	!.
+area(box(_, _, _, _), 0).
+
+% Определяет время Time, находящееся между StartTime и FinishTime включительно.
+timeBetween(StartTime, FinishTime, Time) :-
+	StartTime =< FinishTime,
+	MaxTimeInt is FinishTime - StartTime,
+	timeStep(TimeStep),
+	iter(0, TimeStep, MaxTimeInt, TimeInt),
+	Time is StartTime + TimeInt,
+	timeSlice(Time).
+
+timeBetweenBackwards(StartTime, FinishTime, Time) :-
+	StartTime =< FinishTime,
+	MaxTimeInt is FinishTime - StartTime,
+	timeStep(TimeStep),
+	iter(0, TimeStep, MaxTimeInt, TimeInt),
+	Time is FinishTime - TimeInt,
+	timeSlice(Time).
+
+% Итерируется от MinIter до MaxIter включительно с шагом Step.
+iter(MinIter, _Step, MaxIter, _Iter) :-
+	MinIter > MaxIter,
+	!, fail.
+iter(MinIter, _Step, _MaxIter, MinIter).
+iter(MinIter, Step, MaxIter, Iter) :-
+	NextMinIter is MinIter + Step,
+	iter(NextMinIter, Step, MaxIter, Iter).
+
+
+vectorOf(point(X1, Y1, Z1), point(X2, Y2, Z2), vector(VX, VY, VZ)) :-
+	VX is X2-X1,
+	VY is Y2-Y1,
+	VZ is Z2-Z1.
+
+vectorLength(vector(X, Y, Z), Length) :-
+	Length is sqrt(X**2 + Y**2 + Z**2).
+
+distanceBetweenPoints(P1, P2, Dist) :-
+	vectorOf(P1, P2, V),
+	vectorLength(V, Dist).
+
+
+% Детектирование сумки зависит от детектирования человека. Если человек поставил сумку и отошел, детектор может посчитать, что
+% объект не является сумков. Для учета этой особенности считаем, что при исчезновении сумки, если на последнем кадре она пересекала
+% обрамляющую рамку человека не более чем на 15% от площади своей обрамляющей рамки, сумка оставалась на своем месте
+% в течение 15 секунд, до тех пор, пока обрамляющая рамка этой же сумки не была детектирована, либо пока 
+% предполагаемая обрамляющая рамка сумки не была пересечена рамкой какого-либо другого человека более чем на 15% от площади
+% рамки сумки.
+correctedBag(Bag, Time) :-
+	deduplicatedBag(Bag, Time).
+
+correctedBag(Bag, Time) :-
+	correctedBagInternal(Bag, Time, _Box, _PrevTime).
+
+correctedBagInternal(Bag, Time, Box, PrevTime) :-
+	\+ (var(Bag)),
+	\+ (var(Time)),
+	\+ (deduplicatedBag(Bag, Time)),
+	timeSlice(Time),
+	StartTime is Time - 15000,
+	timeBetweenBackwards(StartTime, Time, PrevTime),
+	deduplicatedBag(Bag, PrevTime),
+	!,
+	bounds(Bag, Box, PrevTime),
+	\+ (atTheEdge(Box)),
+	\+ (timeBetween(PrevTime, Time, TimeBetween), intersectsBoxByPerson(Box, 0.15, TimeBetween)).
+
+bagConditionMet(Bag, StartTime, EndTime, _Condition) :-
+	\+ (var(Bag)),
+	StartTime > EndTime,
+	!.
+bagConditionMet(Bag, StartTime, EndTime, Condition) :-
+	\+ (var(Bag)),
+	\+ (var(EndTime)),
+	\+ (var(StartTime)),
+	deduplicatedBag(Bag, EndTime),
+	holdsBagCondition(Condition, Bag, EndTime, EndTime),
+	!,
+	timeStep(TimeStep),
+	NewEndTime is EndTime - TimeStep,
+	bagConditionMet(Bag, StartTime, NewEndTime, Condition).
+bagConditionMet(Bag, StartTime, EndTime, Condition) :-
+	\+ (var(Bag)),
+	timeSlice(EndTime),
+	timeStep(TimeStep),
+	StartSearchTime is EndTime - 15000,
+	timeBetweenBackwards(StartSearchTime, EndTime, Time),
+	deduplicatedBag(Bag, Time),
+	!,
+	bounds(Bag, Box, Time),
+	\+ (atTheEdge(Box)),
+	\+ (timeBetween(Time, EndTime, TimeBetween), intersectsBoxByPerson(Box, 0.15, TimeBetween)),
+	St is max(Time, StartTime),
+	\+ (timeBetween(St, EndTime, TimeBetween), \+ (holdsBagCondition(Condition, Bag, Time, TimeBetween))),
+	NewEndTime is Time - TimeStep,
+	bagConditionMet(Bag, StartTime, NewEndTime, Condition).
+
+
+holdsBagCondition(true, _Bag, _BagTime, _Time).
+
+holdsBagCondition(wasntNear(Person), Bag, BagTime, Time) :-
+	\+ (person(Person, Time),
+		position(Person, PersonP, Time),
+		position(Bag, ObjP, BagTime),
+		distanceBetweenPoints(PersonP, ObjP, Dist),
+		Dist =< 2000). % расстояние в миллиметрах
+
+holdsBagCondition(notIntersects(Person), Bag, BagTime, Time) :-
+	\+ (person(Person, Time),
+		bounds(Person, PersonBox, Time),
+		bounds(Bag, ObjBox, BagTime),
+		boxIntersection(PersonBox, ObjBox, BoxInt),
+		area(BoxInt, AreaInt),
+		AreaInt > 0.01).
+
+atTheEdge(box(TLx, TLy, BRx, BRy)) :-
+	frameHeight(FrameHeight),
+	frameWidth(FrameWidth),
+	(TLx < 60 ; TLy < 10 ; BRx > FrameWidth - 60 ; BRy > FrameHeight - 10).
+
+% Определяем утверждения holdsAt
+holdsAt(box(Bag), Time) :-
+	box(Bag, Time).
+
+holdsAt(intersectsBoxByPerson(Box, IntersecPart), Time) :-
+	intersectsBoxByPerson(Box, IntersecPart, Time).
+
+
+correctedBounds(Obj, Box, Time) :-
+	bounds(Obj, Box, Time).
+
+correctedBounds(Bag, Box, Time) :-
+	correctedBagInternal(Bag, Time, Box, _PrevTime).
+
+correctedPosition(Obj, Point, Time) :-
+	position(Obj, Point, Time).
+
+correctedPosition(Bag, Point, Time) :-
+	correctedBagInternal(Bag, Time, _Box, PrevTime),
+	position(Bag, Point, PrevTime).
+
+% Определяет, есть ли человек, который пересекает предоставленную обрамляющую рамку более чем на IntersecPart от площади этой
+% обрамляющей рамки.
+intersectsBoxByPerson(Box, IntersecPart, Time) :-
+	person(Person, Time),
+	bounds(Person, PersonBox, Time),
+	boxIntersection(Box, PersonBox, BoxInt),
+	area(Box, BoxArea),
+	area(BoxInt, AreaInt),
+	AreaInt >= BoxArea * IntersecPart.
+
+
+deduplicatedBag(Bag, Time) :-
+	bag(Bag, Time),
+	bounds(Bag, Box, Time),
+	\+ (bag(OtherBag, Time),
+		OtherBag @< Bag,
+		bounds(OtherBag, OtherBox, Time),
+		boxIntersection(Box, OtherBox, BoxInt),
+		area(Box, BoxArea),
+		area(OtherBox, OtherBoxArea),
+		area(BoxInt, BoxIntArea),
+		BoxIntArea > (BoxArea + OtherBoxArea - BoxIntArea) * 0.8).
+
+
+
+
+
-- 
GitLab