Computing a contrast map - python

I am trying to compute the contrast around each pixel in an NxN window and saving the results in a new image where each pixel in the new image is the contrast of the area around it in the old image. From another post I got this:
1) Convert the image to say LAB and get the L channel
2) Compute the max for an NxN neighborhood around each pixel
3) Compute the min for an NxN neighborhood around each pixel
4) Compute the contrast from the equation above at each pixel.
5) Insert the contrast as a pixel value in new image.
Currently I have the following:
def cmap(roi):
max = roi.reshape((roi.shape[0] * roi.shape[1], 3)).max(axis=0)
min = roi.reshape((roi.shape[0] * roi.shape[1], 3)).min(axis=0)
contrast = (max - min) / (max + min)
return contrast
def cm(img):
# convert to LAB color space
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
# separate channels
L, A, B = cv2.split(lab)
img_shape = L.shape
size = 5
shape = (L.shape[0] - size + 1, L.shape[1] - size + 1, size, size)
strides = 2 * L.strides
patches = np.lib.stride_tricks.as_strided(L, shape=shape, strides=strides)
patches = patches.reshape(-1, size, size)
output_img = np.array([cmap(roi) for roi in patches])
cv2.imwrite("labtest.png", output_img)
The code complains about the size of roi. Is there a better (pythonic) way of doing what I want?

You may use Dilation and Erosion morphological operations for finding the max and min for NxN neighborhood.
Dilation of NxN is equivalent to maximum of NxN neighborhood.
Erosion of NxN is equivalent to minimum of NxN neighborhood.
Using morphological operations makes the solution much simpler than "manually" dividing the image into small blocks.
You may use the following stages:
Convert to LAB color space and get L channel.
Use "dilate" morphological operation (dilate is equivalent to finding maximum pixel in NxN neighborhood).
Use "erode" morphological operation (dilate is equivalent to finding maximum pixel in NxN neighborhood).
Convert images to type float (required before using division operation).
Compute contrast map (range of contrast map is [0, 1]).
Convert contrast map to type uint8 with rounding - the conversion loosed accuracy, so I can't recommend it (but I assume you need the conversion for getting the output as an image).
Here is a complete code sample:
import numpy as np
import cv2
size_n = 5 # NxN neighborhood around each pixel
# Read input image
img = cv2.imread('chelsea.png')
# Convert to LAB color space
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
# Get the L channel
L = lab[:, :, 0]
# Use "dilate" morphological operation (dilate is equivalent to finding maximum pixel in NxN neighborhood)
img_max = cv2.morphologyEx(L, cv2.MORPH_DILATE, np.ones((size_n, size_n)))
# Use "erode" morphological operation (dilate is equivalent to finding maximum pixel in NxN neighborhood)
img_min = cv2.morphologyEx(L, cv2.MORPH_ERODE, np.ones((size_n, size_n)))
# Convert to type float (required before using division operation)
img_max = img_max.astype(float)
img_min = img_min.astype(float)
# Compute contrast map (range of img_contrast is [0, 1])
img_contrast = (img_max - img_min) / (img_max + img_min)
# Convert contrast map to type uint8 with rounding - the conversion loosed accuracy, so I can't recommend it.
# Note: img_contrast_uint8 is scaled by 255 (scaled by 255 relative to the original formula).
img_contrast_uint8 = np.round(img_contrast*255).astype(np.uint8)
# Show img_contrast as output
cv2.imshow('img_contrast', img_contrast_uint8)
cv2.waitKey()
cv2.destroyAllWindows()
Input image:
L image:
img_max:
img_min:
Contrast map img_contrast_uint8:

Related

FFT image clearing

I am using python and I need to implement a function to clear a pattern noise from images, I write this function and I get an OK result on some images but on others, I get a bad result, can U guys help me to improve the code?
I can't use cv2, just 'numpy' and 'from scipy.signal import convolve'
def fft_clean(img1: np.array, img2: np.array, img3: np.array, img4: np.array) \
-> (np.array, np.array, np.array, np.array):
'''
This function receives 4 grayscale images and clean them using FFT algorithm.
Args:
img1: image array in float format (range: 0..1) - the source grayscale
image.
img2: image array in float format (range: 0..1) - the source grayscale
image.
img3: image array in float format (range: 0..1) - the source grayscale
image.
img4: image array in float format (range: 0..1) - the source grayscale
image.
Returns:
img_c1: array in int format (values: 0, 1) - the cleaned image.
img_c2: array in int format (values: 0, 1) - the cleaned image.
img_c3: array in int format (values: 0, 1) - the cleaned image.
img_c4: array in int format (values: 0, 1) - the cleaned image.
'''
######################################################################
# TODO: Implement The fft algorithm.
######################################################################
def clean(img):
# perform FFT on the input image
f = fft2(img)
# shift the FFT result to center the image
f = fftshift(f)
# get the magnitude of the FFT result
mag = np.abs(f)
# find the maximum magnitude value
max_val = np.max(mag)
# set a threshold that eliminates most of the noise and leaves the line patterns
threshold = max_val * 0.1
# create a binary mask with the same size as the image
mask = np.zeros_like(mag)
# set the values in the mask corresponding to the line patterns to 1
mask[np.where(mag < threshold)] = 1
# apply the mask to the FFT result
f *= mask
# shift the result back to its original position
f = ifftshift(f)
# perform the inverse FFT to obtain the cleaned image
img_c = ifft2(f)
return img_c
# clean each of the input images and store the result
img_c1 = clean(img1)
img_c2 = clean(img2)
img_c3 = clean(img3)
img_c4 = clean(img4)
######################################################################
# END OF YOUR CODE #
######################################################################
return img_c1.real, img_c2.real, img_c3.real, img_c4.real
as u can see img1 have washed colors, img2 is great, img3 is ok but the color is little bit washed, and img4 look terrible
https://github.com/dnevo/ImageProcessing/tree/main/images
fft1 - ff4.tiff
I change the clean function to this:
def clean(img):
# perform FFT on the input image
f = fft2(img)
# shift the FFT result to center the image
f = fftshift(f)
# get the magnitude of the FFT result
mag = np.abs(f)
# find the maximum magnitude value
max_val = np.max(mag)
# set a threshold that eliminates most of the noise and leaves the line patterns
threshold = max_val * 0.094
# create a binary mask with the same size as the image
mask = np.zeros_like(mag)
# set the values in the mask corresponding to the line patterns to 1
mask[np.where(mag < threshold)] = 1
# apply the mask to the FFT result
f *= mask
# shift the result back to its original position
f = ifftshift(f)
# perform the inverse FFT to obtain the cleaned image
img_c = ifft2(f)
return img_c
I just change this line
threshold = max_val * 0.094
instead of 0.1, I set 0.094 and the last pic is ok now
but the first is still washed.
I also try to change to this:
def clean(img):
# perform FFT on the input image
f = fft2(img)
# shift the FFT result to center the image
f = fftshift(f)
# get the magnitude of the FFT result
mag = np.abs(f)
# set a threshold that eliminates most of the noise and leaves the line patterns
threshold = np.max(mag) * 0.094
# create a binary mask with the same size as the image
mask = np.zeros_like(mag)
# set the values in the mask corresponding to the line patterns to 1
mask[np.where(mag < threshold)] = 1
# find the coordinates of the peaks in the magnitude spectrum that are above the threshold
peaks = np.array(np.where(mag > threshold)).T
# remove the significant peaks that are not at the origin by setting their values in the mask to 0
for peak in peaks:
if peak[0] != mag.shape[0]//2 or peak[1] != mag.shape[1]//2:
mask[peak[0], peak[1]] = 0
# apply the mask to the FFT result
f *= mask
# shift the result back to its original position
f = ifftshift(f)
# perform the inverse FFT to obtain the cleaned image
img_c = ifft2(f)
return img_c
like #CrisLuengo suggest but I get the same results
please let me know where I can improve the code and get better results.
thanks!!

Remove CMYK colors to keep only black from a PNG

I'm trying to remove the colors from a PNG there is a way to do it ? My goal is to import my image in a PDF using Python, I tryed first with an SVG file but impossible to import, nothing appears with no error. So I wanted to try with a PNG but still hard to import.
Now I have an image with these percentage of colors :
And my final result would be this :
I already tried with openCV but no result from it, I'm looking for a solution since few days.
file = "app\\static\\img\\Picto CE_MAROC_H_6mm.png"
src = cv2.imread(file, cv2.IMREAD_UNCHANGED)
src[:,:,2] = np.zeros([src.shape[0], src.shape[1]])
cv2.imwrite(file,src)
Thanks in advance for your help ! :)
What does it mean to only have a K channel?
Most applications use RGB or RGBA, whereas the CMYK color space is typically for printed material. We should translate what does it mean for an image to only use the K channel.
First, let's look the formulas to convert the CMYK colorspace to RGB. We will assume that C, M, K are on a 0-100 integer scale:
R = 255 * (1 - C/100) * (1 - K/100)
G = 255 * (1 - M/100) * (1 - K/100)
B = 255 * (1 - Y/100) * (1 - K/100)
Since we only care for the K channel, we will set C, Y, and M to 0. This simplifies the formulas to:
R = 255 * (1 - K/100)
G = 255 * (1 - K/100)
B = 255 * (1 - K/100)
Notice that R = G = B when only the K channel is set. This produces a gray monochrome throughout the image, effectively making it grayscale. As such, the goal would be to produce a grayscale image given a RGBA image input.
Converting color to grayscale
Converting a color to its grayscale component is simply done by preserving the luminance of the original image in a gray monochrome palette. To do so, a formula must be defined which takes in a RGB input and returns a single value Y, creating a YYY color on the gray monochrome scale. This can simply be done by assigning each color a coefficient to scale how much an effect each has on luminance. Since the human eye is most sensitive to G, R, then B, we would want to assign a high coefficient to G and a low coefficient to B. The most common grayscale calculation used is luma coding for color TV and video systems:
Y = round(0.229 * R + 0.587 * G + 0.114 * B)
Converting an image to only use the K channel in Python
Now knowing the above information, we can convert an image to only use the K channel. For this, we can use imageio which can provide pixel information in RGB format. Since image data is given as an n dimensional array, we can also use numpy to abstract any loops needed to apply a grayscale to every pixel.
I will be using the imageio.v3 module as that is the most recent API as of this post. Loading in the image can be done by calling imageio.v3.imread and passing in the location of the image.
First, we want to get a luminance value for each pixel in the image. This can be done by taking the dot product of the image and the coefficients of the luminance formula. This will produce a 2D array as (height, width, RGB) x (RGB) = (height, width). We also need to round the values and cast each to a unsigned 8-bit integer to get our values into the 0-255 integer color range.
import numpy as np
# For some image `im` loaded by `#imread`
# The coefficients for converting an RGB color to its luminance value
grayscale_coef = [0.299, 0.587, 0.114]
# Create a 2D array where any pixel (height, width) translates to a single luminance value
grayscale = np.dot(im, grayscale_coef)
# Round the each luminance value and convert to a 0-255 range
grayscale = np.round(grayscale).astype(np.uint8)
Saving as CMYK
Now that we have the value to put into the K channel, we need to reconstruct the 3D array setting the CMY channels to 0 and then outputting to an image format that supports CMYK (JPG, TIFF, etc.). For this, we can use pillow.
from PIL import Image
# Create the CMY channels initialized to 0
cmy = np.zeros(grayscale.shape + (3,))
# Stack the CMY and K channels together
# Cast type to unsigned byte to avoid channel turning completely black
cmyk = np.dstack((cmy, grayscale)).astype(np.uint8)
# Read image from CMYK array buffer
result = Image.fromarray(cmyk, mode="CMYK")
# Save image in a supported format
result.save("<filename_here>.jpg")

How can I remove these parallel lines noise on my image using opencv

I'm new to opencv and I m trying to remove all these diagonal parallel lines that are noise in my image.
I have tried using HoughLinesP after some erosion/dilatation but the result is poo (and keeping only the one with a near 135 degree angle).
img = cv2.imread('images/dungeon.jpg')
ret,img = cv2.threshold(img,180,255,0)
element = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
eroded = cv2.erode(img,element)
dilate = cv2.dilate(eroded, element)
skeleton = cv2.subtract(img, dilate)
gray = cv2.cvtColor(skeleton,cv2.COLOR_BGR2GRAY)
minLineLength = 10
lines = cv2.HoughLinesP(gray, 1, np.pi/180, 1, 10, 0.5)
for line in lines:
for x1,y1,x2,y2 in line:
angle = math.atan2(y2-y1,x2-x1)
if (angle > -0.1 and angle < 0.1):
cv2.line(img,(x1,y1),(x2,y2),(0,255,0),1)
cv2.imshow("result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
My thinking here was to detect these lines in order to remove them afterwards but I m not even sure that's the good way to do this.
I guess you are trying to get the contours of the walls, right? Here’s a possible path to the solution using mainly spatial filtering. You will still need to clean the results to get where you want. The idea is to try and compute a mask of the parallel lines (high-frequency noise) of the image and calculate the difference between the (binary) input and this mask. These are the steps:
Convert the input image to grayscale
Apply Gaussian Blur to get rid of the high-frequency noise you are trying to eliminate
Get a binary image of the blurred image
Apply area filters to get rid of everything that is not noise, to get a noise mask
Compute the difference between the original binary mask and the noise mask
Clean up the difference image
Compute contours on this image
Let’s see the code:
import cv2
import numpy as np
# Set image path
path = "C://opencvImages//"
fileName = "map.png"
# Read Input image
inputImage = cv2.imread(path+fileName)
# Convert BGR to grayscale:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
# Apply Gaussian Blur:
blurredImage = cv2.GaussianBlur(grayscaleImage, (3, 3), cv2.BORDER_DEFAULT)
# Threshold via Otsu:
_, binaryImage = cv2.threshold(blurredImage, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# Save a copy of the binary mask
binaryCopy = cv2.cvtColor(binaryImage, cv2.COLOR_GRAY2BGR)
This is the output:
Up until now you get this binary mask. The process so far has smoothed the noise and is creating thick black blobs where the noise is located. Again, the idea is to generate a noise mask that can be subtracted to this image.
Let’s apply an area filter and try to remove the big white blobs, which are NOT the noise we are interested to preserve. I’ll define the function towards the end, for now I just want to present the general idea:
# Set the minimum pixels for the area filter:
minArea = 50000
# Perform an area filter on the binary blobs:
filteredImage = areaFilter(minArea, binaryImage)
The filter will suppress every white blob that is above the minimum threshold. The value is big because in this particular case we are interested in preserving only the black blobs. This is the result:
We have a pretty solid mask. Let’s subtract this from the original binary mask we created earlier:
# Get the difference between the binary image and the mask:
imgDifference = binaryImage - filteredImage
This is what we get:
The difference image has some small noise. Let’s apply the area filter again to get rid of it. This time with a more traditional threshold value:
# Set the minimum pixels for the area filter:
minArea = 20
# Perform an area filter on the binary blobs:
filteredImage = areaFilter(minArea, imgDifference)
Cool. This is the final mask:
Just for completeness. Let’s compute contours on this input, which is very straightforward:
# Find the big contours/blobs on the filtered image:
contours, hierarchy = cv2.findContours(filteredImage, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
# Draw the contours on the mask image:
cv2.drawContours(binaryCopy, contours, -1, (0, 255, 0), 3)
Let’s see the result:
As you see it is not perfect. However, there’s still some room for improvement, perhaps you can polish a little bit more this idea to get a potential solution. Here's the definition and implementation of the areaFilter function:
def areaFilter(minArea, inputImage):
# Perform an area filter on the binary blobs:
componentsNumber, labeledImage, componentStats, componentCentroids = \
cv2.connectedComponentsWithStats(inputImage, connectivity=4)
# Get the indices/labels of the remaining components based on the area stat
# (skip the background component at index 0)
remainingComponentLabels = [i for i in range(1, componentsNumber) if componentStats[i][4] >= minArea]
# Filter the labeled pixels based on the remaining labels,
# assign pixel intensity to 255 (uint8) for the remaining pixels
filteredImage = np.where(np.isin(labeledImage, remainingComponentLabels) == True, 255, 0).astype('uint8')
return filteredImage

Sum of the intensity values of all pixels in image segment

According to the formula that is shown below, I need to calculate an average threshold value by dividing the sum of intensity values in segment on the number of pixels in segment.
where Xi' is a binary mask (structure_mask), |Xi'| is a number of ones (xi_modulus).
I(x,y) is a pixel intensity.
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
...
...
structure_mask = np.logical_and(magnitude_mask, intensity_mask).astype(np.uint8)
xi_modulus = np.count_nonzero(structure_mask.all(axis=2))
intensity_sum # = ??
How to calculate the sum of intensities with numpy?
EDITED: Based on the #HansHirse's answer I've tried to do the following:
thresh_val = np.mean(img_gray[structure_mask])
And I've got IndexError: too many indices for array
Where structure_mask was of shape (1066, 1600,1) and img_gray -
(1066,1600)
UPDATED: Just a dummy mistake. Shape mismatch was fixed by proper indexing
structure_mask = np.logical_and(magnitude_mask, intensity_mask)[:, :, 0]
Using NumPy's boolean array indexing, you can easily access the desired values. You just need to pay attention, that your mask (or segment) is of NumPy's bool_ type.
Let's see this short code snippet, where I compare the mean obtained from np.mean with the one explicitly calculated by the given formula:
import cv2
import numpy as np
# Some artificial image
img = np.uint8(255 * np.tile(np.linspace(1, 0, 400), (400, 1)))
cv2.imshow('img', img)
# Some mask (or segment)
mask = np.zeros((400, 400), np.uint8)
mask[10:390, 10:30] = 255
cv2.imshow('mask', mask)
# Convert mask to bool_ type
mask = np.bool_(mask)
# Calculate mean by NumPy's mean
mean = np.mean(img[mask])
print('mean by np.mean:\n', mean)
# Calculate mean explicitly by given formula
mean = np.sum(img[mask]) / np.count_nonzero(mask)
print('mean by formula:\n', mean)
cv2.waitKey(0)
cv2.destroyAllWindows()
Outputs (images omitted here):
mean by np.mean:
242.05
mean by formula:
242.05
Hope that helps!
numpy supports logical indexes so
magnitude_mask[intensity_mask].mean()
will give you what you want.
if you insist on having the sum use
magnitude_mask[intensity_mask].sum()

Image de-blurring

This post is divided in two
Part One
I have a little issue converting an image from grayscale back to RGB.
Image in question:
I use this code to convert it:
equ = cv2.cvtColor(equ, cv2.COLOR_GRAY2RGB)
without any success though...
Part Two
Moreover I need to de-blur such image. Here I found some code that uses a wiener filter to do so, but when I implement it it doesn't seem to work effectively. Here is the code:
psf = np.ones((5, 5)) / 25
img = convolve2d(equ, psf, 'same')
img += 0.1 * img.std() * np.random.standard_normal(img.shape)
#deconvolved_img = restoration.wiener(img, psf, 1100)
deconvolved = restoration.wiener(img, psf, 1, clip=False)
plt.imshow(deconvolved, cmap='gray')
and this is the output:
Any help for the two problems is greatly appreciated!
To equalize a color image, it seems a common thing to do is
convert the image to HSV or YUV
split the image into separate components (e.g. H, S, V)
equalize on Value channel (or all three if you want)
merge the channels back together
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
split = cv2.split(hsv) # split is a 3D array containing H S V info
split[2] = cv2.equalizeHist(split[2])
hsv = cv2.merge(split)
img = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
For "deblurring", I sometimes use an unsharp mask. From the Wikipedia page on unsharp masking, the formula for this operation is
sharpened = original + (original − blurred) × amount
which can be rearranged to
sharpened = original×(1 + amount) + blurred×(-amount)
Wikipedia says a good starting point for amount is 0.5 to 1.5. In my app I have a spinbox that let's it vary between 0 and 10. For blurring I use a Gaussian blur with kernel size varying from 1 to 31 (must be odd and integer). To do the matrix math, I prefer to use OpenCV functions because they are often faster than NumPy and they will usually autoscale output to values between 0 and 255 (e.g. for 8 bit and 8 bit/3 channel images). Here we use addWeighted which does
dst = src1*alpha + src2*beta + gamma;
amount = 1.5
ksize = 3
blur = cv2.GaussianBlur(img, ksize, 0, 0)
unsharp = cv.addWeighted(img, 1 + amount, blur, -amount, 0)

Categories