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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (C) 2023, Raspberry Pi Ltd
#
# find_dots.py - Used by CAC algorithm to convert image to set of dots
'''
This file takes the black and white version of the image, along with
the color version. It then located the black dots on the image by
thresholding dark pixels.
In a rather fun way, the algorithm bounces around the thresholded area in a random path
We then use the maximum and minimum of these paths to determine the dot shape and size
This info is then used to return colored dots and locations back to the main file
'''
import numpy as np
import random
from PIL import Image, ImageEnhance, ImageFilter
def find_dots_locations(rgb_image, color_threshold=100, dots_edge_avoid=75, image_edge_avoid=10, search_path_length=500, grid_scan_step_size=10, logfile=open("log.txt", "a+")):
# Initialise some starting variables
pixels = Image.fromarray(rgb_image)
pixels = pixels.convert("L")
enhancer = ImageEnhance.Contrast(pixels)
im_output = enhancer.enhance(1.4)
# We smooth it slightly to make it easier for the dot recognition program to locate the dots
im_output = im_output.filter(ImageFilter.GaussianBlur(radius=2))
bw_image = np.array(im_output)
location = [0, 0]
dots = []
dots_location = []
# the program takes away the edges - we don't want a dot that is half a circle, the
# centroids would all be wrong
for x in range(dots_edge_avoid, len(bw_image) - dots_edge_avoid, grid_scan_step_size):
for y in range(dots_edge_avoid, len(bw_image[0]) - dots_edge_avoid, grid_scan_step_size):
location = [x, y]
scrap_dot = False # A variable used to make sure that this is a valid dot
if (bw_image[location[0], location[1]] < color_threshold) and not (scrap_dot):
heading = "south" # Define a starting direction to move in
coords = []
for i in range(search_path_length): # Creates a path of length `search_path_length`. This turns out to always be enough to work out the rough shape of the dot.
# Now make sure that the thresholded area doesn't come within 10 pixels of the edge of the image, ensures we capture all the CA
if ((image_edge_avoid < location[0] < len(bw_image) - image_edge_avoid) and (image_edge_avoid < location[1] < len(bw_image[0]) - image_edge_avoid)) and not (scrap_dot):
if heading == "south":
if bw_image[location[0] + 1, location[1]] < color_threshold:
# Here, notice it does not go south, but actually goes southeast
# This is crucial in ensuring that we make our way around the majority of the dot
location[0] = location[0] + 1
location[1] = location[1] + 1
heading = "south"
else:
# This happens when we reach a thresholded edge. We now randomly change direction and keep searching
dir = random.randint(1, 2)
if dir == 1:
heading = "west"
if dir == 2:
heading = "east"
if heading == "east":
if bw_image[location[0], location[1] + 1] < color_threshold:
location[1] = location[1] + 1
heading = "east"
else:
dir = random.randint(1, 2)
if dir == 1:
heading = "north"
if dir == 2:
heading = "south"
if heading == "west":
if bw_image[location[0], location[1] - 1] < color_threshold:
location[1] = location[1] - 1
heading = "west"
else:
dir = random.randint(1, 2)
if dir == 1:
heading = "north"
if dir == 2:
heading = "south"
if heading == "north":
if bw_image[location[0] - 1, location[1]] < color_threshold:
location[0] = location[0] - 1
heading = "north"
else:
dir = random.randint(1, 2)
if dir == 1:
heading = "west"
if dir == 2:
heading = "east"
# Log where our particle travels across the dot
coords.append([location[0], location[1]])
else:
scrap_dot = True # We just don't have enough space around the dot, discard this one, and move on
if not scrap_dot:
# get the size of the dot surrounding the dot
x_coords = np.array(coords)[:, 0]
y_coords = np.array(coords)[:, 1]
hsquaresize = max(list(x_coords)) - min(list(x_coords))
vsquaresize = max(list(y_coords)) - min(list(y_coords))
# Create the bounding coordinates of the rectangle surrounding the dot
# Program uses the dotsize + half of the dotsize to ensure we get all that color fringing
extra_space_factor = 0.45
top_left_x = (min(list(x_coords)) - int(hsquaresize * extra_space_factor))
btm_right_x = max(list(x_coords)) + int(hsquaresize * extra_space_factor)
top_left_y = (min(list(y_coords)) - int(vsquaresize * extra_space_factor))
btm_right_y = max(list(y_coords)) + int(vsquaresize * extra_space_factor)
# Overwrite the area of the dot to ensure we don't use it again
bw_image[top_left_x:btm_right_x, top_left_y:btm_right_y] = 255
# Add the color version of the dot to the list to send off, along with some coordinates.
dots.append(rgb_image[top_left_x:btm_right_x, top_left_y:btm_right_y])
dots_location.append([top_left_x, top_left_y])
else:
# Dot was too close to the image border to be useable
pass
return dots, dots_location
|