Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mergre feature/bottle cap locations to main #6

Merged
merged 14 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Germany_Beer_Map Project

This Project is a Python-based project focused on efficiently associating beer bottle caps with their corresponding locations on a wooden map of Germanyd esigned for displaying beer caps which was given to me as a present. Using computer vision techniques, it accurately identifies the positions of the holes in the wooden map from a photograph.
This Project is a Python-based project focused on efficiently associating beer bottle caps with their corresponding locations on a wooden map of Germany designed for displaying beer caps which was given to me as a present. Using computer vision techniques, it accurately identifies the positions of the holes in the wooden map from a photograph.

The core functionality involves creating a correlation between the beer bottle caps and their physical locations on the map. This is achieved by matching the list of beer producers with the detected holes. The optimisation process aims to minimize the spatial distance between each hole and its respective geographic reference on the map.
The core functionality involves creating a correlation between the beer bottle caps and their physical locations on the map. This is achieved by matching the list of beer producers with the detected holes. The optimisation process aims to minimize the cumulativethe spatial distance between each hole on the map (its respective geographic reference) and bottle cap placement.

Key Features:

- Computer vision for precise detection of beer bottle cap locations on a wooden map.
- A thin-spline transformation carried out on the wooden map onto a reference outline of Germany.
- Geospatial correlation to establish links between hole cutouts for the bottle caps and their real-world geographic references.
- Optimisation algorithm to minimise the spatial distance between bottle cap placement and their brewery locations.
- Optimisation algorithm (Hungarian algorithm) to minimise the spatial distance between bottle cap placement and their brewery locations.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions germany_beer_map/data/mapping/bottle_caps.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name, brand, brewery_address, image
Störtebeker, Störtebeker, "Greifswalder Chaussee 84, 18439 Stralsund, Germany", stoertebeker.png
Veltins Pilsner, Veltins, "Brauereistrasse 2, 59872 Meschede-Grevenstein, Germany", veltins.png
Grevensteiner Dunkles, Veltins, "Brauereistrasse 2, 59872 Meschede-Grevenstein, Germany", grevensteiner.png
Krombacher Pils, Krombacher, "Hagener Straße 261, 57223 Kreuztal, Germany", krombacher.png
Tegernseer Bier, Herzoglich Bayerisches Brauhaus Tegernsee, "Schlossplatz 1, 83684 Tegernsee, Germany", tegernseer.png
Franziskaner Weissbier, Spaten-Franziskaner-Bräu, "Marsstraße 46-48, 80335 München, Germany", franziskaner.png
Erdinger Dunkel, Erdinger Weissbräu, "Lange Zeile 1-3, 85435 Erding, Germany", erdinger.png
Früh Kolsch, Cölner Hofbräu Früh, "Robert-Bosch-Straße 15-17, 50769 Köln, Germany", frueh.png
Bamberger Schwärzla, Klosterbräu Bamberg, "Obere Mühlbrücke 1, 96049 Bamberg, Germany", klosterbraeu.png
Strecks Bio Helles, Strecks Brauhaus,"Ludwig-Jahn-Straße 11, 97645 Ostheim vor der Rhön, Germany", strecks.png
Bayreuther Hell, Bayreuther Bierbrauerei, "Kulmbacher Straße 1, 95445 Bayreuth, Germany", bayreuther.png
Paulaner Hefe-Weissbier, Paulaner Brauerei, "Hochstraße 75, 81541 München, Germany", paulaner.png
39 changes: 39 additions & 0 deletions germany_beer_map/src/algorithms/geocode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import requests
import numpy as np

def get_geocoordinates(address):
"""
Get the geographic coordinates of an address from OpenStreetMap's Nominatim service.

Parameters:
address (str): The address to geocode.

Returns:
tuple: The latitude and longitude of the address, or None if the geocoding request failed.
"""
response = requests.get(
'https://nominatim.openstreetmap.org/search',
params={'q': address, 'format': 'json'}
)

data = response.json()

if data:
return float(data[0]['lon']), float(data[0]['lat'])

return None

def convert_address_to_coords(csvfile):
# Read in bottle_caps.csv
bottle_caps = np.genfromtxt(csvfile, delimiter=",", dtype=str, skip_header=1)

bottle_cap_coords = []

# bottle_cap_coords = [(13.0944453, 54.2907393), (11.591350844509808, 53.37741685), (12.459549961499999, 50.52140085), (7.956500987110655, 50.9910225), (9.653526, 51.4176975), (11.553636633022338, 48.14608575), (11.9069408, 48.3068168), (6.654346047066047, 51.83582915), (10.8870087, 49.889238199999994), (10.227481824523348, 50.45433915), (11.606263141955619, 50.1081385), (7.1418535749854755, 51.26109235)]

# Get the geocoordinates of each bottle cap
for i in range(len(bottle_caps)):
bottle_cap = get_geocoordinates(bottle_caps[i][2])
bottle_cap_coords.append(bottle_cap)

return bottle_cap_coords
90 changes: 90 additions & 0 deletions germany_beer_map/src/algorithms/spatial_dist_min.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import numpy as np
from scipy.optimize import linear_sum_assignment
from math import radians, sin, cos, sqrt, atan2

import matplotlib.pyplot as plt

def haversine(lat1, lon1, lat2, lon2):
# Convert coordinates to radians
lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])

# Differences
dlon = lon2 - lon1
dlat = lat2 - lat1

# Haversine formula
a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
c = 2 * atan2(sqrt(a), sqrt(1 - a))

# Radius of earth in kilometers (mean radius = 6,371km)
R = 6371.0

# Calculate the distance
distance = R * c

return distance

def spatial_dist_min(points_fixed, points_movable, plotting=False):
"""
spatial_dist_min, calculates the optimal mapping between two sets of geographical points to minimize the total Euclidean distance. The first set of points, referred to as the reference set, represents fixed locations. The second set of points, which will be mapped to the reference set, represents movable locations. The function uses the Hungarian Algorithm to find the optimal one-to-one correspondence between the two sets that results in the smallest cumulative distance. The output is a list of index pairs representing the optimal assignments and the minimum total distance.

Parameters:
points_fixed (list): A list of tuples, where each tuple contains the longitude and latitude of a fixed point (holes in map).
points_movable (list): A list of tuples, where each tuple contains the longitude and latitude of a movable point (bottle caps/brewery locations)).

Returns:
float: The minimum spatial distance between the points.
"""
# Create a cost matrix
cost_matrix = [[haversine(lat1, lon1, lat2, lon2) for (lon2, lat2) in points_fixed] for (lon1, lat1) in points_movable]

# If there are fewer movable points than fixed holes, pad the cost matrix with zeros
if len(points_movable) < len(points_fixed):
padding = np.zeros((len(points_fixed), len(points_fixed) - len(points_movable)))
cost_matrix = np.hstack((cost_matrix, padding))

# Use the Hungarian Algorithm to find the optimal assignment (specifically, the Jonker-Volgenant variant of the algorithm)
row_ind, col_ind = linear_sum_assignment(cost_matrix)

# Convert cost_matrix to numpy array for advanced indexing
cost_matrix_np = np.array(cost_matrix)

# Calculate the minimum total distance
min_total_distance = cost_matrix_np[row_ind, col_ind].sum()

# Return the optimal assignment and the minimum total distance
if plotting:
visualize_cost_matrix(cost_matrix_np, row_ind, col_ind)

return list(zip(row_ind, col_ind)), min_total_distance


def visualize_cost_matrix(cost_matrix, row_ind, col_ind):
# Create column and row labels
col_labels = ['Movable Point ' + str(i) for i in range(len(cost_matrix[0]))]
row_labels = ['Fixed Hole ' + str(i) for i in range(len(cost_matrix))]

# Convert cost matrix entries to strings with 2 decimal places
formatted_values = [["{:.2f}".format(val) for val in row] for row in cost_matrix]

# Define a scaling factor for the figure size
scale = 0.5

# Create a new figure with width and height set to the corresponding lengths of the table
fig, ax = plt.subplots(figsize=(len(col_labels)*scale, len(row_labels)*scale))

# Hide axes
ax.axis('off')

# Create a table and add it to the figure
table = ax.table(cellText=formatted_values, colLabels=col_labels, rowLabels=row_labels, cellLoc='center', loc='center')

# Auto adjust the width of the columns
table.auto_set_column_width(col=list(range(len(col_labels))))

# Highlight the cells corresponding to the optimal assignment
for i, j in zip(row_ind, col_ind):
table.get_celld()[(i+1, j)].set_facecolor('lightgreen')

# Show the table
plt.show()
12 changes: 7 additions & 5 deletions germany_beer_map/src/algorithms/two_dim_interp.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ def two_dim_interp(centres_of_holes, plotting=False, ref_contour=None, mapping="
# Convert to signed int16 to avoid errors when negating y-values
centres_of_holes_y = centres_of_holes[:, 1].astype(np.int16)

print(centres_of_holes_x)
print(centres_of_holes_y)
# print(centres_of_holes_x)
# print(centres_of_holes_y)

distinctive_points_x = mapping[:, 0]
distinctive_points_y = mapping[:, 1]
distinctive_points_longitude = mapping[:, 3]
distinctive_points_latitude = mapping[:, 2]
distinctive_points_longitude = mapping[:, 3]

circles_interp_longitude = griddata(
(distinctive_points_x, distinctive_points_y),
Expand All @@ -38,7 +38,9 @@ def two_dim_interp(centres_of_holes, plotting=False, ref_contour=None, mapping="
method='cubic'
)

print(circles_interp_latitude)
# print(circles_interp_latitude)

circles_interp = np.column_stack((circles_interp_longitude, circles_interp_latitude))

if plotting:
reference_contour_x = ref_contour[:, 0]
Expand Down Expand Up @@ -114,4 +116,4 @@ def two_dim_interp(centres_of_holes, plotting=False, ref_contour=None, mapping="

plt.show()

return circles_interp_longitude, circles_interp_latitude
return circles_interp
16 changes: 7 additions & 9 deletions germany_beer_map/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
from algorithms.tps_transform import *
from algorithms.two_dim_interp import *
from user_interface.draw_contours import *
from algorithms.geocode import *
from algorithms.spatial_dist_min import spatial_dist_min

# Create a single instance of ImageProcessor
preprocessor_photo = ImageProcessor("germany_beer_map/data/images/map.jpg")
preprocessor_ref = ImageProcessor("germany_beer_map/data/images/map_ref.jpg")



# Use the processed image in both feature detection modules
circles = detect_circles(preprocessor_photo.processed_image)
contour = detect_outline(preprocessor_photo.processed_image)
Expand All @@ -32,21 +33,18 @@
scale_factor = calculate_scale_factor(contour, ref_contour)
ref_contour_scaled = scale_contour(ref_contour, scale_factor) # sqrt of scale factor



ref_contour_scaled_aligned = translate_contour(contour, ref_contour_scaled).reshape(-1, 2)





rotation_angle = find_optimal_rotation(ref_contour_scaled_aligned, contour)
# print(rotation_angle*180/3.16259)

contour_rotated = rotate_contour_around_centroid(contour, -rotation_angle)

two_dim_interp(circles, True, ref_contour_scaled_aligned)
circles_coords = two_dim_interp(circles, True, ref_contour_scaled_aligned)

bottle_cap_coords = convert_address_to_coords("germany_beer_map/data/mapping/bottle_caps.csv")

placements, min_dist = spatial_dist_min(bottle_cap_coords, circles_coords, plotting=True)
print(placements)
exit(0)

# draw_contours(ref_contour_scaled_aligned)
Expand Down