I have the following Python code to generate random circles in order to simulate Brownian motion. I need to find the total area of the small red circles so that I can compare it to the total area of a larger blue circle. Since the circles are generated randomly, many of them overlap making it difficult to find the area. I have read many other responses related to this question about pixel painting, etc. What is the best way to find the area of these circles? I do not want to modify the generation of the circles, I just need to find the total area of the red circles on the plot.
The code to generate the circles I need is as follows (Python v. 2.7.6):
import matplotlib.pyplot as plt
import numpy as np
new_line = []
new_angle = []
x_c = [0]
y_c = [0]
x_real = []
y_real = []
xy_dist = []
circ = []
range_value = 101
for x in range(0,range_value):
mu, sigma = 0, 1
new_line = np.random.normal(mu, sigma, 1)
new_angle = np.random.uniform(0, 360)*np.pi/180
x_c.append(new_line*np.cos(new_angle))
y_c.append(new_line*np.sin(new_angle))
x_real = np.cumsum(x_c)
y_real = np.cumsum(y_c)
a = np.mean(x_real)
b = np.mean(y_real)
i = 0
while i<=range_value:
xy_dist.append(np.sqrt((x_real[i]-a)**2+(y_real[i]-b)**2))
i += 1
circ_rad = max(xy_dist)
small_rad = 0.2
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
circ1 = plt.Circle((a,b), radius=circ_rad+small_rad, color='b')
ax.add_patch(circ1)
j = 0
while j<=range_value:
circ = plt.Circle((x_real[j], y_real[j]), radius=small_rad, color='r', fill=True)
ax.add_patch(circ)
j += 1
plt.axis('auto')
plt.show()
The package Shapely might be of some use:
https://gis.stackexchange.com/questions/11987/polygon-overlay-with-shapely
http://toblerity.org/shapely/manual.html#geometric-objects
I can think of an easy way to do it thought the result will have inaccuracies:
With Python draw all your circles on a white image, filling the circles as you draw them. At the end each "pixel" of your image will have one of 2 colors: white color is the background and the other color (let's say red) means that pixel is occupied by a circle.
You then need to sum the number of red pixels and multiply them by the scale with which you draw them. You will have then the area.
This is inaccurate as there is no way of drawing a circle using square pixels, so in the mapping you lose accuracy. Keep in mind that the bigger you draw the circles, the smaller the inaccuracy becomes.
Related
How do I randomly fill a given rectangular of bottom left corner (0, 0) and top right corner (350,250) with Ellipses of random sizes without the Ellipses overlapping each other using NumPy?
and get the data of the Ellipses placed as center point, radius1 and radius2.
I saw this method but didn't got much
How to randomly fill a region with non-overlapping rectangles using NumPy?
What I tried is mentioned below I have generated the radius of ellipses required but no idea how to place them all in the rectangle without overlapping.
import numpy as np
import math
import random
Area_of_quad = 350 * 250
Af_ell = 0.4 # Vol Fraction of Ellipses
Area_of_Ell = Af_ell*Area_of_quad
Ell_range_r1 = np.array([20,12.5,10])
Ell_ragne_r2 = np.array([0.6,0.7,0.8,0.9])
Area_of_ell_each = Area_of_Ell/len(Ell_range_r1)
rad = []
for i in range(len(Ell_range_r1)):
area = Area_of_ell_each
while area >= 0.01*Area_of_ell_each:
r1 = Ell_range_r1[i]
r2 = random.choice(Ell_ragne_r2)*r1
Ar_ac = math.pi*r1*r2
area -= Ar_ac
rad.append([r1,r2])
Is there an algorithm that can assign, get indexes or operate on an array giving a batch of angles, origins and values without any loops, non-differentiability or performance heavy computation.
Finding a function that operates on angles in a 3 dimensional space [W,H,D,C] is much more appreciated.
The following basically does what you want. Notice that it does actually contain a loop but not of the type one should avoid. The loop has only 4 iterations so it is not a performance problem. What one should avoid with numpy/pytorch is looping over all entries in a large array.
img = torch.zeros([9,9,3])
points = np.stack(np.indices([9,9])).reshape(2,-1)
blue = (0,0,1)
red = (1,0,0)
orange = (1,1/2,0)
green = (0,1,0)
angles = [0,45,225,280]
origions = np.array([(4,4),(4,4),(4,4),(4,4)])
colors = [blue, red, orange, green]
def angle_from(p):
return np.rad2deg(np.arctan2(*(points-p.reshape(2,1)))) % 360
def set_color(angle, origin, color):
angles = angle_from(np.array(origin))
mask = angles - angle == 0
img.view(-1,3)[np.where(mask),:] = torch.tensor(color, dtype=torch.float)
for angle, origin, color in zip(angles,origions,colors):
set_color(angle, origin, color)
rad = np.deg2rad(angle)
x = (origin[0],origin[1]+10*np.cos(rad))
y = (origin[0],origin[1]+10*np.sin(rad))
plt.plot(x, y, c='white')
angle = 280
rad = np.deg2rad(angle)
plt.scatter(*points)
plt.imshow(img, origin='lower')
Seems almost perfect except that you cheated with the green area. As you can see in the picture the centers of the green squares are not actually on the ray with the angle you claim it is. I plotted the centers and rays with the origin and angle you chose so one can see that more easily. I suspect that is often going to be the case and you want a way to choose the green spots. My approach there was to pick not one but two rays and show the squares with their centers being between the rays.
def set_color_between_rays(angles, origins, color):
angles1 = angle_from(np.array(origins[0]))
angles2 = angle_from(np.array(origins[1]))
mask = ((angles1 - angles[0]) >= 0) & ((angles2 - angles[1]) <= 0)
img.view(-1,3)[np.where(mask),:] = torch.tensor(color, dtype=torch.float)
angle = 295
rad = np.deg2rad(angle)
origins = np.array([[3.6,3.6],[4.3,4.3]])
plt.plot((origins[0,0],origins[0,0]+10*np.cos(rad)),(origins[0,1],origins[0,1]+10*np.sin(rad)),c='green')
plt.plot((origins[1,0],origins[1,0]+10*np.cos(rad)),(origins[1,1],origins[1,1]+10*np.sin(rad)),c='green')
set_color_between_rays([angle, angle], origins, green)
plt.scatter(*points)
plt.imshow(img, origin='lower')
According to this blog post, there are several ways to make voronoi cells a bit more dynamic looking. The one that I'm interested in is the first one that they mentioned:
The above illustration is the same Voronoi diagram as the one above, only now, Perlin noise has been used to distort which points belong to which cell. This creates some more interesting borders between between cells.
This is kind of easy to accomplish as long as you use a pixel-by-pixel (or tile-by-tile) assignment to the closest voronoi origin, since you can quite simply offset the actual coordinates of the pixel by Perlin noise - quite closely related to distorted Perlin noise.
I have seen similar ideas mentioned elsewhere, but not actual code showing how the perlin noise is "distorted" or "added" to the voronoi diagram. I've tried applying it through guessing, but have had no luck. The way my code is written, the distance between points is in the hundreds, while the perlin noise value is only from 0 to 1, so adding or subtracting the noise really doesn't do much. Multiplying just seems to break the voronoi. I've tried scaling the voronoi distance values to be between 0 and 1 or -1 to 1 and then applying to noise, but that didn't work either.
Below is an example of the voronoi diagram and perlin noise I am generating. I would appreciate any feedback or ability to point me in the right direction.
from PIL import Image
import random
import math
import numpy as np
import noise
wid = 500
hei = 250
image = Image.new("RGB",(wid,hei))
world_test = np.zeros(image.size)
scale = 100 # Number that determines at what distance to view the noisemap
octaves = 6 # the number of levels of detail you want you perlin noise to have
persistence = 0.5 # number that determines how much detail is added or removed at each octave (adjusts frequency)
lacunarity = 2.0 # number that determines how much each octave contributes to the overall shape (adjusts amplitude)
# Creates perlin noise
for x in range(wid):
for y in range(hei):
world_test[x][y] = ((noise.pnoise2(x/100,
y/100,
octaves = octaves,
persistence = persistence,
lacunarity = lacunarity,
repeatx = wid,
repeaty = hei,
base = 0)))
def generate_voronoi_diagram(width, height, num_cells):
image = Image.new("RGB", (width, height))
putpixel = image.putpixel
imgx, imgy = image.size
nx = []
ny = []
nr = []
ng = []
nb = []
#Go through number of cells
for i in range(num_cells):
#creat a point (x,y) and give it a specific color value
nx.append(random.randrange(imgx))
ny.append(random.randrange(imgy))
nr.append(random.randrange(256))
ng.append(random.randrange(256))
nb.append(random.randrange(256))
#go through each pixel in the image
for y in range(int(imgy)):
for x in range(int(imgx)):
dmin = math.hypot(imgx-1, imgy-1)
j = -1
#go through each cell
for i in range(num_cells):
# d is distance from each voronoi starting point
d = math.hypot((nx[i]-x), (ny[i]-y))
# apply perlin distort to d
d += world_test[x][y]
#if distance is less than the current min distance,
#set that point as the owner of this pixel and the new dmin
if d < dmin:
dmin = d
j = i
putpixel((x, y), (nr[j], ng[j], nb[j]))
image.save("Voronoi_example.png", "PNG")
image.show()
generate_voronoi_diagram(wid, hei, 30)
I think I found a solution to this question.
If you create separated noise maps for each coordinate(x and y) and add the value of that noise maps to each pixel during the calculation of d this value will get distorted by the maps.
I think I found this answer on this site but i'm not sure:
https://gamedev.stackexchange.com/questions/182582/how-to-distort-an-image-using-perlin-noise
Based on this site I modified your python code and it now should work.
from PIL import Image
import random
import math
import numpy as np
import noise
wid = 100
hei = 100
image = Image.new("RGB",(wid,hei))
world_test_x = np.zeros(image.size)
world_test_y = np.zeros(image.size)
scale = 0.1 # Number that determines at what distance to view the noisemap
octaves = 6 # the number of levels of detail you want you perlin noise to have
persistence = 0.5 # number that determines how much detail is added or removed at each octave (adjusts frequency)
lacunarity = 2.0 # number that determines how much each octave contributes to the overall shape (adjusts amplitude)
seed = 19829813472
mult = 50 # Strenght
# Creates perlin noise to distort x coordinates
for x in range(wid):
for y in range(hei):
world_test_x[x][y] = ((noise.pnoise2(x/100,
y/100,
octaves = octaves,
persistence = persistence,
lacunarity = lacunarity,
repeatx = wid,
repeaty = hei,
base = 0)))*mult
# Creates perlin noise to distort y coordinates
for x in range(wid):
for y in range(hei):
world_test_y[x][y] = ((noise.pnoise2((x+seed)/100,
(y+seed)/100,
octaves = octaves,
persistence = persistence,
lacunarity = lacunarity,
repeatx = wid,
repeaty = hei,
base = 0)))*mult
def generate_voronoi_diagram(width, height, num_cells):
image = Image.new("RGB", (width, height))
putpixel = image.putpixel
imgx, imgy = image.size
nx = []
ny = []
nr = []
ng = []
nb = []
nsize=[]
#Go through number of cells
for i in range(num_cells):
#creat a point (x,y) and give it a specific color value
nx.append(random.randrange(imgx))
ny.append(random.randrange(imgy))
nr.append(random.randrange(256))
ng.append(random.randrange(256))
nb.append(random.randrange(256))
nsize.append(0)
#go through each pixel in the image
for y in range(int(imgy)):
for x in range(int(imgx)):
dmin = math.hypot(imgx-1, imgy-1)
j = -1
#go through each cell
for i in range(num_cells):
# d is distance from each voronoi starting point
# each point gets its coordinates distorted so d also gets distorted
d = math.hypot((nx[i]-x+world_test_x[x][y]), (ny[i]-y+world_test_y[x][y]))
#if distance is less than the current min distance,
#set that point as the owner of this pixel and the new dmin
if d < dmin:
dmin = d
j = i
nsize[j]+=1
putpixel((x, y), (nr[j], ng[j], nb[j]))
image.save("Voronoi_example.png", "PNG")
image.show()
print(nsize)
print(nr)
generate_voronoi_diagram(wid, hei, 8)
Here is the Voronoi map
Here is the distorted Voronoi map
I have to draw a triangle in Python using mathplotlib.
This is how it should eventually look like:
My objective is, once drawn the triangle, to plot some points on it.
At the moment I can draw the triangle just fine:
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
ax.add_patch(Polygon([[0,0],[0,1],[1,0]], closed=True,fill=True))
ax.set_xlim((0,1))
ax.set_ylim((0,1))
plt.show()
But I can only fill it with a solid color. How do I add a gradient like shown in the picture?
Can some one help me?
There is an example on the matplotlib page showing how to use a clip path for an image.
Adapting this to your case would give this:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.path import Path
from matplotlib.patches import PathPatch
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
path = Path([[0,0],[0,1],[1,0],[0,0]])
patch = PathPatch(path, facecolor='none')
ax.add_patch(patch)
Z, Z2 = np.meshgrid(np.linspace(0,1), np.linspace(0,1))
im = plt.imshow(Z-Z2, interpolation='bilinear', cmap=plt.cm.RdYlGn,
origin='lower', extent=[0, 1, 0, 1],
clip_path=patch, clip_on=True)
im.set_clip_path(patch)
ax.set_xlim((0,1))
ax.set_ylim((0,1))
plt.show()
In response to the comment by Stücke, here is an example of a rotation (in degrees) of a 2-colour pattern for an arbitrary closed geometry:
MWE
This is an example for a geometry with a 2 colour pattern rotated 10 degrees counter clock wise (ccw).
def create_gradient_rectangle():
"""Creates a gradient in arbitrary direction in the shape of a
rectangle."""
fig = plt.figure()
ax = fig.add_subplot(111, aspect="equal")
path = Path([[1, 1], [3, 1], [3, 5], [1, 6], [1, 1]])
patch = PathPatch(path, facecolor="none")
ax.add_patch(patch)
# Create a grid that specifies the grid pattern from 0 to 1 for
# red to blue. Resolution = 50 pixels
resolution = 400
arr = np.zeros([resolution, resolution])
for row in range(resolution):
for col in range(resolution):
arr[row][col] = row / resolution
# TODO: verify the entries start at 0 for the first rows
# Ensure the matrix can be plotted at once.
np.set_printoptions(threshold=np.inf)
np.set_printoptions(linewidth=2000) # default = 75
# Rotate the colour gradient matrix.
angle_ccw_deg = -10 # degrees
arr = rotate(arr, angle=angle_ccw_deg)
if angle_ccw_deg > 90 or angle_ccw_deg < -90:
raise Exception(
"Rotation error too large, swap the colour pattern instead please."
)
# Trim the rotated matrix to remove blank triangles that are generated.
colour_resolution = 4 # 10^5=10.000 different colours.
rounded_flipped_arr = np.flip(np.around(arr, colour_resolution), axis=1)
arr = trim_rotated_square(rounded_flipped_arr, resolution, angle_ccw_deg)
im = plt.imshow(
arr,
interpolation="bilinear",
origin="lower",
cmap=plt.cm.RdYlGn,
extent=[1, 3, 1, 6],
clip_path=patch,
clip_on=True,
)
im.set_clip_path(patch)
ax.set_xlim((0, 10))
ax.set_ylim((0, 10))
plt.show()
plt.cla()
plt.clf()
plt.close()
def trim_rotated_square(arr, resolution, angle_ccw_deg):
"""Removes the right and left sides of the colour gradient matrix because
it contains triangles on which the pattern is not extended due to the
rotation.
:param arr: The original rotated and rounded array.
:param resolution: The resolution of the colour gradient pattern/original
unrotated matrix size.
:param angle_ccw_deg: The angle at which the pattern is rotated.
"""
# Assumes the rotated matrix is a square matrix.
width = arr.shape[0]
# If the rotation is to the ccw, then the top right triangle will move up
# into the edge of the larger matrix that encapsulates the rotated matrix.
if angle_ccw_deg < 0:
# Get the most right column on which the pattern is uninterrupted.
max_col = get_max_col(arr, resolution)
# Get the most left column on which the pattern is uninterrupted.
min_col = width - max_col
# If the rotation is to the cw, then the top left triangle will move up
# into the edge of the larger matrix that encapsulates the rotated matrix.
elif angle_ccw_deg > 0:
# Get the most left column on which the pattern is uninterrupted.
min_col = get_max_col(arr, resolution)
# Get the most right column on which the pattern is uninterrupted.
max_col = width - min_col
cut = arr[:, min_col:max_col]
return cut
def get_max_col(arr, resolution):
"""Returns the maximum column number for which the rotated matrix shows an
uninterrupted pattern.
:param arr: The original rotated and rounded array.
:param resolution: The resolution of the colour gradient pattern/original
unrotated matrix size.
"""
# Loop through the rows from top to bottom until the rotated left or right
# edge is encountered.
for row in range(resolution):
# Scan the columns horizontally until an edge is encountered. Assumes
# the matrix stars with zeros on top for the first colour pattern.
for col in range(resolution):
# Since the matrix is rounded to some digits, the first 0.000x will
# be rounded down to 0, and when the colour value becomes larger,
# it will exceed 0, this indicates the first top edge is found.
# Print the arr to see how this happens.
if arr[row][col] > 0:
# Return the column for which the rotated edge is found.
print(f"row={row},col={col},arr[row][col]={arr[row][col]}")
return col
raise Exception("Did not find rotated corner.")
It yields:
whereas rotating it -10 degrees ccw yields:
Inefficiency
It is quite in-efficient as it first rounds the gradient pattern to some number of variables, then rotates the square matrix by putting it into a larger square matrix, and then I start looping through the larger rotated matrix to find the first top edge position. Then I trim the sides of the larger matrix again to ensure a matrix is returned in which the pattern is propagated completely, instead of with missing triangles.
Recommendation
I have not yet tried multi colour patterns. And it currently only supports a single line as colour pattern. However, if one computes the cut off position for the rotation using sinesoids, the rotation angle and the lenght of the original matrix, then one could do the cut off regardless of the colour pattern.
I have a binary black and white images that looks like this
I want to fill in those white circles to be solid white disks. How can I do this in Python, preferrably using skimage?
You can detect circles with skimage's methods hough_circle and hough_circle_peaks and then draw over them to "fill" them.
In the following example most of the code is doing "hierarchy" computation for the best fitting circles to avoid drawing circles which are one inside another:
# skimage version 0.14.0
import math
import numpy as np
import matplotlib.pyplot as plt
from skimage import color
from skimage.io import imread
from skimage.transform import hough_circle, hough_circle_peaks
from skimage.feature import canny
from skimage.draw import circle
from skimage.util import img_as_ubyte
INPUT_IMAGE = 'circles.png' # input image name
BEST_COUNT = 6 # how many circles to draw
MIN_RADIUS = 20 # min radius should be bigger than noise
MAX_RADIUS = 60 # max radius of circles to be detected (in pixels)
LARGER_THRESH = 1.2 # circle is considered significantly larger than another one if its radius is at least so much bigger
OVERLAP_THRESH = 0.1 # circles are considered overlapping if this part of the smaller circle is overlapping
def circle_overlap_percent(centers_distance, radius1, radius2):
'''
Calculating the percentage area overlap between circles
See Gist for comments:
https://gist.github.com/amakukha/5019bfd4694304d85c617df0ca123854
'''
R, r = max(radius1, radius2), min(radius1, radius2)
if centers_distance >= R + r:
return 0.0
elif R >= centers_distance + r:
return 1.0
R2, r2 = R**2, r**2
x1 = (centers_distance**2 - R2 + r2 )/(2*centers_distance)
x2 = abs(centers_distance - x1)
y = math.sqrt(R2 - x1**2)
a1 = R2 * math.atan2(y, x1) - x1*y
if x1 <= centers_distance:
a2 = r2 * math.atan2(y, x2) - x2*y
else:
a2 = math.pi * r2 - a2
overlap_area = a1 + a2
return overlap_area / (math.pi * r2)
def circle_overlap(c1, c2):
d = math.sqrt((c1[0]-c2[0])**2 + (c1[1]-c2[1])**2)
return circle_overlap_percent(d, c1[2], c2[2])
def inner_circle(cs, c, thresh):
'''Is circle `c` is "inside" one of the `cs` circles?'''
for dc in cs:
# if new circle is larger than existing -> it's not inside
if c[2] > dc[2]*LARGER_THRESH: continue
# if new circle is smaller than existing one...
if circle_overlap(dc, c)>thresh:
# ...and there is a significant overlap -> it's inner circle
return True
return False
# Load picture and detect edges
image = imread(INPUT_IMAGE, 1)
image = img_as_ubyte(image)
edges = canny(image, sigma=3, low_threshold=10, high_threshold=50)
# Detect circles of specific radii
hough_radii = np.arange(MIN_RADIUS, MAX_RADIUS, 2)
hough_res = hough_circle(edges, hough_radii)
# Select the most prominent circles (in order from best to worst)
accums, cx, cy, radii = hough_circle_peaks(hough_res, hough_radii)
# Determine BEST_COUNT circles to be drawn
drawn_circles = []
for crcl in zip(cy, cx, radii):
# Do not draw circles if they are mostly inside better fitting ones
if not inner_circle(drawn_circles, crcl, OVERLAP_THRESH):
# A good circle found: exclude smaller circles it covers
i = 0
while i<len(drawn_circles):
if circle_overlap(crcl, drawn_circles[i]) > OVERLAP_THRESH:
t = drawn_circles.pop(i)
else:
i += 1
# Remember the new circle
drawn_circles.append(crcl)
# Stop after have found more circles than needed
if len(drawn_circles)>BEST_COUNT:
break
drawn_circles = drawn_circles[:BEST_COUNT]
# Actually draw circles
colors = [(250, 0, 0), (0, 250, 0), (0, 0, 250)]
colors += [(200, 200, 0), (0, 200, 200), (200, 0, 200)]
fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(10, 4))
image = color.gray2rgb(image)
for center_y, center_x, radius in drawn_circles:
circy, circx = circle(center_y, center_x, radius, image.shape)
color = colors.pop(0)
image[circy, circx] = color
colors.append(color)
ax.imshow(image, cmap=plt.cm.gray)
plt.show()
Result:
Do a morphological closing (explanation) to fill those tiny gaps, to complete the circles. Then fill the resulting binary image.
Code :
from skimage import io
from skimage.morphology import binary_closing, disk
import scipy.ndimage as nd
import matplotlib.pyplot as plt
# Read image, binarize
I = io.imread("FillHoles.png")
bwI =I[:,:,1] > 0
fig=plt.figure(figsize=(24, 8))
# Original image
fig.add_subplot(1,3,1)
plt.imshow(bwI, cmap='gray')
# Dilate -> Erode. You might not want to use a disk in this case,
# more asymmetric structuring elements might work better
strel = disk(4)
I_closed = binary_closing(bwI, strel)
# Closed image
fig.add_subplot(1,3,2)
plt.imshow(I_closed, cmap='gray')
I_closed_filled = nd.morphology.binary_fill_holes(I_closed)
# Filled image
fig.add_subplot(1,3,3)
plt.imshow(I_closed_filled, cmap='gray')
Result :
Note how the segmentation trash has melded to your object on the lower right and the small cape on the lower part of the middle object has been closed. You might want to continue with an morphological erosion or opening after this.
EDIT: Long response to comments below
The disk(4) was just the example I used to produce the results seen in the image. You will need to find a suitable value yourself. Too big of a value will lead to small objects being melded into bigger objects near them, like on the right side cluster in the image. It will also close gaps between objects, whether you want it or not. Too small of a value will lead to the algorithm failing to complete the circles, so the filling operation will then fail.
Morphological erosion will erase a structuring element sized zone from the borders of the objects. Morphological opening is the inverse operation of closing, so instead of dilate->erode it will do erode->dilate. The net effect of opening is that all objects and capes smaller than the structuring element will vanish. If you do it after filling then the large objects will stay relatively the same. Ideally it should remove a lot of the segmentation artifacts caused by the morphological closing I used in the code example, which might or might not be pertinent to you based on your application.
I don't know skimage but if you'd use OpenCv, I would do a Hough transform for circles, and then just draw them over.
Hough Transform is robust, if there are some small holes in the circles that is no problem.
Something like:
circles = cv2.HoughCircles(gray, cv2.cv.CV_HOUGH_GRADIENT, 1.2, 100)
# ensure at least some circles were found
if circles is not None:
# convert the (x, y) coordinates and radius of the circles to integers
circles = np.round(circles[0, :]).astype("int")
# loop over the (x, y) coordinates and radius of the circles
# you can check size etc here.
for (x, y, r) in circles:
# draw the circle in the output image
# you can fill here.
cv2.circle(output, (x, y), r, (0, 255, 0), 4)
# show the output image
cv2.imshow("output", np.hstack([image, output]))
cv2.waitKey(0)
See more info here: https://www.pyimagesearch.com/2014/07/21/detecting-circles-images-using-opencv-hough-circles/