0% found this document useful (0 votes)
45 views

Lab04 Object Segmentation

This document describes functions for image segmentation and object detection. It includes functions to show images, fill holes in masks, perform morphological operations, segment images using masks, resize images, perform k-means clustering segmentation, label segmented objects, and select masks based on minimum and maximum area thresholds. The functions are designed to take an input image and mask, and output a segmented or labeled version of the image for visualization and analysis of objects.

Uploaded by

Dũng Hi
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
45 views

Lab04 Object Segmentation

This document describes functions for image segmentation and object detection. It includes functions to show images, fill holes in masks, perform morphological operations, segment images using masks, resize images, perform k-means clustering segmentation, label segmented objects, and select masks based on minimum and maximum area thresholds. The functions are designed to take an input image and mask, and output a segmented or labeled version of the image for visualization and analysis of objects.

Uploaded by

Dũng Hi
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 29

6/24/2020 Lab04-Object Segmentation

LAB04 - Image Segmentation

Dr. Tran Anh Tuan,

Faculty of Mathematics and Computer Science,

University of Science, HCMC


In [1]: import numpy as np
import cv2
from matplotlib import pyplot as plt
from skimage.color import rgb2gray
from skimage.filters import threshold_otsu
from skimage.measure import label, regionprops
from skimage.segmentation import mark_boundaries
from scipy import ndimage as ndi
import pandas as pd
import json
import os
import timeit
import random

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 1/29


6/24/2020 Lab04-Object Segmentation

In [2]: def ShowImage(ImageList, nRows = 1, nCols = 2, WidthSpace = 0.00, HeightSpace = 0.00):


from matplotlib import pyplot as plt
import matplotlib.gridspec as gridspec

gs = gridspec.GridSpec(nRows, nCols)
gs.update(wspace=WidthSpace, hspace=HeightSpace) # set the spacing between axes.
plt.figure(figsize=(20,20))
for i in range(len(ImageList)):
ax1 = plt.subplot(gs[i])
ax1.set_xticklabels([])
ax1.set_yticklabels([])
ax1.set_aspect('equal')

plt.subplot(nRows, nCols,i+1)

image = ImageList[i].copy()
if (len(image.shape) < 3):
plt.imshow(image, plt.cm.gray)
else:
plt.imshow(image)
plt.title("Image " + str(i))
plt.axis('off')

plt.show()

In [3]: def FillHoles(Mask):


Result = ndi.binary_fill_holes(Mask)
return Result

def morphology(Mask, Size):


from skimage.morphology import erosion, dilation, opening, closing, white_tophat
from skimage.morphology import disk
selem = disk(abs(Size))
if(Size > 0):
result = dilation(Mask, selem)
else:
result = erosion(Mask, selem)
return result

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 2/29


6/24/2020 Lab04-Object Segmentation

In [4]: import os
import pandas as pd

def get_subfiles(dir):
"Get a list of immediate subfiles"
return next(os.walk(dir))[2]

In [5]: def SegmentColorImageByMask(IM, Mask):


Mask = Mask.astype(np.uint8)
result = cv2.bitwise_and(IM, IM, mask = Mask)
return result

In [6]: def SegmentationByOtsu(image, mask):


image_process = image.copy()
image_mask = mask.copy()

image_process[image_mask == 0] = 0
ListPixel = image_process.ravel()
ListPixel = ListPixel[ListPixel > 0]

from skimage.filters import threshold_otsu


otsu_thresh = threshold_otsu(ListPixel)

return otsu_thresh

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 3/29


6/24/2020 Lab04-Object Segmentation

In [7]: def ResizeImage(IM, DesiredWidth, DesiredHeight):


from skimage.transform import rescale, resize

OrigWidth = float(IM.shape[1])
OrigHeight = float(IM.shape[0])
Width = DesiredWidth
Height = DesiredHeight

if((Width == 0) & (Height == 0)):


return IM

if(Width == 0):
Width = int((OrigWidth * Height)/OrigHeight)

if(Height == 0):
Height = int((OrigHeight * Width)/OrigWidth)

dim = (Width, Height)


# print(dim)
resizedIM = cv2.resize(IM, dim, interpolation = cv2.INTER_NEAREST)
# imshows([IM, resizedIM], ["Image", "resizedIM"],1,2)
return resizedIM

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 4/29


6/24/2020 Lab04-Object Segmentation

In [8]: def SegmentByKmeans(image_orig, nClusters = 3):


img = image_orig.copy()
Z = img.reshape((-1,3))

# convert to np.float32
Z = np.float32(Z)

# define criteria, number of clusters(K) and apply kmeans()


criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 1.0)
K = nClusters
ret,labellist,center=cv2.kmeans(Z,K,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)

# Now convert back into uint8, and make original image


center = np.uint8(center)
res = center[labellist.flatten()]
res2 = res.reshape((img.shape))
label2 = labellist.reshape((img.shape[:2]))

image_index = label2
image_kmeans = res2

# Sort to make sure the index is stable


AreaList = []
for idx in range(image_index.max() + 1):
mask = image_index == idx
AreaList.append(mask.sum().sum())

sort_index = np.argsort(AreaList)[::-1]
index = 0
image_index1 = image_index * 0
for idx in sort_index:
image_index1[image_index == idx] = index
index = index + 1

image_index = image_index1.copy()

return image_index, image_kmeans

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 5/29


6/24/2020 Lab04-Object Segmentation

In [9]: def LabelObjectByMask(image_input, image_mask, type = "BBox", color = (0,255,0), thick = 2):
# image_input = image_orig.copy()
image_output = image_input.copy()

label_img = label(image_mask)
regions = regionprops(label_img)
for props in regions:
minr, minc, maxr, maxc = props.bbox
left_top = (minc, minr)
right_bottom = (maxc, maxr)
at_row, at_col = props.centroid

if(type == "Center"):
cv2.drawMarker(image_output, (int(at_col), int(at_row)),color, markerType=cv2.MARKER_STAR, marker
Size=15, thickness= 1, line_type=cv2.LINE_AA)
if(type == "BBox"):
cv2.rectangle(image_output,left_top, right_bottom, color ,thick)

if(type == "Boundary"):
color = [(number / 255) for number in color]
image_mask = morphology(image_mask, 1)
image_output = mark_boundaries(image_output, image_mask, color = color, mode='thick')

if(type == "Fill"):
image_output[image_mask > 0] = color

return image_output

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 6/29


6/24/2020 Lab04-Object Segmentation

In [10]: def SelectMaskByThreshArea(Mask, minArea = 300, maxArea = 100000):


import pandas as pd
from skimage.measure import label, regionprops

mask = Mask.copy()
mask_output = mask * 0
bboxList = []

label_img = label(mask)
regions = regionprops(label_img)
for props in regions:
area = props.area
label = props.label
if((area > minArea) and (area < maxArea)):
mask_output = mask_output + (label_img == label).astype(int)

return mask_output

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 7/29


6/24/2020 Lab04-Object Segmentation

In [11]: def EdgeDetection(image):


import numpy as np
import matplotlib.pyplot as plt

#define the vertical filter


vertical_filter = [[-1,-2,-1], [0,0,0], [1,2,1]]

#define the horizontal filter


horizontal_filter = [[-1,0,1], [-2,0,2], [-1,0,1]]

#read in the pinwheel image


img = image_orig

#get the dimensions of the image


n,m,d = img.shape

#initialize the edges image


edges_img = img.copy()

#loop over all pixels in the image


for row in range(3, n-2):
for col in range(3, m-2):

#create little local 3x3 box


local_pixels = img[row-1:row+2, col-1:col+2, 0]

#apply the vertical filter


vertical_transformed_pixels = vertical_filter*local_pixels
#remap the vertical score
vertical_score = vertical_transformed_pixels.sum()/4

#apply the horizontal filter


horizontal_transformed_pixels = horizontal_filter*local_pixels
#remap the horizontal score
horizontal_score = horizontal_transformed_pixels.sum()/4

#combine the horizontal and vertical scores into a total edge score
edge_score = (vertical_score**2 + horizontal_score**2)**.5

#insert this edge score into the edges image


edges_img[row, col] = [edge_score]*3

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 8/29


6/24/2020 Lab04-Object Segmentation

#remap the values in the 0-1 range in case they went out of bounds
edges_img = edges_img/edges_img.max()
edges_img = (edges_img[:,:,0]*255).astype(int)
return edges_img

In [12]: DataPath = "D:\\MSI DATA (Previous Computer)\\Teaching And Training\\Image Segmentation\\Image Segmentation D
ataSet1\\"

path = DataPath
all_names = get_subfiles(path)
print("Number of Images:", len(all_names))
IMG = []
for i in range(len(all_names)):
tmp = cv2.imread(path + all_names[i])
IMG.append(tmp)

SegDataIMG = IMG.copy()
SegDataName = all_names

Number of Images: 45

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 9/29


6/24/2020 Lab04-Object Segmentation

In [13]: FileName = 'Bill 04.jpg'


idx = SegDataName.index(FileName)
print("Selected Image : ", "\nIndex ", idx, "\nName ", SegDataName[idx])

image = SegDataIMG[idx]
image = ResizeImage(image, DesiredWidth = 300, DesiredHeight = 0)

image_orig = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)


image_gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
image_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
image_ycbcr = cv2.cvtColor(image, cv2.COLOR_BGR2YCR_CB)
ShowImage([image_orig, image_gray, image_hsv, image_ycbcr], 1, 4)

Selected Image :
Index 3
Name Bill 04.jpg

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 10/29


6/24/2020 Lab04-Object Segmentation

In [14]: image_edge = EdgeDetection(image_orig)


image_mask_edge = image_edge > 20
image_mask_fill = FillHoles(image_mask_edge)

s = image_hsv[:,:,1]
image_mask_object = image_mask_fill.copy()
image_mask_object[s > 50] = 0
image_mask_object = FillHoles(image_mask_object)

image_result = LabelObjectByMask(image_orig, image_mask_object, type = "Boundary", color = (0,255,0), thick =


3)

ShowImage([image_orig, image_edge, image_mask_edge, image_mask_fill], 1, 4)


ShowImage([s, image_mask_object, image_result], 1, 4)

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 11/29


6/24/2020 Lab04-Object Segmentation

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 12/29


6/24/2020 Lab04-Object Segmentation

In [15]: FileName = 'Cross 01.jpg'


idx = SegDataName.index(FileName)
print("Selected Image : ", "\nIndex ", idx, "\nName ", SegDataName[idx])

image = SegDataIMG[idx]
image = ResizeImage(image, DesiredWidth = 300, DesiredHeight = 0)

image_orig = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)


image_gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
image_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
image_ycbcr = cv2.cvtColor(image, cv2.COLOR_BGR2YCR_CB)
ShowImage([image_orig, image_gray, image_hsv, image_ycbcr], 1, 4)

Selected Image :
Index 15
Name Cross 01.jpg

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 13/29


6/24/2020 Lab04-Object Segmentation

In [16]: image_edge = EdgeDetection(image_orig)


image_mask_edge = image_edge > 50

image_mask_edge = SelectMaskByThreshArea(image_mask_edge, minArea = 200, maxArea = 300)


image_mask_edge = morphology(image_mask_edge, 1)
image_mask_edge = FillHoles(image_mask_edge)

image_result = LabelObjectByMask(image_orig, image_mask_edge, type = "Boundary", color = (0,255,0), thick = 3


)

ShowImage([image_orig, image_edge, image_mask_edge], 1, 3)


ShowImage([image_result], 1, 2)

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 14/29


6/24/2020 Lab04-Object Segmentation

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 15/29


6/24/2020 Lab04-Object Segmentation

In [23]: FileName = 'TrafficSign 02.jpg'


idx = SegDataName.index(FileName)
print("Selected Image : ", "\nIndex ", idx, "\nName ", SegDataName[idx])

image = SegDataIMG[idx]
image = ResizeImage(image, DesiredWidth = 300, DesiredHeight = 0)

image_orig = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)


image_gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
image_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
image_ycbcr = cv2.cvtColor(image, cv2.COLOR_BGR2YCR_CB)
ShowImage([image_orig, image_gray, image_hsv, image_ycbcr], 1, 4)

Selected Image :
Index 41
Name TrafficSign 02.jpg

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 16/29


6/24/2020 Lab04-Object Segmentation

In [24]: import matplotlib.pyplot as plt


import numpy as np

from skimage.data import astronaut


from skimage.color import rgb2gray
from skimage.filters import sobel
from skimage.segmentation import felzenszwalb, slic, quickshift, watershed
from skimage.segmentation import mark_boundaries
from skimage.util import img_as_float

img = image_orig.copy()

segments_fz = felzenszwalb(img, scale=100, sigma=0.5, min_size=50)


segments_slic = slic(img, n_segments=250, compactness=10, sigma=1)
segments_quick = quickshift(img, kernel_size=3, max_dist=6, ratio=0.5)
gradient = sobel(rgb2gray(img))
segments_watershed = watershed(gradient, markers=250, compactness=0.001)

print(f"Felzenszwalb number of segments: {len(np.unique(segments_fz))}")


print(f"SLIC number of segments: {len(np.unique(segments_slic))}")
print(f"Quickshift number of segments: {len(np.unique(segments_quick))}")

fig, ax = plt.subplots(2, 2, figsize=(10, 10), sharex=True, sharey=True)

ax[0, 0].imshow(mark_boundaries(img, segments_fz))


ax[0, 0].set_title("Felzenszwalbs's method")
ax[0, 1].imshow(mark_boundaries(img, segments_slic))
ax[0, 1].set_title('SLIC')
ax[1, 0].imshow(mark_boundaries(img, segments_quick))
ax[1, 0].set_title('Quickshift')
ax[1, 1].imshow(mark_boundaries(img, segments_watershed))
ax[1, 1].set_title('Compact watershed')

for a in ax.ravel():
a.set_axis_off()

plt.tight_layout()
plt.show()

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 17/29


6/24/2020 Lab04-Object Segmentation

Felzenszwalb number of segments: 173


SLIC number of segments: 177
Quickshift number of segments: 743

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 18/29


6/24/2020 Lab04-Object Segmentation

In [55]: h = image_hsv[:,:,0]
s = image_hsv[:,:,1]
v = image_hsv[:,:,2]

y = image_ycbcr[:,:,0]
cb = image_ycbcr[:,:,1]
cr = image_ycbcr[:,:,2]

ShowImage([h, s, v], 1, 3)
ShowImage([y, cb, cr], 1, 3)
ShowImage([h < 10, (cb > 200)], 1, 2)

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 19/29


6/24/2020 Lab04-Object Segmentation

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 20/29


6/24/2020 Lab04-Object Segmentation

In [60]: image_index = segments_fz.copy()


max_index = image_index.max()
image_mask = image_index * 0

h = image_hsv[:,:,0]
s = image_hsv[:,:,1]
v = image_hsv[:,:,2]

y = image_ycbcr[:,:,0]
cb = image_ycbcr[:,:,1]
cr = image_ycbcr[:,:,2]

for idx in range(max_index):


mask = (image_index == idx)
HValueList = h[mask == 1]
CbValueList = cb[mask == 1]

if((HValueList.mean() < 20) or (CbValueList.mean() > 200)):


image_mask = image_mask + mask

image_result1 = LabelObjectByMask(image_orig, image_mask, type = "Boundary", color = (0,255,0), thick = 3)


image_result2 = LabelObjectByMask(image_orig, image_mask, type = "BBox", color = (0,255,0), thick = 3)
ShowImage([image_index, image_mask], 1, 2)
ShowImage([image_result1, image_result2], 1, 2)

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 21/29


6/24/2020 Lab04-Object Segmentation

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 22/29


6/24/2020 Lab04-Object Segmentation

In [61]: FileName = 'KFC 01.jpg'


idx = SegDataName.index(FileName)
print("Selected Image : ", "\nIndex ", idx, "\nName ", SegDataName[idx])

image = SegDataIMG[idx]
image = ResizeImage(image, DesiredWidth = 300, DesiredHeight = 0)

image_orig = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)


image_gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
image_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
image_ycbcr = cv2.cvtColor(image, cv2.COLOR_BGR2YCR_CB)
ShowImage([image_orig, image_gray, image_hsv, image_ycbcr], 1, 4)

Selected Image :
Index 20
Name KFC 01.jpg

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 23/29


6/24/2020 Lab04-Object Segmentation

In [62]: import matplotlib.pyplot as plt


import numpy as np

from skimage.data import astronaut


from skimage.color import rgb2gray
from skimage.filters import sobel
from skimage.segmentation import felzenszwalb, slic, quickshift, watershed
from skimage.segmentation import mark_boundaries
from skimage.util import img_as_float

img = image_orig.copy()

segments_fz = felzenszwalb(img, scale=100, sigma=0.5, min_size=50)


segments_slic = slic(img, n_segments=250, compactness=10, sigma=1)
segments_quick = quickshift(img, kernel_size=3, max_dist=6, ratio=0.5)
gradient = sobel(rgb2gray(img))
segments_watershed = watershed(gradient, markers=250, compactness=0.001)

print(f"Felzenszwalb number of segments: {len(np.unique(segments_fz))}")


print(f"SLIC number of segments: {len(np.unique(segments_slic))}")
print(f"Quickshift number of segments: {len(np.unique(segments_quick))}")

fig, ax = plt.subplots(2, 2, figsize=(10, 10), sharex=True, sharey=True)

ax[0, 0].imshow(mark_boundaries(img, segments_fz))


ax[0, 0].set_title("Felzenszwalbs's method")
ax[0, 1].imshow(mark_boundaries(img, segments_slic))
ax[0, 1].set_title('SLIC')
ax[1, 0].imshow(mark_boundaries(img, segments_quick))
ax[1, 0].set_title('Quickshift')
ax[1, 1].imshow(mark_boundaries(img, segments_watershed))
ax[1, 1].set_title('Compact watershed')

for a in ax.ravel():
a.set_axis_off()

plt.tight_layout()
plt.show()

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 24/29


6/24/2020 Lab04-Object Segmentation

Felzenszwalb number of segments: 208


SLIC number of segments: 206
Quickshift number of segments: 1098

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 25/29


6/24/2020 Lab04-Object Segmentation

In [73]: h = image_hsv[:,:,0]
s = image_hsv[:,:,1]
v = image_hsv[:,:,2]

y = image_ycbcr[:,:,0]
cb = image_ycbcr[:,:,1]
cr = image_ycbcr[:,:,2]

ShowImage([h, s, v], 1, 3)
ShowImage([y, cb, cr], 1, 3)
ShowImage([s > 150, (cr < 100) & (cr > 50)], 1, 2)

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 26/29


6/24/2020 Lab04-Object Segmentation

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 27/29


6/24/2020 Lab04-Object Segmentation

In [77]: image_index = segments_watershed.copy()


max_index = image_index.max()
image_mask = image_index * 0

h = image_hsv[:,:,0]
s = image_hsv[:,:,1]
v = image_hsv[:,:,2]

y = image_ycbcr[:,:,0]
cb = image_ycbcr[:,:,1]
cr = image_ycbcr[:,:,2]

for idx in range(max_index):


mask = (image_index == idx)
SValueList = s[mask == 1]
CrValueList = cr[mask == 1]

Condition1 = SValueList.mean() > 150


Condition2 = (CrValueList.mean() < 100) & (CrValueList.mean() > 50)

if(Condition1 & Condition2):


image_mask = image_mask + mask

image_mask = SelectMaskByThreshArea(image_mask, minArea = 500, maxArea = 100000)

image_result1 = LabelObjectByMask(image_orig, image_mask, type = "Boundary", color = (0,255,0), thick = 3)


image_result2 = LabelObjectByMask(image_orig, image_mask, type = "BBox", color = (0,255,0), thick = 3)
ShowImage([image_index, image_mask], 1, 2)
ShowImage([image_result1, image_result2], 1, 2)

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 28/29


6/24/2020 Lab04-Object Segmentation

C:\Users\tuant\Anaconda3\lib\site-packages\ipykernel_launcher.py:18: RuntimeWarning: Mean of empty slice.


C:\Users\tuant\Anaconda3\lib\site-packages\ipykernel_launcher.py:19: RuntimeWarning: Mean of empty slice.

file:///D:/MSI DATA (Previous Computer)/Teaching And Training/Image Segmentation/Lab04-Object Segmentation.html 29/29

You might also like