Python - Image from numpy array losing true pixels - python

I have a jpg picture of a face, I need to access the picture pixel by pixel (know what value is at each pixel), and use some sort of DFS to change background color.
image = Image.open("pic.jpg")
image = np.array(image)
First of all, why is the shape of the array (473, 354, 3)? It doesn't make sense to me.
When I do
plt.imshow(image.reshape(473, -1))
plt.show()
I get a picture that looks like the following, which consists of only red, blue and yellow colors (and a mixture of the three?)
This means that the values in the array are not what I can reliably use to make my edge detection decisions.
Why and what should I do?
I want the pixel values to reflect the true color of the original image, not like above.
The background in the actual picture is kinda white, and I want them and all other pixel values to stay that way, so I can implement my algorithm.

The 3 is because each color (blue, green red) gets its own entry in the array.
For edge detection, you would might do best to collapse the image down to B&W. OpenCV has cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) that will do the trick.

Related

Is there another way to fill the area outside a rotated image with white color? 'fillcolor' does not work with older versions of Python

I want to rotate a black and white image. I am trying to use the rotate function as follows:
image.rotate(angle, fillcolor=255)
I am required to older versions of Python and Pillow, and they do not support the 'fillcolor' argument. I cannot upgrade to the newer versions due to certain restrictions and cannot use any external libraries.
Is there another way to fill the area outside the rotated image with white color using Pillow?
Rotated image has black color in the area outside the rotated part. I want to fill it with white color.
Original : Original image
Rotated :Rotated image
You can try Interpolating the Original Image, with the cropped one via Image.composite() to get rid of the black bars/borders.
from PIL import Image
img = Image.open(r"Image_Path").convert("RGBA")
angle = 30
img = img.rotate(angle)
new_img = Image.new('RGBA', img.size, 'white')
Alpha_Image = Image.composite(img, new_img, img)
Alpha_Image = Alpha_Image.convert(img.mode)
Alpha_Image.show()
The above code takes in an Image, converts it into mode RGBA (Alpha is required for this process), and then rotates the Image by 30 degrees. After that It creates a empty Image object of mode RGBA of the same dimensions as the original image, with each pixel having a default value of 255 each channel (i.e Pure white for RGB, and Full Opacity in the context of Alpha/Transparency). Then Interpolates the original image with this empty one using the mask of original Image (we are using the transparency mask of the first image). This results in the Desired images, where black bars/edges are replaced by white. In the end we convert the image color space to the original one.
ORIGINAL IMAGE:-
IMAGE AFTER ROTATING 30 DEGREES:-
An awkward option that has always worked for me, seeing as with my tools I always get a light gray "border" around the rotated image that interferes with filling:
add a border on the non-rotated image and use the fill color with that border.
The bordering operation is lossless and filling will be exact (and easy).
rotate the bordered image. The seam will now also be correct (but not exact unless you
rotate by 45° or 90°).
calculate the size of the rotated border using trigonometry. The result will not be exact (i.e. "131.12 pixel"). Usually you can do this in reverse, starting with an exact border on the rotated image and calculating the border you need to add, and adjust the border width so that the nonrotated border is exact. Example: with a rotated border of 170 pixels you get a nonrotated border of 140.3394 pixels. So you use a 510 pixel rotated border, resulting in the need to add a 421.018 pixel nonrotated border. This is close enough to 421 pixels that it is acceptable.
remove the rotated border.
This also helps avoiding some artefacts near the cut parts of the image that fall off the rotated image.
It has the drawback that you end up with a more massive rotation, with higher memory expenditure and computation time, especially if you use larger borders to increase precision.
Edit: As no external libraries are allowed, I would suggest cropping the rectangle you want and pasting it onto the original image, this could be done with magic numbers (of the rectangle's coordinates), this works for me (you might will need to tweek a little)
im = Image.open("mFul4.png")
rotated = im.rotate(105)
box = (55, 65,200,210)
d = rotated.crop(box=box)
im.paste(d, box=box)
im.save("ex.bmp" )
and the output
Edit2: This is the ugliest way, but it works, you might need to tweak the magic numbers a bit to have it more precise, I was working on your given image, so couldn't tell when i'm overdoing it. It produces the same output
from PIL import Image
im = Image.open("mFul4.png")
angle=105
cos = 0.240959049 # -cos(angle)
d = im.rotate(angle)
pix = d.load()
tri_x = 120
for i in range(4): # 4 triangles
for j in range(tri_x, -1, -1):
for k in range(int((tri_x-j)*cos)+1, -1, -1):
x,y =( j, k )if i <1 else (d.size[0]-j-1, d.size[1]-k-1)
if i in [2,3]:
y, x = (d.size[0] - j-2 , k) if i <3 else (j, d.size[1] - k)
pix[x,y] = (255, 255, 255, 255)
d.show()

keeping all details of object removing the shape in the smoothest and most efficient way

I have some images for which I also have their mask (green in the picture). I am producing a bounding box (dot line in the picture) around the object, and take only this part of the image.
Now I would like to replace the gray part with pixel that extend the car color in the most natural way. For example, taking the same color as the closest car pixel. At the end I would like to have an image with all the car details but without any shape anymore.
I tried to simple inverse the mask, so that the mask represent the gray pixel around the car, and then use the 'inpaint' function from opencv to paint this new mask with adequate color:
result = cv2.inpaint(car_image,new_mask,50,cv2.INPAINT_NS)
Its not working well as we clearly still see the borders all around the car.
Any hints would be greatly appreciated. I am working on python and it would need to be quite efficient as I have a huge number of images.
Here is a good working solution, its a fucntion that given an image with zero value outside of the mask, it output a similar image where instead of the zero values the most adequate color is choosen so that we keep all details of the object but removing the form:
#replace black pixel by smoothing with adequate color to keep all info, removing shape
def remove_shape_keep_all_info(img):
#create mask (s.t. Non-zero pixels indicate the area that needs to be inpainted)
mask_ = np.array([[1 if j==0 else 0 for j in i] for i in cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)]).astype('uint8')
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7,7))
mask_ = cv2.dilate(mask_, kernel, iterations=4)
result = cv2.inpaint(img,mask_,5,cv2.INPAINT_TELEA) #,cv2.INPAINT_TELEA, INPAINT_NS
return (result)

Change the colors within certain range to another color using OpenCV

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.

Using OpenCV Python, How would you make all black pixels transparent, and then overlay it over original image

I'm trying to make a colored mask, white.
And my idea is to:
make black pixels transparent in the mask
merge the two images
crop images
so then my original masked area will be white.
What kind of OpenCV python code/methods would I need?
Like so:
Original
Mask
Desired result (mocked up - no green edges)
Instead of
I suppose to do a color threshold to get the mask itself.
The result I got in a first quick and dirty attempt with Hue 43-81, Saturation 39-197 and Brightness from 115-255 is:
The next step is a whole fill algorithm to fill the inside of the mask. Note that also one small area to the right is selected.
The next step is a substraction of the two results (mask-filled_mask):
Again fill the wholes and get rid of the noisy pixels with binary opening:
Last mask the image with the created mask.
Every step can be adjusted to yield optimal results. A good idea is to try the steps out (for example with imageJ) to get your workflow set up and then script the steps in python/openCV.
Refer also to http://fiji.sc/Segmentation.
I am assuming your mask is a boolean numpy array and your 2 images are numpy arrays image1 and image2.
Then you can use the boolean array as multiplier.
overlay= mask*image1 + (-mask)*image2
So you get the "True" pixels from image1 and the False pixels from image2

PIL Converting an image's hue, then saving out in Python

I'm loading and saving out images with PIL just fine but I can't seem to change the "overall" hue of a given image ~ google and here revealed an answer, sort of, with the numpy module, but thats not an option for me
It should be quite simple, given a gray image with alpha, I'd like to make it's hue red
I think you want a mono-hue image. Is this true?
It's not clear what you want done with the existing bands (alpha and greyscale/level). Do you want alpha to remain alpha and the greyscale to become red saturation? Do you want the alpha to become your red saturation? Do you want greyscale to be the image lightness and the alpha to become the saturation?
Edit:
I've changed the output based on your comment. You wanted the darkest shade of the greyscale band to represent fully saturated red and the lightest grey to represent white (in other words full-saturated with all colors). You also indicated that you wanted alpha to be preserved as alpha in the output. I've made that change too.
This is possible with some band swapping:
import Image
# get an image that is greyscale with alpha
i = Image.open('hsvwheel.png').convert('LA')
# get the two bands
L,A = i.split()
# a fully saturated band
S, = Image.new('L', i.size, 255).split()
# re-combine the bands
# this keeps tha alpha channel in the new image
i2 = Image.merge('RGBA', (S,L,L,A))
# save
i2.save('test.png')

Categories