Image Processing to remove noise from image - python-3.x

I am using opencv to do image processing on image.
I would like to transform my image in black and white only, but there is some gray color (noise) that I would like to remove
Here is my image:
I would like to have an Image in white and black only to get clearly the text:
"
PARTICIPATION -3.93 C
Redevance Patronale -1.92 C
"
I have tried to change the threshold of the image with OpenCV but without success
#grayscale
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
#binary
ret,thresh = cv2.threshold(gray,175,255,cv2.THRESH_BINARY_INV)

I think you mean to remove the noise of the image. For this you can choose a lower threshold value. I choose 64 using ret,thresh = cv2.threshold(img,64,255,cv2.THRESH_BINARY) and I got this result:
But this is not that clear and letters are very thin so we use cv2.erode. This gives:
and now we perform cv2.bitwise_or between original image and eroded image to obtain noise free image.
The full code used is
img = cv2.imread('grayed.png', 0)
ret,thresh = cv2.threshold(img,64,255,cv2.THRESH_BINARY)
kernel = np.ones((5, 5), np.uint8)
erode = cv2.erode(thresh, kernel, iterations = 1)
result = cv2.bitwise_or(img, erode)

Your colour conversion converts to an RGB image with grey colour (according to GIMP it is still an RGB image). The opencv documentation says that the image must be grey scale not colour. Even though your image is grey it is still a colour image. Not sure that a color image with the GRAY colourspace is the same as a gray scale image.
This is really a duplicate of :-
Converting an OpenCV Image to Black and White

Related

grayscale image rotation with cv2

I'm trying to crop and rotate an grayscale image.
The image is being rotated successfully according to my defined dimensions, but the intensity channel seems to get zeroed up across the entire rotated image.
image - the original 32,000X1024X1 grayscale image.
i - an index from which I want to crop the image.
windowWidth - a size constant, which defines the number of pixels I wish to crop (e.g in our case, windowWidth = 5000).
cropped - the piece from the original image I wish to rotate.
code example:
cropped = image[i:i+windowWidth, :]
ch, cw = cropped.shape[:2]
rotation_matrix = cv2.getRotationMatrix2D((cw/2,ch/2),90,1)
return cv2.warpAffine(cropped,rotation_matrix, (ch,cw))
The returned 1024X5000X1 matrix contains only 0's, although the original image does not.
It is possible that you are using width instead of height, then maybe this would solve your problem:
cropped = image[:, i:i+windowWidth]

How to convert the background of the entire image to white when both white and black backgrounds are present?

The form image contains text in different background. The image needs to be converted to one background (here white) and hence the heading needs to be converted into black.
input image :
output image:
My approach was to detect the grid(horizontal lines and vertical lines and sum them up) and then crop each section of the grid into new sub-images and then check the majority pixel color and transform accordingly. But after implementing that, the blue background image is not getting detected and getting cropped like :
So I am trying to convert the entire form image into one background so that I can avoid such outcomes.
Here's a different way of doing it that will cope with the "reverse video" being black, rather than relying on some colour saturation to find it.
#!/usr/bin/env python3
import cv2
import numpy as np
# Load image, greyscale and threshold
im = cv2.imread('form.jpg',cv2.IMREAD_GRAYSCALE)
# Threshold and invert
_,thr = cv2.threshold(im,127,255,cv2.THRESH_BINARY)
inv = 255 - thr
# Perform morphological closing with square 7x7 structuring element to remove details and thin lines
SE = np.ones((7,7),np.uint8)
closed = cv2.morphologyEx(thr, cv2.MORPH_CLOSE, SE)
# DEBUG save closed image
cv2.imwrite('closed.png', closed)
# Find row numbers of dark rows
meanByRow=np.mean(closed,axis=1)
rows = np.where(meanByRow<50)
# Replace selected rows with those from the inverted image
im[rows]=inv[rows]
# Save result
cv2.imwrite('result.png',im)
The result looks like this:
And the intermediate closed image looks like this - I artificially added a red border so you can see its extent on Stack Overflow's white background:
You can read about morphology here and an excellent description by Anthony Thyssen, here.
Here's a possible approach. Shades of blue will show up with a higher saturation than black and white if you convert to HSV colourspace, so...
convert to HSV
find mean saturation for each row and select rows where mean saturation exceeds a threshold
greyscale those rows, invert and threshold them
This approach should work if the reverse (standout) backgrounds are any colour other than black or white. It assumes you have de-skewed your images to be truly vertical/horizontal per your example.
That could look something like this in Python:
#!/usr/bin/env python3
import cv2
import numpy as np
# Load image
im = cv2.imread('form.jpg')
# Make HSV and extract S, i.e. Saturation
hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
s=hsv[:,:,1]
# Save saturation just for debug
cv2.imwrite('saturation.png',s)
# Make greyscale version and inverted, thresholded greyscale version
gr = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
_,grinv = cv2.threshold(gr,127,255,cv2.THRESH_BINARY_INV)
# Find row numbers of rows with colour in them
meanSatByRow=np.mean(s,axis=1)
rows = np.where(meanSatByRow>50)
# Replace selected rows with those from the inverted, thresholded image
gr[rows]=grinv[rows]
# Save result
cv2.imwrite('result.png',gr)
The result looks like this:
The saturation image looks as follows - note that saturated colours (i.e. the blues) show up as light, everything else as black:
The greyscale, inverted image looks like this:

How to get the intensity (darkness) of all the text in an image to one level?

I have used Pytesseract and openCV to read text from an image. I used the median blur, normalization and threshold to remove the background and was able to read the text.
However, some parts of the text have turned too light during the process of normalization and I wish to darken them so that they match the darkness/intensity of the remaining text in the image. I tried morphological transformations and tried canny+erosion to remove noise, but neither of those helped.
My input looks like this:
In here, "Code", "Division Name", "15" and "Mechanical" are lighter and I am unable to read them, whereas I am easily able to read "Air Distribution" and "Basic materials & methods".
Any help regarding how to change the color of the lighter text would be greatly helpful.
You can make change in threshold and then apply erode in the white-text-in-black-ground image.
import cv2
import numpy as np
image = cv2.imread("1.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.blur(gray,(3,3))
_,thresh = cv2.threshold(blur,240,255,cv2.THRESH_BINARY)
cv2.imshow("thresh",thresh)
thresh = cv2.bitwise_not(thresh)
element = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(5, 5))
erode = cv2.erode(thresh,element,3)
cv2.imshow("erode",erode)
cv2.imshow("img",image)
cv2.waitKey(0)
cv2.destroyAllWindows()

PIL change color channel intensity

I want to make a color picker, which recolors a png texture while preserving transparency in python3.
I only want the brighter parts of the image to be recolored, but also keep the gradient.
The only option the I could think of was to adjust the color channel intensity, however I did not find anything like that in the PIL docs.
How do I change the color channel intensity? My PNG texture is loaded in ARGB mode and can be found >>here<<.
Original Image:
I dreamt up an approach for this:
Extract and save the Alpha/transparency channel
Convert the image, minus Alpha, to HSV colourspace and save the V (lightness)
Get a new Hue (and possibly Saturation) from your Colour Picker
Synthesize a new Hue channel, and a new Saturation channel of 255 (fully saturated)
Merge the new Hue, Saturation and original V (lightness) to a 3 channel HSV image
Convert the HSV image back to RGB space
Merge the original Alpha channel back in
That looks like this:
#!/usr/local/bin/python3
import numpy as np
from PIL import Image
# Open and ensure it is RGB, not palettised
img = Image.open("keyshape.png").convert('RGBA')
# Save the Alpha channel to re-apply at the end
A = img.getchannel('A')
# Convert to HSV and save the V (Lightness) channel
V = img.convert('RGB').convert('HSV').getchannel('V')
# Synthesize new Hue and Saturation channels using values from colour picker
colpickerH, colpickerS = 10, 255
newH=Image.new('L',img.size,(colpickerH))
newS=Image.new('L',img.size,(colpickerS))
# Recombine original V channel plus 2 synthetic ones to a 3 channel HSV image
HSV = Image.merge('HSV', (newH, newS, V))
# Add original Alpha layer back in
R,G,B = HSV.convert('RGB').split()
RGBA = Image.merge('RGBA',(R,G,B,A))
RGBA.save('result.png')
With colpickerH=10 you get this (try putting Hue=10 here):
With colpickerH=120 you get this (try putting Hue=120 here):
Just for fun, you can do it exactly the same without writing any Python, just at the command line with ImageMagick which is installed on most Linux distros and available for macOS and Windows:
# Split into Hue, Saturation, Lightness and Alpha channels
convert keyshape.png -colorspace hsl -separate ch-%d.png
# Make a new solid Hue channel filled with 40, a new solid Saturation channel filled with 255, take the original V channel (and darken it a little), convert from HSL to RGB, copy the Alpha channel from the original image
convert -size 73x320 xc:gray40 xc:white \( ch-2.png -evaluate multiply 0.5 \) -set colorspace HSL -combine -colorspace RGB ch-3.png -compose copyalpha -composite result.png
Yes, I could do it as a one-liner, but it would be harder to read.

Change background and pixel color of image

For an experiment I want to show the participants drawings from a database which includes black drawn lines on a white background. Eventually I only want to shown what is the 'drawn part' per image in a certain color. So I want the white parts of the image to be made gray, so it is indistinguishable from the gray background. And I want to show the black parts of the image (the actual drawing) in other colors, for example red.
I am quite new to programming and so far I couldn't find an answer. I have tried several things, including the 2 options below.
Could anyone maybe show me an example of how to change the colors of the image I have attached to this message?
It would be very much appreciated!
[enter image description here][1]
####### OPTION 1, not working
#picture = Image.open(fname)
fname = exp.get_file('PICTURE_1.png')
picture = Image.open(fname)
# Get the size of the image
width, height = picture.size
# Process every pixel
for x in range(width):
for y in range(height):
current_color = picture.getpixel( (x,y) )
if current_color == (255,255,255):
new_color = (255,0,0)
picture.putpixel( (x,y), new_color)
elif current_color == (0,0,0):
new_color2 = (115,115,115)
picture.putpixel( (x,y), new_color2)
picture.show()
#picture.show()
win.flip()
clock.sleep(1000)
Implemented changes as you suggested gives: TypeError: 'int' object has no attribute 'getitem'
for x in range(width):
for y in range(height):
current_color = picture.getpixel( (x,y) )
if (current_color[0]<200) and (current_color[1]<200) and (current_color[2]<200):
new_color = (255,0,0)
picture.putpixel( (x,y), new_color)
elif (current_color[0]>200) and (current_color[1]>200) and (current_color[2]>200):
new_color2 = (115,115,115)
picture.putpixel( (x,y), new_color2)
picture.show()
Your approach in option one is basically correct, but here are a few tips to help you get it working properly:
Instead of saying if current_color == (255,255,255):, you should instead put
if (current_color[0]>200) and (current_color[1]>200) and (current_color[2]>200):
as even though the white parts of the image look white the pixels may not be exactly (255,255,255).
I thought you wanted to turn the white parts grey and the black parts red? In your code for option one, the lines
if current_color == (255,255,255):
new_color = (255,0,0)
will turn white pixels red. To turn black pixels red, it should be if current_color == (0,0,0).
If your code is still not working when these changes are made, you could try creating a new image with the same dimensions as the original one, and adding pixels to the new image rather than editing the pixels in the original one.
Also, it would help if you could tell us what actually happens when you run your code. Is there an error message, or is an image shown but the image is not correct? Could you please attach an example output?
Update:
I fiddled around with your code, and got it to do what you want it to do. Here is the code I ended up with:
import PIL
from PIL import Image
picture = Image.open('image_one.png')
# Get the size of the image
width, height = picture.size
for x in range(width):
for y in range(height):
current_color = picture.getpixel( (x,y) )
if (current_color[0]<200) and (current_color[1]<200) and (current_color[2]<200):
new_color = (255,0,0)
picture.putpixel( (x,y), new_color)
elif (current_color[0]>200) and (current_color[1]>200) and (current_color[2]>200):
new_color2 = (115,115,115)
picture.putpixel( (x,y), new_color2)
picture.show()
If you copy and paste this code into a script and run it in the same folder as your image, it should work.
There are much more efficient ways to do this than looping through each pixel and changing its value.
Since it looks like you're using PsychoPy, you can save your images as greyscale with a transparent background. By using the greyscale image format you allow PsychoPy to change the color of the lines to anything you want simply by altering the stimulus color setting. By using a transparent background, whatever you see behind your lines will show through, so you can choose to have a white square, a different square or no square at all. By this method, all the calculations for the colors are being done on the graphics card and can be changed every frame with no problems.
If for some reason you need to alter the image in ways that PsychoPy doesn't inherently allow (and if speed of processing matters) then you should try to change all the pixels in a single operation (using the numpy arrays) rather than one pixel at a time in a for-loop.

Resources