Based on stackoverflow question at http://stackoverflow.com/questions/33709384/calculating-the-scores-using-matlab
This is my walk through the data.
I assume that the OP already has code to separate shots into binary masks for holes, and go from there.
%matplotlib inline
# tell interpreter to "plot in the webviewer not in separate window"
from matplotlib import pyplot as plt
# python plotting library
import numpy as np
# math related
from scipy import ndimage
# N-Dimensional image processing, pretty generic (and very cool)
img_path = "/Path/To/File" # first image given of a binary mask
img = ndimage.imread(img_path,flatten=True) # load in grayscale not RGB
plt.imshow(img,"gray")
plt.colorbar()
<matplotlib.colorbar.Colorbar instance at 0x000000000A3A5A88>
img.shape # image dimension
(386L, 390L)
mask = img > np.max(img)//2
# binary mask from the grayscale image, thresholding at half image maximum
plt.imshow(mask,"gray")
<matplotlib.image.AxesImage at 0xe511d68>
distance = ndimage.distance_transform_edt(mask)
# compute the distance transform of mask to help locate center
plt.imshow(distance,"hot")
plt.colorbar()
<matplotlib.colorbar.Colorbar instance at 0x000000000E999648>
centroids_mask = distance == ndimage.maximum_filter(distance,size=10)
# binary image with True for each local maximum of distance transform = centroid
position = np.where(centroids_mask * (mask > 0))
# get positions where centroids mask is True
plt.imshow(img,"gray")
plt.scatter(position[1],position[0])
<matplotlib.collections.PathCollection at 0xeda8828>
The score sheet is a circle. This means we need to convert a 2D cartesian coordinate into a polar coordinate system centered around image center. The score is the distance from circle center.
from math import atan2
def cartesian2polar(position,center):
"""Transforms a 2D cartesian position into a recentered distance,angle couple"""
pos_offset = np.array([p - c for p,c in zip(position,center)])
norm = np.linalg.norm(pos_offset) # norm of the position
unit = pos_offset / norm
theta = atan2(unit[1],unit[0])
return norm,theta
img_center = [i//2 for i in img.shape]
cartesian2polar(position,img_center)
# distance to center , angle in radians
(40.049968789001575, 1.5208379310729538)
It says the given point is 40 pixels off the center. It visually makes sense. Let's confirm on a known point
print cartesian2polar([0,0],img_center)
# testing on the top left corner of image
(274.36107595648474, -2.3510398966698043)
print np.linalg.norm(img_center)
# actual distance from top left corner to center of image
274.361075956
Perfect ! the top left corner of the image is correctly found ~ 275 px off the center.
We want now to estimate the score of a given point
src = "/Path/To/File2" # an image of full target
img2 = ndimage.imread(src,flatten=True)
plt.imshow(img2,"gray")
fig = plt.gcf()
fig.set_size_inches(18.5, 10.5) # SUPERSIZE IMAGE display
# "Zooming" on horizontal score limits
plt.imshow(img2[200:400,...],"gray")
fig = plt.gcf()
fig.set_size_inches(10,10)
Looking at points 3 to 6 on the left (starting at 100 pixels till 200), we can estimate that 30px is the horizontal distance between circles.
Assuming that border = 30 :
We can try the following scoring function:
border = 30
def score_function(distance):
""" Returns gunrange score based on pixel distance to center"""
return max(10 - (distance // border), 0) # avoid negative score
img_center2 = [i//2 for i in img2.shape]
Let's visualize these borders
plt.imshow(img2,"gray")
for points in range(11):
circle = plt.Circle(img_center2, radius=points*border,fc="None",ec="g")
# create an "abstract circle"
plt.gca().add_patch(circle)
# attach this circle to the specific image
fig = plt.gcf()
fig.set_size_inches(10,10) # make plot bigger
Well the 30 px border is a bit off, let's adjust with 32.
border = 32
plt.imshow(img2,"gray")
for points in range(11):
circle = plt.Circle(img_center2, radius=points*border,fc="None",ec="g")
plt.gca().add_patch(circle)
fig = plt.gcf()
fig.set_size_inches(10,10)
Great ! Now let's try with random shots to assess the decision function !
n = 10 # how many gunshots we estimate
sigma = 3 * border # the variance of the normal distribution = 10-3 = 7 score.
# This should mean that ~67% of shots are better than 7 points
samples = np.random.normal(img_center2,sigma,(n,2))
plt.imshow(img2,"gray")
plt.scatter(samples[:,1],samples[:,0],color="r")
fig = plt.gcf()
fig.set_size_inches(10,10)
Let's try the decision function on these and label the graph with it
polar = [cartesian2polar(i,img_center2) for i in samples]
# from 2D points to polar coordinates
score = [score_function(d) for d,theta in polar]
# from distances to score
plt.imshow(img2,"gray")
plt.scatter(samples[:,1],samples[:,0],color="r")
fig = plt.gcf()
fig.set_size_inches(10,10)
for label, x, y in zip(score, samples[:, 1], samples[:, 0]):
plt.annotate(
label,
xy = (x, y), xytext = (20, 20),
textcoords = 'offset points', ha = 'right', va = 'bottom',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))
# complicated code that just draws a pretty label around holes
Thank you for following this fun walkthrough !
You can find more experiments like this on my github profile at http://gist.github.com/OverkillGuy