I used K-Means Clustering to perform segmentation on this traffic sign as shown below.
These are my code
Read image and blur
img = cv.imread('000_0001.png')
img_rgb = cv.cvtColor(img, cv.COLOR_BGR2RGB)
kernel_size = 5
img_rgb = cv.blur(img_rgb, (kernel_size, kernel_size))
# reshape
img_reshape = img_rgb.reshape((-1, 3))
img_reshape = np.float32(img_reshape)
Perform k-means clustering
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 4
attempts = 10
ret, label, center = cv.kmeans(img_reshape, K, None, criteria, attempts, cv.KMEANS_PP_CENTERS)
# reshape into original dimensions
center = np.uint8(center)
res = center[label.flatten()]
result = res.reshape(img_rgb.shape)
plt.figure(figsize = (15, 15))
plt.subplot(1, 2, 1)
plt.imshow(img_rgb)
plt.subplot(1, 2, 2)
plt.imshow(result)
plt.show()
create masks
from numpy import linalg as LN
red = (255, 0, 0)
Idx_min_red = np.argmin(LN.norm(center-red, axis = 1))
white = (255, 255, 255)
Idx_min_white = np.argmin(LN.norm(center-white, axis = 1))
black = (0, 0, 0)
Idx_min_black = np.argmin(LN.norm(center-black, axis = 1))
mask_red = result == center[Idx_min_red]
mask_white = result == center[Idx_min_white]
mask_black = result == center[Idx_min_black]
pre_mask = cv.bitwise_or(np.float32(mask_red), np.float32(mask_white))
mask = cv.bitwise_or(np.float32(pre_mask), np.float32(mask_black))
Segment the image
seg_img = img*(mask.astype("uint8"))
Morphological Transformation
kernel = np.ones((5, 5), dtype = np.uint8)
img_dilate = cv.morphologyEx(seg_img, cv.MORPH_OPEN, kernel)
res = np.hstack((img, img_dilate))
cv.imshow("res", res)
cv.waitKey(0)
cv.destroyAllWindows()
Question here
These codes can only segment red traffic signs, is it possible to tweak a little bit on different colours so that it can segment red, blue and yellow traffic signs? (like the one below for example)
Update, this is what I have tried:
I used a pipeline to do OR operation on all the masks
mask = mask_red | mask_white | mask_black | mask_blue
but then the new mask will fail to segment the image
Rather than K means, I'd suggest a connected components based approach to find this. These signs and symbols have relatively uniform color areas that take a fairly significant part of the image, that would result in large connected components. You can later write downstream logic to select the relevant connected components to define your sign and create a segmented image from them.
import cv2
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
colors = (np.array(plt.cm.tab10.colors) * 255).astype('int')
im1 = cv2.imread('37Gu6.png')
im2 = cv2.imread('dU47J.png')
for i in [im1, im2]:
gray = cv2.cvtColor(i, cv2.COLOR_BGR2GRAY)
# Calculate image value histogram to get an adaptive threshold value based
hist = np.histogram(gray.ravel(), bins=25)
modeIdx = np.where(hist[0] == hist[0].max())[0][0]
modeVal = hist[1][modeIdx]
tVal = modeVal * 1.15 # Our threshold is 115% of the most common hist value
_, thresh = cv2.threshold(gray, tVal, 255, cv2.THRESH_BINARY)
# Find connected componenents
output = cv2.connectedComponentsWithStats(thresh, 8, cv2.CV_32S)
(numLabels, labels, stats, centroids) = output
# The 5th column in the CC stats is the area
# Find the indices of CCs with above average area
CCArea = stats[:,4]
aboveAvgLbls = np.where(CCArea > CCArea.mean())[0]
# Generate labeled output image
outputImg = np.zeros(i.shape, dtype="uint8")
for l, c in zip(aboveAvgLbls, colors):
w = np.where(labels == l)
outputImg[ w[0], w[1] ] = c
f, ax = plt.subplots(1,4,figsize=(20,5))
for a, img, t in zip(ax, [i, gray, thresh, outputImg], ['RGB', 'Gray', f'Thresh [t={tVal}]', 'Labels']):
a.imshow(img[:,:,::-1] if img.shape[-1] == 3 else img, 'gray')
a.set_title(t)
plt.show()
Related
I want to segment the left auricle DICOM image for the calculation of cv2.minAreaRect.
I use cv2.GaussianBlur to filtter noise first, then use cv2.kmeans to segment the image as mask.
Afterwards, I use cv2.Canny and cv2.findContours to find the edge I want.
But after I apply cv2.minAreaRect to the contours, I get many small rectangles.
This isn't what I expect, I want to find the whole minimum bounding rectangle as below.
The following is my code.
import cv2
import numpy as np
# read input and convert to range 0-1
image = cv2.imread('1.jpg')
image = cv2.GaussianBlur(image, (15, 15), 0)
h, w, c = image.shape
# reshape to 1D array
image_2d = image.reshape(h*w, c).astype(np.float32)
# set number of colors
numcolors = 2
numiters = 10
epsilon = 1
attempts = 10
# do kmeans processing
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, numiters, epsilon)
ret, labels, centers = cv2.kmeans(image_2d, numcolors, None, criteria, attempts, cv2.KMEANS_RANDOM_CENTERS)
# reconstitute 2D image of results
centers = np.uint8(centers)
newimage = centers[labels.flatten()]
newimage = newimage.reshape(image.shape)
cv2.imshow('new image', newimage)
cv2.waitKey(0)
k = 0
for center in centers:
# select color and create mask
layer = newimage.copy()
mask = cv2.inRange(layer, center, center)
#print(mask[203][130])
# apply mask to layer
layer[mask == 0] = [0,0,100]
#cv2.imshow('layer', layer)
#cv2.waitKey(0)
num = 0
# save kmeans clustered image and layer
if(k == 0):
edges = cv2.Canny(mask, 1, 10)
contours, hierarchy = cv2.findContours(edges,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)[-2:]
raw_img3 = cv2.drawContours(image.copy(), contours, -1, (0, 0, 255), 3)
cnts = contours
cv2.imshow('Canny', raw_img3)
cv2.waitKey(0)
for cnt in cnts:
# find minimum bounding rectangle
rect = cv2.minAreaRect(cnt)
box2 = cv2.boxPoints(rect)
box2 = np.int0(box2)
aa = cv2.drawContours(image.copy(), [box2], 0, (255, 0, 0), 4)
cv2.imshow('Canny', aa)
cv2.waitKey(0)
k = k + 1
I use deep learning algorithms to detect elements in an image.
Once these elements are detected I try to recover two colours in this image.
Here is an example of the images I process:
Non-contrasted image
To make it easier I contrast the image to improve the colours here is an example:
Contrasted image
My goal is to find in this image the blue and red colour, it is at this precise moment that I block.
When the image is of good quality I manage to find the colours but on other images of less good quality it is very difficult to get good results.
Knowing that the colours that I would like to find are the following:
red, green, blue, yellow, grey, brown, violet, turquoise, orange, pink
Do you know of any image processing methods or machine learning models that could solve my problem?
More images for exemple :
Good image 1
Good image 2
Bad image 1
Bad image 2
And the code i used :
import cv2
import copy
from sklearn import multioutput
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import numpy as np
from collections import Counter
from skimage.color import rgb2lab, deltaE_cie76
import os
from PIL import Image, ImageEnhance
class ImageColorDetection(object):
origineFrame : list = []
imageFrame : list = []
hsvFrame : list = []
colorList : dict = {}
def __init__(self, array=None, path=None, rotated=0):
self.colorList = {}
if path is not None:
self.origineFrame = Image.open(path).convert('RGB').rotate(rotated)
im_output = Image.open(path).convert('RGB').rotate(rotated)
elif array is not None:
self.origineFrame = Image.fromarray(array).convert('RGB').rotate(rotated)
im_output = Image.fromarray(array).convert('RGB').rotate(rotated)
else:
raise Exception('Aucune image n\'est renseigner dans le constructeur')
#im_output = im_output.filter(ImageFilter.BLUR)
#im_output = im_output.filter(ImageFilter.EDGE_ENHANCE_MORE)
#im_output = ImageOps.autocontrast(im_output, cutoff = 5, ignore = 5)
enhancer = ImageEnhance.Color(im_output)
im_output = enhancer.enhance(3)
enhancer = ImageEnhance.Contrast(im_output)
im_output = enhancer.enhance(0.9)
enhancer = ImageEnhance.Sharpness(im_output)
im_output = enhancer.enhance(2)
enhancer = ImageEnhance.Brightness(im_output)
im_output = enhancer.enhance(1.6)
im_output = np.array(im_output)
self.imageFrame = cv2.cvtColor(im_output, cv2.COLOR_RGB2BGR)
self.hsvFrame = cv2.cvtColor(self.imageFrame, cv2.COLOR_BGR2HSV)
def findColor(self, color_rgb, color_title, color_upper, color_lower):
kernal = np.ones((5, 5), "uint8")
color_mask = cv2.inRange(self.hsvFrame, color_lower, color_upper)
color_mask = cv2.dilate(color_mask, kernal)
res_red = cv2.bitwise_and(self.imageFrame, self.imageFrame,
mask = color_mask)
current_area = 0
x, y, w, h, (r,g,b) = 0, 0, 0, 0, color_rgb
# Creating contour to track blue color
im, contours, hierarchy = cv2.findContours(color_mask,
cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
for pic, contour in enumerate(contours):
area = cv2.contourArea(contour)
if(area > 1000 and current_area < area):
x, y, w, h = cv2.boundingRect(contour)
self.colorList[color_title] = x, y, w, h, color_rgb
current_area = area
return color_title in self.colorList.keys()
def ShowImage(self):
tmp_img = np.asarray(copy.copy(self.origineFrame))
for color in self.colorList:
cv2.rectangle(
tmp_img,
(self.colorList[color][0], self.colorList[color][1]),
((self.colorList[color][0] + self.colorList[color][2]), (self.colorList[color][1] + self.colorList[color][3])),
self.colorList[color][4], 2)
cv2.putText(
tmp_img,
color,
(self.colorList[color][0], self.colorList[color][1]),
cv2.FONT_HERSHEY_SIMPLEX,
1.0,
self.colorList[color][4])
#plt.imshow(tmp_img, multioutput=True)
return tmp_img
def ShowImageContrast(self):
tmp_img = copy.copy(self.imageFrame)
tmp_img = cv2.cvtColor(tmp_img, cv2.COLOR_BGR2RGB)
for color in self.colorList:
cv2.rectangle(
tmp_img,
(self.colorList[color][0], self.colorList[color][1]),
((self.colorList[color][0] + self.colorList[color][2]), (self.colorList[color][1] + self.colorList[color][3])),
self.colorList[color][4], 3)
cv2.putText(
tmp_img,
color,
(self.colorList[color][0], self.colorList[color][1]),
cv2.FONT_HERSHEY_SIMPLEX,
0.8,
self.colorList[color][4])
#plt.imshow(tmp_img, multioutput=True)
return tmp_img
def RGB2HEX(self, color):
return "#{:02x}{:02x}{:02x}".format(int(color[0]), int(color[1]), int(color[2]))
def get_colors(self, contrasted, number_of_colors, show_chart):
if contrasted:
modified_image = cv2.resize(np.asarray(self.imageFrame), (600, 400), interpolation = cv2.INTER_AREA)
else:
modified_image = cv2.resize(np.asarray(self.origineFrame), (600, 400), interpolation = cv2.INTER_AREA)
#modified_image = cv2.resize(np.asarray(self.origineFrame), (600, 400), interpolation = cv2.INTER_AREA)
modified_image = modified_image.reshape(modified_image.shape[0]*modified_image.shape[1], 3)
clf = KMeans(n_clusters = number_of_colors)
labels = clf.fit_predict(modified_image)
counts = Counter(labels)
# sort to ensure correct color percentage
counts = dict(sorted(counts.items()))
center_colors = clf.cluster_centers_
# We get ordered colors by iterating through the keys
ordered_colors = [center_colors[i] for i in counts.keys()]
hex_colors = [self.RGB2HEX(ordered_colors[i]) for i in counts.keys()]
rgb_colors = [ordered_colors[i] for i in counts.keys()]
print("Nombre de couleur : ", len(hex_colors))
if (show_chart):
plt.figure(figsize = (8, 6))
plt.pie(counts.values(), labels = hex_colors, colors = hex_colors)
return counts, hex_colors, rgb_colors
Attempt with HSV :
With the HSV my problem persists I found a tutorial that sets up the problem I'm trying to solve but it doesn't work on the images I'm trying to process, do you have any knowledge on the subject (courses, youtube videos, articles)?
Voici l'article en question : https://towardsdatascience.com/color-identification-in-images-machine-learning-application-b26e770c4c71
Maybe OpenCV's inRange() might help?
import cv2
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower_hsvcolorspace = np.array([hue_min, saturation_min, value_min])
upper_hsvcolorspace = np.array([hue_max, saturation_max, value_max])
mask = cv2.inRange(hsv_image, lower_hsvcolorspace, upper_hsvcolorspace)
You can look up your expected HSV values here for example. Just be aware that the ranges in OpenCV are different: 0-179 (hue) and 0-255 (saturation, value).
Can you post more images: good ones, bad ones and the expected output?
Convert you image to HSV cv2.cvtColor(image, cv2.COLOR_BGR2HSV), than create threshold vectors like
lower_green = np.array([30, 0, 0])
upper_green = np.array([90, 255, 255])
Using this thresholds you can filter different colors, read more about HSV
mask = cv2.inRange(hsv_image, lower_green, upper_green)
I want to mask human fingernails (fingernails white and everything including the hand is black). I do simple image operations then Canny edge detection after I smoothen the image then find contours to give internal contours white color which would be fingernails.
My problem is that when fingernails are painted it is quite easy to detect however when there is no paint it becomes really complicated and the program has to get 50 images and save outputs to a certain folder.
I am confused about how to proceed, if anybody did something similar I would appreciate some help.
import cv2
import numpy as np
import matplotlib.pyplot as plt
def display_img(img):
fig = plt.figure(figsize = (12,10))
ax = fig.add_subplot(111)
plt.imshow(img,cmap='gray')
img = cv2.imread('nail2.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
blur = cv2.blur(gray,ksize=(1,1))
kernel = np.ones((5,5),np.uint8)
display_img(blur)
med = np.median(gray)
gradient = cv2.Laplacian(blur,cv2.CV_64F)
gradient = cv2.convertScaleAbs(gradient)
plt.imshow(gradient,'gray')
lower = int(max(0,0.7*med))
upper = int(min(255,1.3*med))
edges = cv2.Canny(blur,lower,upper)
display_img(edges)
edges = cv2.GaussianBlur(edges, (11, 11), 0) # smoothing before applying threshold
display_img(edges)
image, contours, hierarchy = cv2.findContours(edges, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
# Create empty array to hold internal contours
image_internal = np.zeros(image.shape)
# Iterate through list of contour arrays
for i in range(len(contours)):
# If third column value is NOT equal to -1 than its internal
if hierarchy[0][i][3] != -1:
# Draw the Contour
cv2.drawContours(image_internal, contours, i, 255, -1)
display_img(image_internal)
below is a good result:
some bad result even though fingers have pink paint:
Well, you have a big light and scale problem in these two images. But a possible solution is to segment the color channels and look for blobs.
Then you can segment with blob params.
The code you can try here:
import cv2
import numpy as np
fra = cv2.imread('nails.png')
height, width, channels = fra.shape
src = cv2.medianBlur(fra, 21)
hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV_FULL)
mask = cv2.inRange(hsv, np.array([0, 0, 131]), np.array([62, 105, 255]))
mask = cv2.erode(mask, None, iterations=8)
mask = cv2.dilate(mask, None, iterations=8)
params = cv2.SimpleBlobDetector_Params()
params.filterByArea = True
params.minArea = int((height * width) / 500)
params.maxArea = int((height * width) / 10)
params.filterByCircularity = True
params.minCircularity = 0.5
params.filterByConvexity = True
params.minConvexity = 0.5
params.filterByInertia = True
params.minInertiaRatio = 0.01
detector = cv2.SimpleBlobDetector_create(params)
key_points = detector.detect(255 - mask)
vis = cv2.bitwise_and(hsv, hsv, mask=mask)
vis = cv2.addWeighted(src, 0.2, vis, 0.8, 0)
cv2.drawKeypoints(vis, key_points, vis, (0, 0, 255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
for kp in key_points:
cv2.drawMarker(vis, (int(kp.pt[0]), int(kp.pt[1])), color=(0, 255, 0), markerType=cv2.MARKER_CROSS, thickness=3)
cv2.imshow("VIS", vis)
cv2.imwrite('nails_detected.png', vis)
cv2.waitKey(0)
cv2.destroyAllWindows()
Good luck!
I am trying to filter the background of images presenting electric cables. I tried to do the following:
Transform from color to gray
Apply cv2.Laplacian or 2 times of cv2.Sobel for finding edges in both directions.
Apply thresholding cv2.THRESH_BINARY(_INV), cv2.THRESH_OTSU
Lastly, I tried to find edges with 'filtered' images using cv2.Canny together with cv2.HoughLinesP
Overall, the results aren't satisfying at all. I will give an example of 2 images:
And the output of my script:
I also played with the values in config, but the results weren't different much.
Here's the little script I managed to do:
import cv2
import matplotlib.pyplot as plt
import numpy as np
def img_show(images, cmap=None):
fig = plt.figure(figsize=(17, 10))
root = 3 # len(images) ** 0.5
for i, img in enumerate(images):
ax = fig.add_subplot(root, root, i + 1)
ax.imshow(img, cmap=cmap[i])
plt.show()
class Config:
scale = 0.4
min_threshold = 120
max_threshold = 200
canny_min_threshold = 100
canny_max_threshold = 200
config = Config()
def find_lines(img, rgb_img):
dst = cv2.Canny(img, config.canny_min_threshold, config.canny_max_threshold)
cdstP = np.copy(rgb_img)
lines = cv2.HoughLinesP(dst, 1, np.pi / 180, 150, None, 0, 0)
lines1 = lines[:, 0, :]
for x1, y1, x2, y2 in lines1[:]:
cv2.line(cdstP, (x1, y1), (x2, y2), (255, 0, 0), 5)
return cdstP
if __name__ == "__main__":
bgr_img = cv2.imread('DJI_0009.JPG')
bgr_img = cv2.resize(bgr_img, (0, 0), bgr_img, config.scale, config.scale)
rgb_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB)
gray_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2GRAY)
# _, threshold = cv2.threshold(gray_img, config.min_threshold, config.max_threshold, cv2.THRESH_BINARY)
# laplacian = cv2.Laplacian(rgb_img, cv2.CV_8UC1)
sobelx = cv2.Sobel(gray_img, cv2.CV_8UC1, 1, 0)
sobely = cv2.Sobel(gray_img, cv2.CV_8UC1, 0, 1)
blended = cv2.addWeighted(src1=sobelx, alpha=0.5, src2=sobely, beta=0.5, gamma=0)
_, threshold = cv2.threshold(blended, config.min_threshold, config.max_threshold,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)
p1 = find_lines(threshold, rgb_img)
p2 = find_lines(blended, rgb_img)
p3 = find_lines(gray_img, rgb_img)
plots = [rgb_img, p1, p2, p3]
cmaps = [None] + ['gray'] * (len(plots) - 1)
img_show(plots, cmaps)
I am assuming I need to do much better filtring. However, I also tried image segmentation, but the results weren't promising at all.
Any ideas on how to improve this?
Thanks
Here is one way to do that in Python/OpenCV. I threshold, then optionally clean with morphology. Then get the contours and for each contour compute its rotated rectangle. Then get the dimensions of the rotated rectangle and compute the aspect ratio (largest dimension / smallest dimension) and optionally the area. Then I threshold on the aspect ratio (and optionally the area) and keep only those contours that pass)
Input:
import cv2
import numpy as np
image = cv2.imread("DCIM-100-MEDIA-DJI-0009-JPG.jpg")
hh, ww = image.shape[:2]
# convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# create a binary thresholded image
thresh = cv2.threshold(gray, 64, 255, cv2.THRESH_BINARY)[1]
# invert so line is white on black background
thresh = 255 - thresh
# apply morphology
kernel = np.ones((11,11), np.uint8)
clean = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# get external contours
contours = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
area_thresh = ww / 2
aspect_thresh = ww / 30
print(area_thresh,aspect_thresh)
print('')
result = image.copy()
for c in contours:
# get rotated rectangle from contour
# get its dimensions
rotrect = cv2.minAreaRect(c)
(center), (dim1,dim2), angle = rotrect
maxdim = max(dim1,dim2)
mindim = min(dim1,dim2)
area = dim1 * dim2
if mindim != 0:
aspect = maxdim / mindim
#print(area, aspect)
#if area > area_thresh and aspect > aspect_thresh:
if aspect > aspect_thresh:
# draw contour on input
cv2.drawContours(result,[c],0,(0,0,255),3)
print(area, aspect)
# save result
cv2.imwrite("DCIM-100-MEDIA-DJI-0009-JPG_thresh.jpg",thresh)
cv2.imwrite("DCIM-100-MEDIA-DJI-0009-JPG_clean.jpg",clean)
cv2.imwrite("DCIM-100-MEDIA-DJI-0009-JPG_result.jpg",result)
# display result
cv2.imshow("thresh", thresh)
cv2.imshow("clean", clean)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Thresholded image:
Morphology cleaned image:
Result image:
I am trying to apply the kmeans from opencv in order to segment the image in HSV color space.
def leftOffset(src, p_countours):
height, width, size = src.shape
p_width = width/p_countours
o_left = src[0:height, 0:p_width]
HSV_img = cv2.cvtColor(o_left, cv2.COLOR_BGR2HSV)
hue = HSV_img[0]
hue = np.float32(HSV_img)
# Define criteria = ( type, max_iter = 10 , epsilon = 1.0 )
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
# Set flags (Just to avoid line break in the code)
flags = cv2.KMEANS_RANDOM_CENTERS
# Apply KMeans
compactness,labels,centers = cv2.kmeans(hue,2,criteria,10,flags)
centers = np.uint8(centers)
res = centers[labels.flatten()]
res2 = res.reshape((hue.shape))
cv2.imshow("o_left", hue)
cv2.waitKey(0)
I am now able to apply the kmeans algorithm to the HSVImage[0] with K=2, and how can I get a image like threshold according to the result?
Thanks
To clarify the question:
I have color-based captchas, and I want to segment each digits.
The image is like
I am going to use k-means method to find out the dominant color and segment the digits inside.
1) If all you need is to find the dominant color why not find the histograms of each color channel? Find the dominant channel then segment only that channel using otsu? For example if I threshold only the hue I can get nice results. K-means could be an overkill for this task:
import cv2
import numpy as np
import matplotlib.pylab as plt
## Simple Otsu over hue
six = cv2.imread('7zovC.jpg')
##convert to hsv
hsv = cv2.cvtColor(six, cv2.COLOR_BGR2HSV)
hue = hsv[:, :, 0]
binary_img = cv2.threshold(hue, 128, 255, cv2.THRESH_OTSU)
plt.figure()
plt.imshow(binary_img*255)
plt.show()
2) Why not use all channels for clustering instead of just hue? What you need is clustering -> color quantization this link should be useful. This is for opencv version > 3.0.0
Note for python 2.4.11, cv2.kmeans has a slightly difference interface and you could use this instead:
def color_quantize(img, K):
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, 10, 1.0)
ret, label, center = cv2.kmeans(Z, 2, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
# Now convert back into uint8, and make original image
center = np.uint8(center)
res = center[label.flatten()]
quantized_img = res.reshape((img.shape))
label_img = label.reshape((img.shape[:2]))
return label_img, quantized_img
six = cv2.imread('7zovC.jpg')
##convert to hsv
hsv = cv2.cvtColor(six, cv2.COLOR_BGR2HSV)
K = 2
label_img, six_q = color_quantize(hsv, K)
plt.figure()
plt.imshow(label_img)
plt.show()
My results for color quantization were not impressive.
May I suggest a conventional alternative? I you get rid of the very dark and bright regions first, you may be able to simply rely on the most frequent value of the hue component calculated from the histogram.
Mind that the borders of the numbers will never be absolutely exact, since colours are similar in the surrounding.
Furthrmore, you could select the maximum blob only (according to size) to suppress remaining small blobs outside.
Results:
Code:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('image1.jpg')
#get rid of very bright and very dark regions
delta=30
lower_gray = np.array([delta, delta,delta])
upper_gray = np.array([255-delta,255-delta,255-delta])
# Threshold the image to get only selected
mask = cv2.inRange(img, lower_gray, upper_gray)
# Bitwise-AND mask and original image
res = cv2.bitwise_and(img,img, mask= mask)
#Convert to HSV space
HSV_img = cv2.cvtColor(res, cv2.COLOR_BGR2HSV)
hue = HSV_img[:, :, 0]
#select maximum value of H component from histogram
hist = cv2.calcHist([hue],[0],None,[256],[0,256])
hist= hist[1:, :] #suppress black value
elem = np.argmax(hist)
print np.max(hist), np.argmax(hist)
tolerance=10
lower_gray = np.array([elem-tolerance, 0,0])
upper_gray = np.array([elem+tolerance,255,255])
# Threshold the image to get only selected
mask = cv2.inRange(HSV_img, lower_gray, upper_gray)
# Bitwise-AND mask and original image
res2 = cv2.bitwise_and(img,img, mask= mask)
titles = ['Original Image', 'Selected Gray Values', 'Hue', 'Result']
images = [img, res, hue, res2]
for i in xrange(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()