Let say I have this rose (Do not care about the background, only the white leaves are important).
I transform it to a grayscale picture:
grayscaled=cv2.imread('white_rose.png',cv2.IMREAD_GRAYSCALE)
How can I change every white pixel to a red one under the condition the red color (R=255) will have the same contrast as the white one has ? Meaning I want to see the white leaves in red color but with the same L value of every pixel that in grayscaled ?
You need to loop over your grey image and create a new coloured image by yourself.
For each pixel, you can replace the R value of your coloured image with the remainder of dividing of 255 and relative grey value:
import cv2
import numpy as np
img = cv2.imread('5585T.jpg')
print type(img)
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
new=[[[0,0,255%j] for j in i] for i in img_gray]
dt = np.dtype('f8')
new=np.array(new,dtype=dt)
cv2.imwrite('img.jpg',new)
and with new=[[[255%j,255%j,j] for j in i] for i in img_gray] :
Related
image example:
I want to create a black and white bitmap from scratch (not converting or manipulating an existing image) and have the ability to change individual pixels to either black or white using pixel coordinates, somehow, maybe via a dictionary?. Something like a chessboard but with one pixel per chessboard-square (if that makes sense?).I found something to generate a colour spectrum image but don't know how to adapt this.
from PIL import Image
img = Image.new( 'RGB', (300,50), "black") # Create a new black image
pixels = img.load() # Create the pixel map
for i in range(img.size[0]): # For every pixel:
for j in range(img.size[1]):
pixels[i,j] = (i, j, 100) # Set the colour accordingly
img.show()
Zoomed in on leftmost edge of bitmap
You can do individual odd pixels here and there like this:
from PIL import PIL
# Create new black image - L mode for b/w
img = Image.new( 'L', (10,6))
# Make pixels at locations (0,5) and (2,1) white (255)
img.putpixel((0,5), 255)
img.putpixel((2,1), 255)
# Save result
img.save('result.png')
However, if you want to do whole rows, or columns, or longer lines, I would recommend round-tripping to Numpy like this:
import numpy as np
# Create new black image - L mode for b/w
img = Image.new( 'L', (10,6))
# Convert to Numpy array for easy processing
na = np.array(img)
# Make row 1 white
na[1,:] = 255
# Make column 8 white
na[:,8] = 255
# Revert to PIL Image from Numpy array and save
Image.fromarray(na).save('result.png')
Or if you want to do a block:
... as above ...
na[1:3,5:9] = 255
... as above ...
I currently have a code for measuring the average brightness of an RGB image.
When i run this with a black and white image, the RGB values are the same, e.g. 37, 37, 37
I have no idea about colours etc but i dont think this is correct
Here is my code at the moment:
from PIL import Image
from math import sqrt
imag = Image.open("../Images/pexels-photo-57905.jpeg")
imag = imag.convert ('RGB')
imag.show()
X,Y = 0,0
pixelRGB = imag.getpixel((X,Y))
R,G,B = pixelRGB
brightness = sum([R,G,B])/3 ##0 is dark (black) and 255 is bright (white)
print(brightness)
print(R,G,B)
In a nutshell, i must convert an RGB image into grayscale, which im using .convert('LA') for, i must THEN measure the brightness of the image by adding the black and white values then dividing them by 2
Are these codes correct to measure the average brightness of a greyscale image? What do the three lines below mean? Does it return the average brightness of the whole picture, or just (0,0)?
X,Y = 0,0
pixelRGB = imag.getpixel((X,Y))
R,G,B = pixelRGB
I want to calculate persentage of black pixels and white pixels for the picture, its colorful one
import numpy as np
import matplotlib.pyplot as plt
image = cv2.imread("image.png")
cropped_image = image[183:779,0:1907,:]
You don't want to run for loops over images - it is dog slow - no disrespect to dogs. Use Numpy.
#!/usr/bin/env python3
import numpy as np
import random
# Generate a random image 640x150 with many colours but no black or white
im = np.random.randint(1,255,(150,640,3), dtype=np.uint8)
# Draw a white rectangle 100x100
im[10:110,10:110] = [255,255,255]
# Draw a black rectangle 10x10
im[120:130,200:210] = [0,0,0]
# Count white pixels
sought = [255,255,255]
white = np.count_nonzero(np.all(im==sought,axis=2))
print(f"white: {white}")
# Count black pixels
sought = [0,0,0]
black = np.count_nonzero(np.all(im==sought,axis=2))
print(f"black: {black}")
Output
white: 10000
black: 100
If you mean you want the tally of pixels that are either black or white, you can either add the two numbers above together, or test for both in one go like this:
blackorwhite = np.count_nonzero(np.all(im==[255,255,255],axis=2) | np.all(im==[0,0,0],axis=2))
If you want the percentage, bear mind that the total number of pixels is easily calculated with:
total = im.shape[0] * im.shape[1]
As regards testing, it is the same as any software development - get used to generating test data and using it :-)
white_pixels = np.logical_and(255==cropped_image[:,:,0],np.logical_and(255==cropped_image[:,:,1],255==cropped_image[:,:,2]))
num_white = np.sum(white_pixels)
and the same with 0 for the black ones
Keep variables for white_count and black_count and just iterate through the image matrix. Whenever you encounter 255 increase the white_count and whenever 0 increase the black_count. Try it yourself, if no success I'll post the code here :)
P.S keep the dimensionality of the image in mind
You can use the getcolors() function from PIL image, this function return a list of tuples with colors found in image and the amount of each one. I'm using the following function to return a dictionary with color as key, and counter as value.
from PIL import Image
def getcolordict(im):
w,h = im.size
colors = im.getcolors(w*h)
colordict = { x[1]:x[0] for x in colors }
return colordict
im = Image.open('image.jpg')
colordict = getcolordict(im)
# get the amount of black pixels in image
# in RGB black is 0,0,0
blackpx = colordict.get((0,0,0))
# get the amount of white pixels in image
# in RGB white is 255,255,255
whitepx = colordict.get((255,255,255))
# percentage
w,h = im.size
totalpx = w*h
whitepercent=(whitepx/totalpx)*100
blackpercent=(blackpx/totalpx)*100
I have an image in RGB and another segmented image in which the pixels have 3 values(segmented image). I want to overlay the segmented image on top of the main image as the segmented areas make contours over the main image such as image below. Here the value of the segmented image pixels are 0, 1 and 2. The red contour shows the contour of pixels with value1 , the yellow contour shows the contour of pixels with 2 value and the background pixel value is 0.
the image is from the paper "Dilated-Inception Net: Multi-Scale FeatureAggregation for Cardiac Right VentricleSegmentation"
Here is an example of a segmented image.
segmented image
The background image can be any image. I only need these rectangle counters appear on the background image as two contours similar to red and yellow lines above. So, the output will be similar to the image below.
output image
sorry as I draw rectangles by hand they are not exact. I just would like to give you an insight about the output.
I had a go at this using four different methods:
OpenCV
PIL/Pillow and Numpy
command-line with ImageMagick
morphology from skimage
Method 1 - OpenCV
Open segmented image as greyscale
Open main image as greyscale and make colour to allow annotation
Find the contours using cv2.findContours()
Iterate over contours and use cv2.drawContours() to draw each one onto main image in colour according to label in segmented image.
Documentation is here.
So, starting with this image:
and this segmented image:
which looks like this when contrast-stretched and the sandwich is labelled as grey(1) and the snout as grey(2):
Here's the code:
#!/usr/bin/env python3
import numpy as np
import cv2
# Load images as greyscale but make main RGB so we can annotate in colour
seg = cv2.imread('segmented.png',cv2.IMREAD_GRAYSCALE)
main = cv2.imread('main.png',cv2.IMREAD_GRAYSCALE)
main = cv2.cvtColor(main,cv2.COLOR_GRAY2BGR)
# Dictionary giving RGB colour for label (segment label) - label 1 in red, label 2 in yellow
RGBforLabel = { 1:(0,0,255), 2:(0,255,255) }
# Find external contours
_,contours,_ = cv2.findContours(seg,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
# Iterate over all contours
for i,c in enumerate(contours):
# Find mean colour inside this contour by doing a masked mean
mask = np.zeros(seg.shape, np.uint8)
cv2.drawContours(mask,[c],-1,255, -1)
# DEBUG: cv2.imwrite(f"mask-{i}.png",mask)
mean,_,_,_ = cv2.mean(seg, mask=mask)
# DEBUG: print(f"i: {i}, mean: {mean}")
# Get appropriate colour for this label
label = 2 if mean > 1.0 else 1
colour = RGBforLabel.get(label)
# DEBUG: print(f"Colour: {colour}")
# Outline contour in that colour on main image, line thickness=1
cv2.drawContours(main,[c],-1,colour,1)
# Save result
cv2.imwrite('result.png',main)
Result:
Method 2 - PIL/Pillow and Numpy
Open segmented image and find unique colours
Open main image and desaturate
Iterate over each unique colour in list
... Make all pixels that colour white and all others black
... Find edges and use edges as mask to draw colour on main image
Here's the code:
#!/usr/bin/env python3
from PIL import Image, ImageFilter
import numpy as np
def drawContour(m,s,c,RGB):
"""Draw edges of contour 'c' from segmented image 's' onto 'm' in colour 'RGB'"""
# Fill contour "c" with white, make all else black
thisContour = s.point(lambda p:p==c and 255)
# DEBUG: thisContour.save(f"interim{c}.png")
# Find edges of this contour and make into Numpy array
thisEdges = thisContour.filter(ImageFilter.FIND_EDGES)
thisEdgesN = np.array(thisEdges)
# Paint locations of found edges in color "RGB" onto "main"
m[np.nonzero(thisEdgesN)] = RGB
return m
# Load segmented image as greyscale
seg = Image.open('segmented.png').convert('L')
# Load main image - desaturate and revert to RGB so we can draw on it in colour
main = Image.open('main.png').convert('L').convert('RGB')
mainN = np.array(main)
mainN = drawContour(mainN,seg,1,(255,0,0)) # draw contour 1 in red
mainN = drawContour(mainN,seg,2,(255,255,0)) # draw contour 2 in yellow
# Save result
Image.fromarray(mainN).save('result.png')
You'll get this result:
Method 3 - ImageMagick
You can also do the same thing from the command-line without writing any Python, and just using ImageMagick which is installed on most Linux distros and is available for macOS and Windows:
#!/bin/bash
# Make red overlay for "1" labels
convert segmented.png -colorspace gray -fill black +opaque "gray(1)" -fill white -opaque "gray(1)" -edge 1 -transparent black -fill red -colorize 100% m1.gif
# Make yellow overlay for "2" labels
convert segmented.png -colorspace gray -fill black +opaque "gray(2)" -fill white -opaque "gray(2)" -edge 1 -transparent black -fill yellow -colorize 100% m2.gif
# Overlay both "m1.gif" and "m2.gif" onto main image
convert main.png -colorspace gray -colorspace rgb m1.gif -composite m2.gif -composite result.png
Method 4 - Morphology from skimage
Here I am using morphology to find black pixels near 1 pixels and black pixels near 2 pixels.
#!/usr/bin/env python3
import skimage.filters.rank
import skimage.morphology
import numpy as np
import cv2
# Load images as greyscale but make main RGB so we can annotate in colour
seg = cv2.imread('segmented.png',cv2.IMREAD_GRAYSCALE)
main = cv2.imread('main.png',cv2.IMREAD_GRAYSCALE)
main = cv2.cvtColor(main,cv2.COLOR_GRAY2BGR)
# Create structuring element that defines the neighbourhood for morphology
selem = skimage.morphology.disk(1)
# Mask for edges of segment 1 and segment 2
# We are basically looking for pixels with value 1 in the segmented image within a radius of 1 pixel of a black pixel...
# ... then the same again but for pixels with a vaue of 2 in the segmented image within a radius of 1 pixel of a black pixel
seg1 = (skimage.filters.rank.minimum(seg,selem) == 0) & (skimage.filters.rank.maximum(seg, selem) == 1)
seg2 = (skimage.filters.rank.minimum(seg,selem) == 0) & (skimage.filters.rank.maximum(seg, selem) == 2)
main[seg1,:] = np.asarray([0, 0, 255]) # Make segment 1 pixels red in main image
main[seg2,:] = np.asarray([0, 255, 255]) # Make segment 2 pixels yellow in main image
# Save result
cv2.imwrite('result.png',main)
Note: JPEG is lossy - do not save your segmented image as JPEG, use PNG or GIF!
Keywords: Python, PIL, Pillow, OpenCV, segmentation, segmented, labelled, image, image processing, edges, contours, skimage, ImageMagick, scikit-image, morphology, rank, ranking filter, pixel adjacency.
These are quick one-liners that automatically choose colors for the category/class integer values and execute the overlay onto the original image.
Color entire segmentation area:
from skimage import color
result_image = color.label2rgb(segmentation_results, input_image)
Color contours of segmentation areas:
from skimage import segmentation
result_image = segmentation.mark_boundaries(input_image, segmentation_results, mode='thick')
If semi-transparent segmentation masks are to be displayed on top of the image, skimage has a built-in label2rgb() function that colorizes by a label channel:
Input Image
from skimage import io, color
import matplotlib.pyplot as plt
import numpy as np
seg = np.zeros((256,256)) # create a matrix of zeroes of same size as image
seg[gt > 0.95] = 1 # Change zeroes to label "1" as per your condition(s)
seg[zz == 255] = 2
io.imshow(color.label2rgb(seg,img,colors=[(255,0,0),(0,0,255)],alpha=0.01, bg_label=0, bg_color=None))
plt.show()
I want to change the brown areas to RED (or another color).
Just I don't know how to get the ranges for brown and put them in python code.
I know how to change a single color, but not a range of colors.
Any Ideas?
Thanks
This should give you an idea - it is pretty well commented:
#!/usr/local/bin/python3
import cv2 as cv
import numpy as np
# Load the aerial image and convert to HSV colourspace
image = cv.imread("aerial.png")
hsv=cv.cvtColor(image,cv.COLOR_BGR2HSV)
# Define lower and uppper limits of what we call "brown"
brown_lo=np.array([10,0,0])
brown_hi=np.array([20,255,255])
# Mask image to only select browns
mask=cv.inRange(hsv,brown_lo,brown_hi)
# Change image to red where we found brown
image[mask>0]=(0,0,255)
cv.imwrite("result.png",image)
How did I determine the limits for "brown"? I located a brown area in the image, and cropped it out to remove everything else. Then I resized it to 1x1 to average all the shades of brown in that area and converted it to HSV colourspace, I printed that and took the value for Hue which was 15 and went +/-5 to give a range of 10-20. Increase the range to 8-22 to select a wider range of hues.
HSV/HSL colourspace is described on Wikipedia here.
Keywords: Image processing, Python, OpenCV, inRange, range of colours, prime.
I would like to propose a different approach. However, this will work only for a range of certain dominant colors (red, blue, green and blue). I am focusing on the red colored regions present in the image in question.
Background:
Here I am using LAB color space where:
L-channel: expresses the brightness in the image
A-channel: expresses variation of color in the image between red and green
B-channel: expresses variation of color in the image between yellow and blue
Since I am interested in the red region, I will choose the A-channel for further processing.
Code:
img = cv2.imread('image_path')
# convert to LAB color space
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
# A-channel
cv2.imshow('A-channel', lab[:,:,1])
If you look at the image closely, the bright regions correspond to the red color in the original image. Now when we threshold it, we can isolate it completely:
th = cv2.threshold(lab[:,:,1],127,255,cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
Using the th image as mask, we give a different color to the corresponding regions in white:
# create copy of original image
img1=img.copy()
# highlight white region with different color
img1[th==255]=(255,255,0)
Here are both the images stacked beside each other:
You can normalize the A-channel image to better visualize it:
dst = cv2.normalize(lab[:,:,1], dst=None, alpha=0, beta=255,norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
In this way, there is no need to look for range in HSV space when working with dominant colors. Exploring the B-channel can help isolate blue and yellow colored regions.