Opencv plot black pixels on a image - python-3.x

I have a input image similar to
I am referring to:
How to fill the gaps in letters after Canny edge detection
I want to plot black pixels on this image. The proposed solution on the above url is first find all black pixels using
import matplotlib.pyplot as pp
import numpy as np
image = pp.imread(r'/home/cris/tmp/Zuv3p.jpg')
bin = np.all(image<100, axis=2)
My question is dow do I plot this black pixels (data stored in bin ) on image while ignoring all other colour channels.

In the answer is stated that np.all(image<100, axis=2) is used to select pixels where R,G and B are all lower then 100, which is basically color separation. Personally, I like to use the HSV-colorspace for that.
Result:
Note: if you want to improve the green letters, it is best to create a separate mask for that, and tweak the hsv values for green.
Code:
import numpy as np
import cv2
# load image
img = cv2.imread("img.jpg")
# Convert BGR to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# define range of black color in HSV
lower_val = np.array([0,0,0])
upper_val = np.array([179,255,127])
# Threshold the HSV image to get only black colors
mask = cv2.inRange(hsv, lower_val, upper_val)
# invert mask to get black symbols on white background
mask_inv = cv2.bitwise_not(mask)
# display image
cv2.imshow("Mask", mask_inv)
cv2.imshow("Img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Related

Draw or resize plotted quantized image with nearest neighbour scaling

Following this example of K means clustering I want to recreate the same - only I'm very keen for the final image to contain just the quantized colours (+ white background). As it is, the colour bars get smooshed together to create a pixel line of blended colours.
Whilst they look very similar, the image (top half) is what I've got from CV2 it contains 38 colours total.
The lower image only has 10 colours and is what I'm after.
Let's look at a bit of that with 6 times magnification:
I've tried :
# OpenCV and Python K-Means Color Clustering
# build a histogram of clusters and then create a figure
# representing the number of pixels labeled to each color
hist = colour_utils.centroid_histogram(clt)
bar = colour_utils.plot_colors(hist, clt.cluster_centers_)
bar = cv2.resize(bar, (460, 345), 0, 0, interpolation = cv2.INTER_NEAREST)
However, the resize seems to have no resizing effect or change the scaling type. I don't know what controls the initial image size either.
Confused.
Any ideas?
I recommend you to show the image using cv2.imshow, instead of using matplotlib.
cv2.imshow shows the image "pixel to pixel" by default, while matplotlib.pyplot matches the image dimensions to the size of the axes.
bar_bgr = cv2.cvtColor(bar, cv2.COLOR_RGB2BGR) # Convert RGB to BGR
cv2.imshow('bar', bar_bgr)
cv2.waitKey()
cv2.destroyAllWindows()
In case you want to use matplotlib, take a look at: Display image with a zoom = 1 with Matplotlib imshow() (how to?).
Code used for testing:
# import the necessary packages
import numpy as np
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import argparse
#import utils
import cv2
def centroid_histogram(clt):
# grab the number of different clusters and create a histogram
# based on the number of pixels assigned to each cluster
numLabels = np.arange(0, len(np.unique(clt.labels_)) + 1)
(hist, _) = np.histogram(clt.labels_, bins = numLabels)
# normalize the histogram, such that it sums to one
hist = hist.astype("float")
hist /= hist.sum()
# return the histogram
return hist
def plot_colors(hist, centroids):
# initialize the bar chart representing the relative frequency
# of each of the colors
bar = np.zeros((50, 300, 3), dtype = "uint8")
startX = 0
# loop over the percentage of each cluster and the color of
# each cluster
for (percent, color) in zip(hist, centroids):
# plot the relative percentage of each cluster
endX = startX + (percent * 300)
cv2.rectangle(bar, (int(startX), 0), (int(endX), 50),
color.astype("uint8").tolist(), -1)
startX = endX
# return the bar chart
return bar
# load the image and convert it from BGR to RGB so that
# we can dispaly it with matplotlib
image = cv2.imread('chelsea.png')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# show our image
plt.figure()
plt.axis("off")
plt.imshow(image)
# reshape the image to be a list of pixels
image = image.reshape((image.shape[0] * image.shape[1], 3))
# cluster the pixel intensities
clt = KMeans(n_clusters = 5)
clt.fit(image)
# build a histogram of clusters and then create a figure
# representing the number of pixels labeled to each color
hist = centroid_histogram(clt)
bar = plot_colors(hist, clt.cluster_centers_)
# show our color bart
#plt.figure()
#plt.axis("off")
#plt.imshow(bar)
#plt.show()
bar = cv2.resize(bar, (460, 345), 0, 0, interpolation = cv2.INTER_NEAREST)
bar_bgr = cv2.cvtColor(bar, cv2.COLOR_RGB2BGR) # Convert RGB to BGR
cv2.imshow('bar', bar_bgr)
cv2.waitKey()
cv2.destroyAllWindows()

Remove Freckles from Simple Binary Image

I have the following NumPy array of a running man, which you can download here:
https://drive.google.com/file/d/1SfIEqGsBV_vA7iP4UjLdklLJlLdDzozL/view?usp=sharing
To display it, use this code:
import numpy as np
import matplotlib.pyplot as plt
# load data
data = np.load('running_man.npy')
# plot data
plt.imshow(data)
As you can see there is a lot of noise (freckles) in the image. I would like to get rid of it and retrieve a clean image of the runner. Any idea of how to do it?
This is what I have done so far:
from skimage import measure
# Find contours at a constant value of 1
contours = measure.find_contours(data, 1, fully_connected='high')
# Select the largest contiguous contour
contour = sorted(contours, key=lambda x: len(x))[-1]
# Create an empty image to store the masked array
r_mask = np.zeros_like(data, dtype='bool')
# Create a contour image by using the contour coordinates rounded to their nearest integer value
r_mask[np.round(contour[:, 0]).astype('int'), np.round(contour[:, 1]).astype('int')] = 1
# Fill in the hole created by the contour boundary
r_mask = ndimage.binary_fill_holes(r_mask)
# Invert the mask since one wants pixels outside of the region
r_mask = ~r_mask
plt.imshow(r_mask)
... but as you can see the outline is very rough !
What works well is to upload the image to an online jpg to SVG converter -> this makes the lines super smooth. ... but I want to be able to do it in python.
Idea:
I am looking for something that can keep the sharp corners, maybe something that detects the gradient along the edge and only keeps the point where the gradient is above a certain threshold...
For this specific image you can just use numpy:
import numpy as np
import matplotlib.pyplot as plt
data = np.load('running_man.npy')
data[data > 1] = 0
plt.xticks([])
plt.yticks([])
plt.imshow(data)
For a method that preserves the corners better, we can use median filters, but force the preservation of corners.
Masked Image
Mask after filtering
Recolored
import cv2
import numpy as np
# load image
img = cv2.imread("run.png");
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY);
# make mask
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU);
# median filter
med = cv2.medianBlur(thresh, 11);
med[thresh == 255] = 255;
# inverse filter
mask = cv2.bitwise_not(med);
med = cv2.medianBlur(mask, 3);
med[mask == 255] = 255;
# recolor
color = np.zeros_like(img);
color[med == 0] = (66, 239, 245);
color[med == 255] = (92, 15, 75);
# show
cv2.imshow("colored", color);
cv2.waitKey(0);

While I am detecting shapes from an image using open CV in python, the outer border is also recognized, what can I do to stop this?

While I am detecting shapes from an image using open CV, the outer border is also recognized. What can I do to stop this?
This is my code:
import cv2
import numpy as np
img=cv2.imread("C:\\Users\\laksh\\OneDrive\\Desktop\\E-Yantra\\task_1a_explore_opencv\\task_1a_explore_opencv\\Task_1A_Part1\\Samples\\Sample1.png")
#print(img)
img2 = cv2.imread('C:\\Users\\laksh\\OneDrive\\Desktop\\E-Yantra\\task_1a_explore_opencv\\task_1a_explore_opencv\\Task_1A_Part1\\Samples\\Sample1.png', cv2.IMREAD_COLOR)
font = cv2.FONT_HERSHEY_COMPLEX
imgGrey = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh= cv2.threshold(imgGrey,240,255,cv2.THRESH_BINARY)
contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
approx=cv2.approxPolyDP(contour,0.01* cv2.arcLength(contour, True),True)
cv2.drawContours(img, [approx ],-1 ,(0,0,0),5)
cv2.imshow("shapes",img)
cv2.waitKey()
cv2.closeAllWindows()
This image shows that the the outer rectangle as well as the two internal shapes are recogonised:
That happens because your background is not black. OpenCV sees it as large rectangle. To avoid it, change bg color, or just remove rectangle with image size from list of detected shapes. You also can find gradients and plot fat black frame around your image, to remove edges from background.

How to set a colormap for PySimpleGUI Image()

Is there a way to set a colormap for sg.Image() or sg.DrawImage()? In my case I have a grayscale (single-band) thermal image that I'd like to show with a heat colormap. Short example of current code:
import PySimpleGUI as sg
layout = [[sg.Image(thermal_image_path, size=(600, 600))]]
window = sg.Window('Show image', size=(600, 600),
resizable=True).Layout(layout).finalize()
You could map the grey-tones to a range of different Hues and set the Saturation and Lightness constant - see Wikipedia article on HSL
#!/usr/bin/env python3
import numpy as np
import cv2
def heatmap(im):
# Map range 0..255 of greys to Hues in range 60..180
# Keep Lightness=127, Saturation=255
# https://en.wikipedia.org/wiki/HSL_and_HSV#Hue_and_chroma
H = (im.astype(np.float32) * 120./255.).astype(np.uint8) + 60
L = np.full((h,w), 127, np.uint8)
S = np.full((h,w), 255, np.uint8)
HLS = cv2.merge((H,L,S))
return cv2.cvtColor(HLS,cv2.COLOR_HLS2RGB)
# Create greyscale gradient
w, h = 256, 100
grey = np.repeat(np.arange(w,dtype=np.uint8).reshape(1,-1), h, axis=0)
cv2.imwrite('grey.png',grey) # debug only
# Apply heatmap to greyscale image
hm = heatmap(grey)
# Just for display
from PIL import Image
Image.fromarray(hm).save('result.png')
That makes the following greyscale image:
And then gets transformed to this:
Or you could shell out to ImageMagick with subprocess.run(), or use wand (its Python binding) to do this:
Make a 100x100 greyscale ramp - this is just setup to create an image to work with:
magick -size 100x100 gradient: grey.png
Make a 5-colour heatmap by varying the hues around the HSL circle - this only needs doing once and you can keep and reuse the image heat.png:
magick xc:"hsl(240,255,128)" xc:"hsl(180,255,128)" xc:"hsl(120,255,128))" xc:"hsl(60,255,128)" xc:"hsl(0,255,128)" +append heat.png
Map the shades of the greyscale image to our CLUT (colour lookup table) - this is the actual answer:
magick grey.png heat.png -clut result.png

split image on the basis of color

I have obtained an image after applying k-means with clusters = 3. Now I want to obtain 3 separate images on the basis of colours obtained after k-means.
For example, consider the attached image. Now I need
one image such that it contains only the blue square.
One having the letter v and one with just the background
Is there any possible way to do that using OpenCV and python.
The most general and simplest way to do it is using the three unique gray colors for each region. (Although I could find more than three gray levels in the above image, maybe due to variation as a result of compression of imgur. Though, at the end of the day, k-means should give exactly three BGR values)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
unique = np.unique(gray)
c1, c2, c3 = unique[0], unique[1], unique[2]
mask1 = np.zeros_like(gray)
mask1[gray == c1] = 255
mask2 = np.zeros_like(gray)
mask2[gray == c2] = 255
mask3 = np.zeros_like(gray)
mask3[mask3 == c3] = 255
You can solve the problem by calculating the histogram of the image.
The below plot shows the peaks of the image.
From this, you can threshold the colors. The code and result:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread("inputs/hist.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hist = cv2.calcHist([gray],[0],None,[256],[0,256])
colors = np.where(hist>5000)
img_number = 0
for color in colors[0]:
print(color)
split_image = img.copy()
split_image[np.where(gray != color)] = 0
cv2.imwrite(str(img_number)+".jpg",split_image)
img_number+=1
plt.hist(gray.ravel(),256,[0,256])
plt.savefig('plt')
plt.show()
Results:

Resources