PIL Converting an image's hue, then saving out in Python - 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')

Related

Set particular channel of Image to certain value according to other image

I have this image which has 4 channels. What I want to do is, to reduce this image's opacity. I want the image to remain transparent, however, I just want to reduce the opacity of the VW logo part. (Using opencv/numpy and python)
Here's what I tried :
logo = cv2.imread(FILE_PATH, cv2.IMREAD_UNCHANGED)
logo[:, :, 3] = 50
But this assigns the value 50 to all over the image, giving me this result. Notice how the remaining part of the image is no more transparent (I want it to remain like the original one.)
I thought of something like:
#This is my logic NOT ANY CODE, I want to do something like this:
if (any of) other 3 channels are non zero, make alpha channel of that pixel = 50.
else, keep that pixel as it is. (This pixel would be part of logo)
Is there any way of achieving this result, by using opencv / numpy in python? My last option would be to iterate through all the pixels and look for above conditions, but I feel that would be inefficient.
This answer is exactly what I DON'T want. I want only the logo part(Colored pixels)'s alpha channel to be set to 50.
Just fleshing out the comment from #yann-ziselman...
Here are the RGBA channels of your image side-by-side, with a red border so you can see the extent on Stack Overflow's background:
Here's how to do what you ask:
import cv2
# Load image including alpha channel
im = cv2.imread('logo.png', cv2.IMREAD_UNCHANGED)
# Set alpha channel to 50 anywhere none of the BGR channels is non-zero
im[(im[..., :3]!=0).any(2), 3] = 50
# Save result
cv2.imwrite('result.png', im)
Result
Result split into RGBA channels side-by-side

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.

Python - Image from numpy array losing true pixels

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.

extract the dress from image with python

I was doing some research about how can i crop the dress in this image (see image1) using python and some other libraries, so i need to do this for different images with many models on the photo, they will have different sizes and shapes so i need to do something generic that could take the image, analize it and remove all but the dress,
image1
I have a code that takes this image and do some mask around the model's shape and put the alpha channel so i get this (image2):
image2
As you can see this is the result of my code, but is not what i need, i really need to remove all the colors around the model, if possible all the colors around the dress, and need to be generic.. i.e. should work with different models that have different shapes and sizes
this is the code i have written on python using PIL and numpy libraries, i was using python 3.4
import numpy
from numpy import array
from PIL import Image
#import cv2
# read image as RGB and add alpha (transparency)
im = Image.open("one.jpg").convert("RGBA")
# convert to numpy (for convenience)
imArray = numpy.asarray(im)
# create mask (zeros + circle with ones)
center = (100,100)
radius = 100
mask = numpy.zeros((imArray.shape[0],imArray.shape[1]))
for i in range(imArray.shape[0]):
for j in range(imArray.shape[1]):
#if (i-center[0])**2 + (j-center[0])**2 < radius**2:
# mask[i,j] = 1
if ((j > 110 and j<240 and i>65 ) or (j > 440 and j<580 and i>83 )):
mask[i, j] = 1
"""
lower = numpy.array([0,0,0])
upper = numpy.array([15, 15, 15])
shapeMask = cv2.inRange(imArray, lower, upper)
"""
# assemble new image (uint8: 0-255)
newImArray = numpy.empty(imArray.shape,dtype='uint8')
# colors (three first columns, RGB)
newImArray[:,:,:3] = imArray[:,:,:3]
# transparency (4th column)
newImArray[:,:,3] = mask*255
# back to Image from numpy
newIm = Image.fromarray(newImArray, "RGBA")
newIm.save("one2.png")
The result should be a PNG image with all transparent except the model, or the dress if possible
As you can see im only making a static mask that always will be in the same place, and it is rectangular, not adjusted to the model, let me know if you need more explanation of what i need
Thanks a lot!
cesar
This is a very hard problem, especially when you do not know what the background is going to be and when the background has shadows.
The netting of the dress is also going to be lost in part or whole as might the areas between the body and the arms.
Here is an attempt using ImageMagick. But OpenCV has similar commands.
Input:
First, blur the image slightly and then extract the Hue channel from HCL colorspace.
Second I change all white colors within a tolerance of 30% to black.
Third I perform Otsu thresholding using one of my scripts.
Fourth I do a small amount of morphology close.
Fifth I use connected components processing to remove all regions smaller than 150 pixels in area. In OpenCV, that would be blob detection (SimpleBlobDetection) and invert (negate) the result as a mask.
Last, I put the mask into the alpha channel of the input to make the background transparent (which will show up white here).
convert image.jpg -blur 0x1 -colorspace HCL -channel r -separate hue.png
convert hue.png -fuzz 30% -fill black -opaque white filled.png
otsuthresh -g save filled.png thresh.png
convert thresh.png -morphology open disk:1 morph.png
convert morph.png -type bilevel \
-define connected-components:mean-color=true \
-define connected-components:area-threshold=150 \
-connected-components 4 \
-negate \
mask.png
convert image.jpg mask.png -alpha off -compose copy_opacity -composite result.png
Here are the image for the steps:
Hue Image:
Filled Image after converting white to black:
Otsu Thresholded Image:
Mask:
Result:
As you can see, the result is not very good at keeping to the outline of the woman and the dress, especially in the hair and the netting of the dress.
You might investigate OpenCV GrabCut Foreground Extaction at https://docs.opencv.org/3.4/d8/d83/tutorial_py_grabcut.html
If you can assume the background is fairly simple, (uniform in color, or only nearly horizontal lines) you could do edge detection, and the remove all pixels that's outside the first occuring edge.
Any edge detection filter should be sufficient, But I would probably go for a simple high pass filter, that enhances vertical edges only.
You'r merely trying to figure out where the models silhouette is!
Then remove all the pixels from the frame, going inwards, till the first edge is encountered. (cleans up background outside model).
To remove holes between arms and dress etc.. Median the color value of the removed pixels, to get the background color for this row, then remove pixels with a color value close to the found mean on the remainder of the row.
removals should be done via building a mask image, and then subtract it from the image, as the mask can be used for an opacity / alpha channel afterwards.
risks:
if dress or model is too close in colour to the background, holes will appear in the model/dress.
patterns in background disturbs algorithm and leaves rows untouched.
noise in the background can cause the removal or colour value to be set from pixels close to the frame only.
some of those problems can be minimized by opening and closing the deletion mask.
others by a spacial median filter prior to edge detection.
First step is to calculate the background color(s). Get a block of 50*50 find the variance, shift 10-20 pixels to right and get another block, calculate its variance as well and many more. Store the variances in an array. (and their means as well).
The ones with lowest variance are background colors, you will see bunch of those. After finding the background color, choose 5*5 blocks and if the variance is very small and its mean is equal to one of the backgrounds (i.e similar characteristic), then make it white or do whatever you want.
That is just my intuition, I'm not professional about image processing.
You can give this a try in order to extract dress from image of a model.
The link is github repo of image-conditional image generation model called PixelDTGAN. This model will perform a challenging task of generating a piece of clothing from an input image of a dressed person
This model transfers an input domain to a target domain in semantic level, and generates the target image in pixel level.
To generate realistic target images, the real/fake-discriminator is used as in Generative Adversarial Nets, a domain-discriminator is used to make the generated image relevant to the input image.

How to extract green channel from RGB image in Python using Scikit-Image library?

I am extremely new to scikit-image (skimage) library in Python for image processing (started few minutes ago!). I have used imread to read an image file in a numpy.ndarray. The array is 3 dimensional where the size of the third dimension is 3 (namely one for each of Red, Green and Blue components of an image).
rgb_image = imread("input_rgb_image.jpg")
rgb_image.shape # gives (1411L, 1411L, 3L)
I tried to extract green channel as:
green_image = rgb_image[:,:,1]
But when I write this image matrix to an output file as:
imsave("green_output_image.jpg",green_image)
I get an image which doesn't really look ONLY green!
What you are extracting is just a single channel and this shows you how much green colour each pixel has. This will ultimately be visualized as a grayscale image where darker pixels denote that there isn't much "greenness" at those points and lighter pixels denote that there is a high amount of "greenness" at those points.
If I'm interpreting what you're saying properly, you wish to visualize the "green" of each colour. In that case, set both the red and blue channels to zero and leave the green channel intact.
So:
green_image = rgb_image.copy() # Make a copy
green_image[:,:,0] = 0
green_image[:,:,2] = 0
Note that I've made a copy of your original image and changed the channels instead of modifying the original one in case you need it. However, if you just want to extract the green channel and visualize this as a grayscale image as I've mentioned above, then doing what you did above with the setting of your green_image variable is just fine.

Categories