Тема обширная, европа разношерстная, стран много, людей много, менталитет местами разный.
Перед прочтением этой статьи скажу, что это только начало и даже не верхушка айсберга, а его самая верхняя точка.
Итак, начнем с GEOINT и его основ.
Часовые пояса
- Континент Европа разделен на следующие часовые пояса:
- Часовой пояс Азорских островов (UTC-1);
- Западноевропейский часовой пояс / Часовой пояс по Гринвичу/
- Ирландский часовой пояс (UTC+0);
- Центрально европейское время (UTC+1);
- Восточноевропейское время (UTC+2);
- Калининградский часовой пояс (UTC+2);
- Восточноевропейский часовой пояс и Московский часовой пояс (UTC+3).
Часовой пояс достаточно сильно сужает объем мест поиска.
Так например, если вам попалась фотография с метаданными (что с одной стороны редкость, а с другой реальность), то в ней может быть прописано дата её создания или редактирования. Эта дата обычна указана в формате timestamp - это формат хранения даты и времени с учетом часового пояса.
Кстати, чтобы вы знали, для метаданных есть куча онлайн и офлайн утилит для их просмотра.
К примеру exiftool, вот пример её работы.
Географические особенности
Вот список некоторых из ключевых географических особенностей стран Европы. Каждая страна имеет свои уникальные черты ландшафта, от горных хребтов до озёр, равнин и прибрежных линий. Приведу несколько примеров:
Альпы (Швейцария, Франция, Австрия, Италия) — одни из самых высоких и обширных горных массивов в Европе.
Пиренеи (Испания, Франция, Андорра) — горы, отделяющие Иберийский полуостров от остальной части Европы.
Апеннины (Италия) — горная цепь, проходящая через центральную часть Италии.
Карпаты (Польша, Словакия, Украина, Румыния) — большие горы в Восточной Европе.
Скандинавские горы (Норвегия, Швеция) — массив, протягивающийся через всю Скандинавию.
Плато Месета (Испания) — огромное плато, занимающее центральную часть Испании.
Балканские горы (Болгария) — цепь, простирающаяся на Балканах.
Трансильванские Альпы (Румыния) — одна из частей Карпатских гор.
Пелопоннесский полуостров (Греция) — гористый полуостров на юге Греции.
Ривьерские побережья (Франция, Италия) — известные своим средиземноморским климатом и скалистыми берегами.
И этот список огромен, хочешь заниматься OSINT учи географию, а вообще учи всё... Я хотел этим не большим списком показать, что каждую страну Европы нужно рассматривать в этом плане отдельно, как-нибудь доберемся и до этого.
Разбор небольшого кейса GEOINT Вильнюс
Дана фотография, что будем делать...
Всегда, первое, что нужно делать в такой ситуации - это смотреть МЕТОДАННЫЕ, делается это одной командой, занимает 2-3 секунды, а в итоге может получиться так, что это и есть самое правильное решение, ведь в OSINT решение должно быть не только точным, но и быстрым. Никто не будет ждать полгода пока вы будете искать место по жалкой фотографии, разве что если от этого не зависит судьба человечества или в этом месте лежит куча зелёных бумажек.
Итак, что мы здесь видим...
Фотография сделана в 8 часов утра, часовой пояс +3
Чтобы узнать, в какой полосе сделана фотография можно воспользоваться чем угодно... Ну я сделал это с помощью
Итак, выяснили какой это часовой пояс, что делать дальше...
Но судя по тому, что этот путь кажется сложным и не особо точным эту задачу, давайте смотреть что вообще есть на картинке и как нам ещё сильнее сузить поиск...
Глянем что это деревце растёт...
Клён остролистный (Acer platanoides) широко распространён в различных странах, преимущественно в северных и умеренных широтах. Он растет как в естественных условиях, так и как декоративное дерево в городах и парках. Вот список стран, в которых можно встретить клён остролистный:
Страны, где клён остролистный растёт в естественных условиях:
1. Россия (Европейская часть)
2. Финляндия
3. Швеция
4. Норвегия
5. Дания
6. Эстония
7. Латвия
8. Литва
9. Беларусь
10. Украина
11. Польша
12. Чехия
13. Словакия
14. Германия
15. Австрия
16. Швейцария
17. Франция
18. Великобритания
19. Нидерланды
20. Бельгия
21. Люксембург
22. Италия (Северные регионы)
23. Румыния
24. Болгария
25. Сербия
26. Хорватия
27. Словения
28. Венгрия
Страны, где клён остролистный интродуцирован и часто используется как декоративное дерево:
1. Канада (в основном как декоративное дерево)
2. США (северные штаты, интродуцирован)
3. Новая Зеландия (интродуцирован)
4. Австралия (интродуцирован)
Клён остролистный в ряде стран стал частью городской растительности, особенно в Европе, где его часто используют для озеленения парков, улиц и аллей.
Список стран конечно впечатляет, но мы исключили огромный кусок земли, а именно, теперь мы знаем, что наше место находится в этом районе.
Ну и так далее...
А дальше поиск по картинке, яндекс, гугл, и т.д. пока либо сервисы не закончатся, либо результат не найдём
Поздравляю место считай найдено, переходим на страницу и смотрим, что это и где это...
Поздравляю, место найдено с точностью до нанометра :-)
Но у этой задачи есть и другое решение...
Это решение более рутинное, но с моей точки зрение полезное, т.к. я бы это назвал "Против лома нет приёма".
Когда попадается нам такой объект как этот, фотография со зданием и парковкой, а сервисы гугл maps нам ничего сказать не могут, то на помощь приходит OSM
Openstreepmap
Инструмент opensource, может выполнять очень сложные автоматизированные запросы по поиску места, к примеру здесь в нашем кейсе, я подошел бы с той позицией, что мне надо найти все пятиэтажные здания, у которых есть парковка в пяти метрах от него и есть соседнее пятиэтажное здание, которое находится примерно в 200 метрах от него. Поиск как мы выяснили надо осуществлять по странам Европы, которые находятся в часовом поясе +3, Украину в счет не берём, остаётся не большой выбор Литва, Латвия или Эстония.
Для того чтобы найти это нам нужно сделать запрос в OSM
import requests from geopy.distance import geodesic import folium # URL для запроса к API Overpass overpass_url = "http://overpass-api.de/api/interpreter" # Overpass запрос для Литвы, Латвии и Эстонии overpass_query = """ [out:json][timeout:60]; area[name="Lithuania"]->.searchArea; area[name="Latvia"]->.searchArea2; area[name="Estonia"]->.searchArea3; ( node["building"]["building:levels"="5"](area.searchArea); way["building"]["building:levels"="5"](area.searchArea); relation["building"]["building:levels"="5"](area.searchArea); node["building"]["building:levels"="5"](area.searchArea2); way["building"]["building:levels"="5"](area.searchArea2); relation["building"]["building:levels"="5"](area.searchArea2); node["building"]["building:levels"="5"](area.searchArea3); way["building"]["building:levels"="5"](area.searchArea3); relation["building"]["building:levels"="5"](area.searchArea3); node["amenity"="parking"](area.searchArea); way["amenity"="parking"](area.searchArea); relation["amenity"="parking"](area.searchArea); node["amenity"="parking"](area.searchArea2); way["amenity"="parking"](area.searchArea2); relation["amenity"="parking"](area.searchArea2); node["amenity"="parking"](area.searchArea3); way["amenity"="parking"](area.searchArea3); relation["amenity"="parking"](area.searchArea3); ); out center; """ # Выполняем запрос к Overpass API response = requests.get(overpass_url, params={'data': overpass_query}) # Проверяем статус запроса if response.status_code == 200: data = response.json() else: print(f"Error: {response.status_code}") # Функция для фильтрации зданий с парковкой и соседними зданиями def filter_buildings_with_parking_and_nearby_buildings(data, parking_distance=5, building_distance=200): buildings = [] parkings = [] # Сортируем здания и парковки по типу for element in data['elements']: if "tags" in element and "building" in element["tags"]: buildings.append(element) elif "tags" in element and element["tags"].get("amenity") == "parking": parkings.append(element) valid_buildings = [] # Фильтрация зданий с парковкой в 5 метрах for building in buildings: building_coords = (building['lat'], building['lon']) # Проверка наличия парковки в радиусе 5 метров has_parking = any(geodesic(building_coords, (parking['lat'], parking['lon'])).meters <= parking_distance for parking in parkings) if has_parking: # Проверка наличия соседнего здания на расстоянии 200 метров has_nearby_building = any(geodesic(building_coords, (other_building['lat'], other_building['lon'])).meters <= building_distance for other_building in buildings if other_building != building) if has_nearby_building: valid_buildings.append(building) return valid_buildings # Фильтрация зданий filtered_buildings = filter_buildings_with_parking_and_nearby_buildings(data) # Создание карты с результатами map_center = (55.169438, 23.881275) # Центр Литвы для примера osm_map = folium.Map(location=map_center, zoom_start=7) # Добавление пятиэтажных зданий на карту for building in filtered_buildings: folium.Marker( location=(building['lat'], building['lon']), popup=f"Здание ID: {building['id']}", icon=folium.Icon(color='blue', icon='info-sign') ).add_to(osm_map) # Сохраняем карту в файл osm_map.save("filtered_buildings_map.html") osm_map |
Дальше суть следующая, открываем карту html filtered_buildings_map.html, google map и гуляем по всем улицам по поиске подходящего под описание картинки места.
Может показаться, что точек овер много, больше 5 тысяч, но поверьте, делается это достаточно быстро, если у вас конечно присутствует образное мышление и вы примерно представляете как будет выглядеть место с фотографии на карте, а если у вас топографический кретинизм и отсутствует образное мышление, то придётся делать всё ручками.
Но для ленивых и умных есть более элегантное решение, вы можете сделать скрипт на selenium или Playwright. Суть алгоритма автоматизации в следующем, в радиусе 30 метров от точки найти режим просмотр улиц в google maps, зайти в эту точку просмотреть 360 градусов с углом поворота 45 градусов и при каждом повороте сравнивать картинку google maps с исходной.
Алгоритмы сравнения картинок
1. Сравнение изображений с помощью разности пикселей
Самый простой способ — сравнение двух изображений на уровне пикселей. Этот метод заключается в сравнении значений пикселей на соответствующих позициях. Если изображения идентичны, их разница будет нулевой.
Пример с использованием OpenCV:
import cv2 import numpy as np # Загрузка изображений image1 = cv2.imread('image1.jpg') image2 = cv2.imread('image2.jpg') # Изменение размера изображений (если они разные) image1 = cv2.resize(image1, (image2.shape[1], image2.shape[0])) # Вычисление разницы между изображениями difference = cv2.absdiff(image1, image2) # Преобразуем разницу в серый цвет для облегчения анализа gray_diff = cv2.cvtColor(difference, cv2.COLOR_BGR2GRAY) # Если разница невелика, изображения считаются похожими if np.sum(gray_diff) == 0: print("Изображения идентичны") else: print(f"Изображения различаются. Сумма различий: {np.sum(gray_diff)}") |
2. Алгоритм структурного сходства (SSIM)
Алгоритм SSIM (Structural Similarity Index) — один из самых популярных методов для измерения сходства между двумя изображениями. Он учитывает изменения яркости, контраста и структуры.
Пример использования SSIM с помощью библиотеки scikit-image :
from skimage.metrics import structural_similarity as ssim import cv2 # Загрузка изображений image1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE) image2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE) # Изменение размера изображений (если они разные) image1 = cv2.resize(image1, (image2.shape[1], image2.shape[0])) # Вычисление SSIM score, diff = ssim(image1, image2, full=True) print(f"SSIM: {score}") if score == 1.0: print("Изображения идентичны") else: print("Изображения различаются") |
3. Гистограмма изображений
Сравнение изображений можно производить по гистограмме (распределению цветов). Если два изображения имеют схожие гистограммы, это указывает на визуальное сходство.
Пример с использованием OpenCV:
import cv2 # Загрузка изображений image1 = cv2.imread('image1.jpg') image2 = cv2.imread('image2.jpg') # Преобразование изображений в формат HSV image1_hsv = cv2.cvtColor(image1, cv2.COLOR_BGR2HSV) image2_hsv = cv2.cvtColor(image2, cv2.COLOR_BGR2HSV) # Вычисление гистограммы и нормализация hist1 = cv2.calcHist([image1_hsv], [0, 1], None, [50, 60], [0, 180, 0, 256]) hist2 = cv2.calcHist([image2_hsv], [0, 1], None, [50, 60], [0, 180, 0, 256]) cv2.normalize(hist1, hist1, 0, 1, cv2.NORM_MINMAX) cv2.normalize(hist2, hist2, 0, 1, cv2.NORM_MINMAX) # Сравнение гистограмм с использованием метода корреляции score = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL) print(f"Сходство по гистограмме: {score}") |
4. Feature Matching (сопоставление признаков)
Этот метод основан на выявлении ключевых особенностей (features) изображений и их сопоставлении. Алгоритмы вроде SIFT (Scale-Invariant Feature Transform) или ORB (Oriented FAST and Rotated BRIEF) помогают найти уникальные ключевые точки и сравнить их между изображениями.
Пример с использованием ORB и BFMatcher:
import cv2 # Загрузка изображений image1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE) image2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE) # Использование ORB для нахождения ключевых точек и дескрипторов orb = cv2.ORB_create() keypoints1, descriptors1 = orb.detectAndCompute(image1, None) keypoints2, descriptors2 = orb.detectAndCompute(image2, None) # BFMatcher для сопоставления дескрипторов bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(descriptors1, descriptors2) # Сортировка по расстоянию matches = sorted(matches, key=lambda x: x.distance) # Вывод числа совпадений print(f"Число совпадений: {len(matches)}") # Отрисовка сопоставленных ключевых точек matched_image = cv2.drawMatches(image1, keypoints1, image2, keypoints2, matches[:10], None) cv2.imshow('Matches', matched_image) cv2.waitKey(0) cv2.destroyAllWindows() |
5. Perceptual Hashing (Перцептивное хеширование)
Perceptual hashing — это метод, который генерирует хеш на основе визуального содержимого изображения. Сравнение хешей двух изображений позволяет определить, насколько они похожи.
Пример с использованием библиотеки imagehash :
from PIL import Image import imagehash # Загрузка изображений image1 = Image.open('image1.jpg') image2 = Image.open('image2.jpg') # Генерация перцептивных хешей hash1 = imagehash.average_hash(image1) hash2 = imagehash.average_hash(image2) # Вычисление разницы между хешами hash_diff = hash1 - hash2 print(f"Разница между хешами: {hash_diff}") |
Life-Hack Media в телеграме: