Dette problemet er nok kanskje ikke riktig så vanskelig å løse som det virker som. Jeg kommer i hvert fall på to ganske enkle løsninger. (Det skal sies at navnene på bildene dine angir en rotasjon jeg ikke helt ser at er konsekvent med noen akse. Så jeg løser denne vinkelen med hensyn på horisontalaksen. Det kan også nevnes at vertikalaksen når vi ser på bildet normalt tenkes på som positiv opp, mens pixlene defineres fra topp og ned, noe som må taes hensyn til i koden.)
Den minst elegante først: Kort og godt roterer du bildet suksessivt, og plukker ut pixlene til pilen ved å se på hvor lys gråfargen er. Deretter lagrer du unna hvor mange lyse pixler du har på linje i x-retning – altså som har samme y-verdi. Når pilen er tilnærmet horisontal, vil denne verdien være størst. For å finne ut om pilen peker mot venstre eller høyre, kan du finne midtpunktet på pilen og se hvilken side av midtpunktet som har flest lyse pixler. Om du ønsker å finne posisjonen til pilspissen, kan du ganske enkelt gjøre det med å plukke ut pilspisskoordinatene fra det roterte bildet (den lyse pixelen lengst til høyre eller venstre, ettersom orienteringen du fant), og regne ut ny (altså opprinnelig) posisjon fra rotasjonsvinkelen du allerede har. Du kan optimalisere med å beskjære bildet rundt pilen, så fremt du er villig til å gjøre et ekstra regneledd om du trenger posisjonen. Her vil det være en avveining av behovet for nøyaktighet og effektivitet: Desto mer finoppløst du gjør roteringen, jo høyere nøyaktighet vil du få på bekostning av prosesseringstid – men se også kommentaren om nøyaktighet nederst i innlegget. Her er et fungerende eksempel jeg skrev uten en slik beskjæringsoptimalisering, og som heller ikke regner tilbake til posisjonen:
Kode
#!/usr/bin/env python
import sys
from PIL import Image
from collections import Counter
def indexes_to_coords(indexes, size):
coords = [(index % size[0], int(index / size[1])) for index in indexes]
return coords
def find_light_pixel_indexes(pixels):
indexes = []
for i, pixel in enumerate(pixels):
if pixel[0] > 200:
indexes.append(i)
return indexes
def find_num_y_aligned_points(points):
y_coords = [p[1] for p in points]
count = Counter(y_coords)
return max(count.values())
def is_pointing_left(points):
x_positions = [c[0] for c in points]
min_x_pos = min(x_positions)
max_x_pos = max(x_positions)
x_midpoint = (min_x_pos + max_x_pos) / 2
n_points_sub_midpoint = len([x for x in x_positions if x < x_midpoint])
return n_points_sub_midpoint > len(x_positions) / 2
def main(image_file):
image = Image.open(image_file)
image.load()
max_aligned_points = 0
for angle in range(0, 360, 5):
rotated_image = image.rotate(360 - angle, expand = 1)
pixels = rotated_image.getdata()
arrow_pixel_indexes = find_light_pixel_indexes(pixels)
arrow_pixel_coords = indexes_to_coords(arrow_pixel_indexes, rotated_image.size)
aligned_points = find_num_y_aligned_points(arrow_pixel_coords)
if aligned_points > max_aligned_points:
max_aligned_points = aligned_points
arrow_angle = angle
best_image_arrow_pixel_coords = arrow_pixel_coords
if is_pointing_left(best_image_arrow_pixel_coords):
arrow_angle += 180
print("Angle: {}".format(arrow_angle))
if __name__ == "__main__":
main(sys.argv[1])
En raskere og mer elegant variant, er å plukke ut pixlene til pilen, finne disse punktenes "convex hull" (det polygon bestående av færrest mulig av punktene dine som omslutter alle de andre punktene), og deretter finne de punktene i dette polygonet som har størst avstand mellom hverandre. Det finnes spesielle algoritmer for å finne denne avstanden, men her vil punktene, eller hjørnene, i polygonet være så få at en enkel sjekk av punktenes avstander er umerkbart tregere, om tregere i det hele tatt. For å finne hvilken side av pilen pilspissen sitter på, kan du måle endepunktenes avstand til polygonhjørnenes gjennomsnittsposisjon. Da har du både vinkel og posisjon gitt. Her har jeg skrevet et eksempel:
Kode
import sys
from PIL import Image
from scipy.spatial import ConvexHull
from math import * # for atan2() and degrees()
import numpy
def sq_distance(point_a, point_b):
dx = point_a[0] - point_b[0]
dy = point_a[1] - point_b[1]
return dx * dx + dy * dy
def get_most_distant_points(points):
farthest_dist = 0
for i, point_a in enumerate(points):
for point_b in points[i+1:]:
sq_dist = sq_distance(point_a, point_b)
if sq_dist > farthest_dist:
farthest_dist = sq_dist
farthest_points = [point_a, point_b]
return farthest_points
def find_angle(points):
point_a, point_b = points
dx = point_b[0] - point_a[0] # Mirrored axis
dy = point_a[1] - point_b[1]
deg = degrees(atan2(dy, dx)) # Against horizontal
while deg < 0: # Force positive
deg += 360
return deg
def indexes_to_coords(indexes, size):
coords = [(index % size[0], int(index / size[1])) for index in indexes]
return coords
def find_light_pixel_indexes(pixels):
indexes = []
for i, pixel in enumerate(pixels):
if pixel[0] > 190:
indexes.append(i)
return indexes
def main(image_file):
image = Image.open(image_file)
pixels = image.getdata()
arrow_pixel_indexes = find_light_pixel_indexes(pixels)
arrow_pixel_coords = indexes_to_coords(arrow_pixel_indexes, image.size)
convex_hull = ConvexHull(arrow_pixel_coords)
vertex_points = convex_hull.points[convex_hull.vertices, :]
most_distant_points = get_most_distant_points(vertex_points)
vertex_centroid = numpy.mean(vertex_points, axis=0)
d0 = sq_distance(vertex_centroid, most_distant_points[0])
d1 = sq_distance(vertex_centroid, most_distant_points[1])
if d0 < d1:
most_distant_points.reverse()
angle = find_angle(most_distant_points)
print "Angle: {}".format(angle)
print "Position: {}".format(most_distant_points[1])
if __name__ == "__main__":
main(sys.argv[1])
Begge disse løsningene vil for øvrig ha en liten unøyaktighet i at den lengste avstanden internt i pilen vil være fra pilspiss til det ene hjørnet av pilens start. Men om du kan leve med det, bør dette funke helt fint.
Sist endret av Provo; 10. september 2016 kl. 11:05.