OpenCV maximum and minimum RGB value using mask - python

I need to get the minimum RGB value of a circle. How can I do like the average RGB value method (cv2.mean) appliying a mask? To get the average RGB value of a circle I'm doing:
circle_img = np.zeros((circle_img.shape[0],circle_img.shape[1]), np.uint8)
cv2.circle(circle_img,(x_center,y_center),radio,(255,255,255),-1)
datos_rgb = cv2.mean(color_img, mask=circle_img)
Where color_img is the original image.
To get the minimum RGB value I'm doing:
masked_data = cv2.bitwise_and(color_img, color_img, mask=circle_img)
rgb_min = masked_data.reshape((masked_data.shape[0]*masked_data.shape[1], 3)).min(axis=0)
Where masked_data is the second image (masked circle).
But I'm getting all time the value [0,0,0] because of the background I think... I need to do like the average (cv2.mean) apliying the mask to ignore the black background. There is no pure black in the original image, so it is not possible to get the value [0,0,0]
To get the maximum RGB value it works perfectly doing:
masked_data = cv2.bitwise_and(color_img, color_img, mask=circle_img)
rgb_max = masked_data.reshape((masked_data.shape[0]*masked_data.shape[1], 3)).max(axis=0)
Because the black color [0,0,0] it does not affect here.
This is the original image.
This is the masked circle.

You may try using only numpy methods to get the results for all required calculations, rather than using OpenCV for some and numpy for others, and in some cases numpy can out-perform OpenCV in terms of execution time. You may use numpys' min, max and mean as:
import cv2
import numpy as np
img = cv2.imread("./assets/11yeJ.jpg")
mask = np.zeros((img.shape[0],img.shape[1]), np.uint8)
cv2.circle(mask, (493, 338), 30, (255, 255, 255), -1)
# Get the indices of mask where value == 255, which may be later used to slice the array.
img_mask = img[np.where(mask == 255)]
img_avg = np.mean(img_mask, axis=0)
img_min = np.min(img_mask, axis=0)
img_max = np.max(img_mask, axis=0)

Related

How to change the colour of pixels in an image depending on their initial luminosity?

The aim is to take a coloured image, and change any pixels within a certain luminosity range to black. For example, if luminosity is the average of a pixel's RGB values, any pixel with a value under 50 is changed to black.
I’ve attempted to begin using PIL and converting to grayscale, but having trouble trying to find a solution that can identify luminosity value and use that info to manipulate a pixel map.
There are many ways to do this, but the simplest and probably fastest is with Numpy, which you should get accustomed to using with image processing in Python:
from PIL import Image
import numpy as np
# Load image and ensure RGB, not palette image
im = Image.open('start.png').convert('RGB')
# Make into Numpy array
na = np.array(im)
# Make all pixels of "na" where the mean of the R,G,B channels is less than 50 into black (0)
na[np.mean(na, axis=-1)<50] = 0
# Convert back to PIL Image to save or display
result = Image.fromarray(na)
result.show()
That turns this:
Into this:
Another slightly different way would be to convert the image to a more conventional greyscale, rather than averaging for the luminosity:
# Load image and ensure RGB
im = Image.open('start.png').convert('RGB')
# Calculate greyscale version
grey = im.convert('L')
# Point process over pixels to make mask of darker ones
mask = grey.point(lambda p: 255 if p<50 else 0)
# Paste black (i.e. 0) into image where mask indicates it is dark
im.paste(0, mask=mask)
Notice that the blue channel is given considerably less significance in the ITU-R 601-2 luma transform that PIL uses (see the lower 114 weighting for Blue versus 299 for Red and 587 for Green) in the formula:
L = R * 299/1000 + G * 587/1000 + B * 114/1000
so the blue shades are considered darker and become black.
Another way would be to make a greyscale and a mask as above. but then choose the darker pixel at each location when comparing the original and the mask:
from PIL import Image, ImageChops
im = Image.open('start.png').convert('RGB')
grey = im.convert('L')
mask = grey.point(lambda p: 0 if p<50 else 255)
res = ImageChops.darker(im, mask.convert('RGB'))
That gives the same result as above.
Another way, pure PIL and probably closest to what you actually asked, would be to derive a luminosity value by averaging the channels:
# Load image and ensure RGB
im = Image.open('start.png').convert('RGB')
# Calculate greyscale version by averaging R,G and B
grey = im.convert('L', matrix=(0.333, 0.333, 0.333, 0))
# Point process over pixels to make mask of darker ones
mask = grey.point(lambda p: 255 if p<50 else 0)
# Paste black (i.e. 0) into image where mask indicates it is dark
im.paste(0, mask=mask)
Another approach could be to split the image into its constituent RGB channels, evaluate a mathematical function over the channels and mask with the result:
from PIL import Image, ImageMath
# Load image and ensure RGB
im = Image.open('start.png').convert('RGB')
# Split into RGB channels
(R, G, B) = im.split()
# Evaluate mathematical function over channels
dark = ImageMath.eval('(((R+G+B)/3) <= 50) * 255', R=R, G=G, B=B)
# Paste black (i.e. 0) into image where mask indicates it is dark
im.paste(0, mask=dark)
I created a function that returns a list with True if the pixel has a luminosity of less than a parameter, and False if it doesn't. It includes an RGB or RGBA option (True or False)
def get_avg_lum(pic,avg=50,RGBA=False):
num=3
numd=4
if RGBA==False:
num=2
numd=3
li=[[[0]for y in range(0,pic.size[1])] for x in range(0,pic.size[0])]
for x in range(0,pic.size[0]):
for y in range(0,pic.size[1]):
if sum(pic.getpixel((x,y))[:num])/numd<avg:
li[x][y]=True
else:
li[x][y]=False
return(li)
a=get_avg_lum(im)
The pixels match in the list, so (0,10) on the image is [0][10] in the list.
Hopefully this helps. My module is for standard PIL objects.

HSV colour range in openCV

I wrote a program that used trackbars, to find out the appropriate HSV values (range) for segmenting out the white lines from the image.
For a long time this seemed like the best shot:
But its still not very accurate, its leaving out chunks of the line...
After messing around some more, I realised something:
This is very accurate: apart from the fact that the black and white regions are swapped.
Is there any way to invert this colour scheme to swap the black and white regions?
If not, what exactly can I do to not leave out chunks of the line like the first image...I have tried out various HSV combinations and it seems like this is the closest I can get.
code:
import cv2 as cv
import numpy as np
def nothing(x):
pass
img= cv.imread("ti2.jpeg")
cv.namedWindow("image") #create a window that will contain the trackbars for HSV values
cv.createTrackbar('HMin','image',0,179,nothing)
cv.createTrackbar('SMin','image',0,255,nothing)
cv.createTrackbar('VMin','image',0,255,nothing)
cv.createTrackbar('HMax','image',0,179,nothing)
cv.createTrackbar('SMax','image',0,255,nothing)
cv.createTrackbar('VMax','image',0,255,nothing)
cv.setTrackbarPos('HMax', 'image', 179) #setting default trackbar pos for max HSV values at max
cv.setTrackbarPos('SMax', 'image', 255)
cv.setTrackbarPos('VMax', 'image', 255)
while True:
hMin = cv.getTrackbarPos('HMin','image') #get the current slider position
sMin = cv.getTrackbarPos('SMin','image')
vMin = cv.getTrackbarPos('VMin','image')
hMax = cv.getTrackbarPos('HMax','image')
sMax = cv.getTrackbarPos('SMax','image')
vMax = cv.getTrackbarPos('VMax','image')
hsv=cv.cvtColor(img, cv.COLOR_BGR2HSV)
lower=np.array([hMin,sMin,vMin])
upper=np.array([hMax,sMax,vMax])
mask=cv.inRange(hsv,lower,upper)
#result=cv.bitwise_and(frame,frame,mask=mask)
cv.imshow("img",img)
cv.imshow("mask",mask)
#cv.imshow("result",result)
k=cv.waitKey(1)
if k==27 :
break
cv.destroyAllWindows()
Test Image:
To invert the mask
mask = 255-mask # if mask is a uint8 which ranges 0 to 255
mask = 1-mask # if mask is a bool which is either 0 or 1

Replacement of circular spots by respective colors

My objective here is to replace the spot in mask_image by a color corresponding to the spot in original_image. What I did here is to find connected components and labeling them, but I can't figure out how to find the corresponding labeled spot and replace it.
How can i put the n circles in n objects and fill them by the corresponding intensities?
Any help would be appreciated.
For example, if spot in (2, 1) in mask image should be painted by color of corresponding spot in this image below.
mask image http://myfair.software/goethe/images/mask.jpg
original image http://myfair.software/goethe/images/original.jpg
def thresh(img):
ret , threshold = cv2.threshold(img,5,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
return threshold
def spot_id(img):
seed_pt = (5, 5)
fill_color = 0
mask = np.zeros_like(img)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
for th in range(5, 255):
prev_mask = mask.copy()
mask = cv2.threshold(img, th, 255, cv2.THRESH_BINARY)[1]
mask = cv2.floodFill(mask, None, seed_pt, fill_color)[1]
mask = cv2.bitwise_or(mask, prev_mask)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
#here I labelled them
n_centers, labels = cv2.connectedComponents(mask)
label_hue = np.uint8(892*labels/np.max(labels))
blank_ch = 255*np.ones_like(label_hue)
labeled_img = cv2.merge([label_hue, blank_ch, blank_ch])
labeled_img = cv2.cvtColor(labeled_img, cv2.COLOR_HSV2BGR)
labeled_img[label_hue==0] = 0
print('There are %d bright spots in the image.'%n_centers)
cv2.imshow("labeled_img",labeled_img)
return mask, n_centers
image_thresh = thresh(img_greyscaled)
mask, centers = spot_id(img_greyscaled)
There is one very simple way of accomplishing this task. First one needs to sample the value at the center of each dot in mask_image. Next, one expands this color to fill the dot in that same image.
Here is some code using PyDIP (because I know it better than OpenCV, I'm an author), I'm sure something similar can be done with OpenCV alone:
import PyDIP as dip
import cv2
import numpy as np
# Load the color image shown in the question
original_image = cv2.imread('/home/cris/tmp/BxW25.png')
# Load the mask image shown in the question
mask_image = cv2.imread('/home/cris/tmp/aqf3Z.png')[:,:,0]
# Get a single colored pixel in the middle of each spot of the mask
colors = dip.EuclideanSkeleton(mask_image > 50, 'loose ends away') * original_image
# Spread that color across the full spot
# (dilation and similar operators like this one don't work with color images,
# so we apply the operation on each channel separately)
for t in range(colors.TensorElements()):
colors.TensorElement(t).Copy(dip.MorphologicalReconstruction(colors.TensorElement(t), mask_image))
# Save the result
cv2.imwrite('/home/cris/tmp/so.png', np.array(colors))

Quickly loop over pixels numpy

I want to do the following loop through an image to remove or modify a pixel if it equals rgb value with threshold.
The goal is to remove the background of an image and feed the image to an OCR.
I have tried 2 different methods to do this.
Method 1:
Basically what I do is get the average background pixel value.
And than loop over all pixels and check which pixels equal the average background pixel.
for x in range(0, w):
for y in range(0, h):
if Pixel(img[y, x]).compare(pixel, threshold):
img[y, x] = 255
else
img[y, x] = 0
compare function will check if it >=/<= the pixel -/+ the threshold value. then if it returns true it will change the pixel to white else to black.
This works well however it is wayyyy too slow when you use bigger pictures.
Method 2:
Just use an opencv method to remove the background.
Simply:
img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
th3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 41, 2)
Results:
Feed normal image:
result method 1:
result method 2:
Feed inverted image:
result method 1:
result method 2:
The first method is way to slow and the second method only works when the image has a whitish background I guess.
I do need it for different background colors.
I found something about vectorizing the numpy array. But couldn't really found a good example about it.
To answer the question with concrete example:
#load an image as grayscale
#get the background average pixel value, it out of the scope of this question, different methods to achieve
bg_avg = get_bg_avg_px_val(img)
th = 80
background_mask = logical_and((bg_avg - th) <= img, img <= (bg_avg + th))
text_mask = logical_or((bg_avg - th) >= img, img >= (bg_avg + th))
img[selected] = 255
img[text] = 0

How to Mask an image using Numpy/OpenCV?

I have an image I load with:
im = cv2.imread(filename)
I want to keep data that is in the center of the image. I created a circle as a mask of the area I want to keep.
I created the circle with:
height,width,depth = im.shape
circle = np.zeros((height,width))
cv2.circle(circle,(width/2,height/2),280,1,thickness=-1)
How can I mask out the data outside of the circle from the original image?
masked_data = im * circle
does not work.
Use cv2.bitwise_and and pass the circle as mask.
im = cv2.imread(filename)
height,width,depth = im.shape
circle_img = np.zeros((height,width), np.uint8)
cv2.circle(circle_img,(width/2,height/2),280,1,thickness=-1)
masked_data = cv2.bitwise_and(im, im, mask=circle_img)
cv2.imshow("masked", masked_data)
cv2.waitKey(0)
circle is just a 2D array with 1.0s and 0.0s. Numpy needs help to understand what you want to do with the third dimension of your im so you must give it an extra axis and then your line would work.
masked_data = im * circle[..., np.newaxis]
But note that the masking is simply setting the color to (0, 0, 0) for things outside the circle according to your code if the image lacks an alpha-channel.
However you have another potential problem: circle will be of the default data-type (which probably will be float64 or float32. That's not good for your image, so you should change the line where you create circle to
circle = np.zeros((height, width), dtype=im.dtype)
Using NumPy assignment to an indexed array:
im[circle == 0] = [0, 0, 0]
In this case if you want to have a circular image you must write a new algorithm and first you must be able to access to the coordinates of the pixels. Then you can simply compare pixels that are not within the scope of that circle or not and replace them with some value (or NULL if it's accepted with your image format criteria).
Here is an example:
import cv2
import numpy as np
im = cv2.imread('sss.png')
def facechop(im):
height,width,depth = im.shape
#circle = np.zeros((height,width))
#print circle
x=width/2
y=height/2
circle=cv2.circle(im,(width/2,height/2),180,1,thickness=1)
#newcameramtx, roi=cv2.getOptimalNewCameraMatrix(im,10,(w,h),1,(w,h))
cv2.rectangle(im,(x-180,y-180),(x+180,y+180),(0,0,255),2)
crop_img = im[y-180:y+180,x-180:x+180]
lastim=np.equal(crop_img,circle)
#dd=np.logical_and(crop_img,circle)
for i in range(len(last_im)) :
if last_im[i].all()==False:
crop_img[i]=[0,0,0]
cv2.imshow('im',crop_img)
if __name__ == '__main__':
facechop(im)
while(True):
key = cv2.waitKey(20)
if key in [27, ord('Q'), ord('q')]:
break

Categories

Resources