How to reduce image portion with numpy.compress method ? (numpy+scikit-image) - python-3.x

Hi using the sample image phantom.png I'm following some operations with numpy + skimage libraries and after some modifications the last one exercise ask for:
Compress the size of center spots by 50% and plot the final image.
These are the steps I do before.
I read the image doing
img = imread(os.path.join(data_dir, 'phantom.png'))
Then apply following to make it black and white
img[np.less_equal(img[:,:,0],50)] = 0
img[np.greater_equal(img[:,:,0],51)] = 255
Took couple of slices of the image (the black spots) with given coordinates
img_slice=img.copy()
img_slice=img_slice[100:300, 100:200]
img_slice2=img.copy()
img_slice2=img_slice2[100:300, 200:300]
Now flip them
img_slice=np.fliplr(img_slice)
img_slice2=np.fliplr(img_slice2)
And put them back into an image copy
img2=img.copy()
img2[100:300, 200:300]=img_slice
img2[100:300, 100:200]=img_slice2
And this is the resulting image before the final ("compress") excersise:
Then I'm asked to "reduce" the black spots by using the numpy.compress method.
The expected result after using "compress" method is the following image (screenshot) where the black spots are reduced by 50%:
But I have no clue of how to use numpy.compress method over the image or image slices to get that result, not even close, all what I get is just chunks of the image that looks like cropped or stretched portions of it.
I will appreciate any help/explanation about how the numpy.compress method works for this matter and even if is feasible to use it for this.

You seem ok with cropping and extracting, but just stuck on the compress aspect. So, crop out the middle and save that as im and we will compress that in the next step. Fill the area you cropped from with white.
Now, compress the part you cropped out. In order to reduce by 50%, you need to take alternate rows and alternate columns, so:
# Generate a vector alternating between True and False the same height as "im"
a = [(i%2)==0 for i in range(im.shape[0])]
# Likewise for the width
b = [(i%2)==0 for i in range(im.shape[1])]
# Now take alternate rows with numpy.compress()
r = np.compress(a,im,0)
# And now take alternate columns with numpy.compress()
res = np.compress(b,r,1)
Finally put res back in the original image, offset by half its width and height relative to where you cut it from.

I guess you can slice off the center spots first by :
center_spots = img2[100:300,100:300]
Then you can replace the center spots values in the original image with 255 (white)
img2[100:300,100:300] = 255
then compress center_spots by 50% along both axes and add the resultant back to img2
the compressed image shape will be (100,100), so add to img2[150:250,150:250]

Check the below code for the output you want. Comment if you need explanation for the below code.
import os.path
from skimage.io import imread
from skimage import data_dir
import matplotlib.pyplot as plt
import numpy as np
img = imread(os.path.join(data_dir, 'phantom.png'))
img[np.less_equal(img[:,:,0],50)] = 0
img[np.greater_equal(img[:,:,0],51)] = 255
img_slice=img[100:300,100:200]
img_slice2=img[100:300,200:300]
img_slice=np.fliplr(img_slice)
img_slice2=np.fliplr(img_slice2)
img2=img.copy()
img2[100:300, 200:300]=img_slice
img2[100:300, 100:200]=img_slice2
#extract the left and right images
img_left = img2[100:300,100:200]
img_right = img2[100:300,200:300]
#reduce the size of the images extracted using compress
#numpy.compress([list of states as True,False... or 1,0,1...], axis = (0 for column-wise and 1 for row-wise))
#In state list whatever is False or 0 that particular row should will be removed from that matrix or image
#note: len(A) -> number of rows and len(A[0]) number of columns
#reducing the height-> axis = 0
img_left = img_left.compress([not(i%2) for i in range(len(img_left))],axis = 0)
#reducing the width-> axis = 1
img_left = img_left.compress([not(i%2) for i in range(len(img_left[0]))],axis = 1)
#reducing the height-> axis = 0
img_right = img_right.compress([not(i%2) for i in range(len(img_right))],axis = 0)
#reducing the width-> axis = 1
img_right = img_right.compress([not(i%2) for i in range(len(img_right[0]))],axis = 1)
#clearing the area before pasting the left and right minimized images
img2[100:300,100:200] = 255 #255 is for whitening the pixel
img2[100:300,200:300] = 255
#paste the reduced size images back into the main picture(but notice the coordinates!)
img2[150:250,125:175] = img_left
img2[150:250,225:275] = img_right
plt.imshow(img2)
numpy.compress document here.

eyes = copy[100:300,100:300]
eyes1 = eyes
e = [(i%2 == 0) for i in range(eyes.shape[0])]
f = [(i%2 == 0) for i in range(eyes.shape[1])]
eyes1 = eyes1.compress(e,axis = 0)
eyes1 = eyes1.compress(f,axis = 1)
# plt.imshow(eyes1)
copy[100:300,100:300] = 255
copy[150:250,150:250] = eyes1
plt.imshow(copy)

Related

How can I make the text of a photo list clearer?

I have about a hundred photos that aren't very sharp and I'd like to make them sharper.
So I created a script with python that already tries with one. I have tried with PIL, OpenCV and OCR readers to read texts from Images.
# External libraries used for
# Image IO
from PIL import Image
# Morphological filtering
from skimage.morphology import opening
from skimage.morphology import disk
# Data handling
import numpy as np
# Connected component filtering
import cv2
black = 0
white = 255
threshold = 160
# Open input image in grayscale mode and get its pixels.
img = Image.open("image3.png").convert("LA")
pixels = np.array(img)[:,:,0]
# Remove pixels above threshold
pixels[pixels > threshold] = white
pixels[pixels < threshold] = black
# Morphological opening
blobSize = 1 # Select the maximum radius of the blobs you would like to remove
structureElement = disk(blobSize) # you can define different shapes, here we take a disk shape
# We need to invert the image such that black is background and white foreground to perform the opening
pixels = np.invert(opening(np.invert(pixels), structureElement))
# Create and save new image.
newImg = Image.fromarray(pixels).convert('RGB')
newImg.save("newImage1.PNG")
# Find the connected components (black objects in your image)
# Because the function searches for white connected components on a black background, we need to invert the image
nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(np.invert(pixels), connectivity=8)
# For every connected component in your image, you can obtain the number of pixels from the stats variable in the last
# column. We remove the first entry from sizes, because this is the entry of the background connected component
sizes = stats[1:,-1]
nb_components -= 1
# Define the minimum size (number of pixels) a component should consist of
minimum_size = 100
# Create a new image
newPixels = np.ones(pixels.shape)*255
# Iterate over all components in the image, only keep the components larger than minimum size
for i in range(1, nb_components):
if sizes[i] > minimum_size:
newPixels[output == i+1] = 0
# Create and save new image.
newImg = Image.fromarray(newPixels).convert('RGB')
newImg.save("newImage2.PNG")
But it returns:
I would prefer it not to be black and white, the best output would be one which upscale both text and image
As mentioned in the comments, the quality is very bad. This is not an easy problem. However, there may be a couple of tricks you can try.
This looks like it is due to some anti-aliasing that has been applied to the image/scan. I would try reversing anti-aliasing if possible. As descriped in the post the steps would be similar to this:
Apply low pass filter
difference = original_image - low_pass_image
sharpened_image = original_image + alpha*difference
Code may look something like this:
from skimage.filters import gaussian
alpha = 1 # Sharpening factor
low_pass_image = gaussian(image, sigma=1)
difference = original_image - low_pass_image
sharpened_image = original_image + alpha*difference
Also, scikit image has an implementation of an unsharp mask as well as the wiener filter.

Create new raster (.tif) from standard deviation stretched bands, works with dstack but not to write a new file, Python

I am sorry if the title is unclear, I am new to python and my vocabulary is limited.
What I am trying to do is apply a standard deviation stretch to each band in a .tif raster and then create a new raster (.tif) by stacking those bands using GDAL (Python).
I able to create new false color rasters with differing band combinations and save them, and I am able to create my desired IMAGE in python using dstack (first block of code), but I am unable to save that image as a georectified .tif file.
So to create the stretched image using dstack my code looks like:
import os
import numpy as np
import matplotlib.pyplot as plt
import math
from osgeo import gdal
# code from my prof
def std_stretch_data(data, n=2):
"""Applies an n-standard deviation stretch to data."""
# Get the mean and n standard deviations.
mean, d = data.mean(), data.std() * n
# Calculate new min and max as integers. Make sure the min isn't
# smaller than the real min value, and the max isn't larger than
# the real max value.
new_min = math.floor(max(mean - d, data.min()))
new_max = math.ceil(min(mean + d, data.max()))
# Convert any values smaller than new_min to new_min, and any
# values larger than new_max to new_max.
data = np.clip(data, new_min, new_max)
# Scale the data.
data = (data - data.min()) / (new_max - new_min)
return data
# open the raster
img = gdal.Open(r'/Users/Rebekah/ThesisData/TestImages/OG/OG_1234.tif')
#open the bands
red = img.GetRasterBand(1).ReadAsArray()
green = img.GetRasterBand(2).ReadAsArray()
blue = img.GetRasterBand(3).ReadAsArray()
# create alpha band where a 0 indicates a transparent pixel and 1 is a opaque pixel
# (this is from class and i dont FULLY understand it)
alpha = np.where(red + green + blue ==0, 0, 1).astype(np.byte)
red_stretched = std_stretch_data(red, 1)
green_stretched = std_stretch_data(green, 1)
blue_stretched = std_stretch_data(blue, 1)
data_stretched = np.dstack((red_stretched, green_stretched, blue_stretched, alpha))
plt.imshow(data_stretched)
plt.show()
And that gives me a beautiful image of exactly what I want in a separate window. But no where in that code is an option to assign projections, or save it as a multiband tif.
So I took that and applied it the best I could to the code I use to create false color images and it fails (code below). If I create a 4 band tif with the alpha band the output is an empty tif, and if I create a 3 band tif and omit the alpha band the output is an entirely black tif.
import os
import numpy as np
import matplotlib.pyplot as plt
import math
from osgeo import gdal
#code from my professor
def std_stretch_data(data, n=2):
"""Applies an n-standard deviation stretch to data."""
# Get the mean and n standard deviations.
mean, d = data.mean(), data.std() * n
# Calculate new min and max as integers. Make sure the min isn't
# smaller than the real min value, and the max isn't larger than
# the real max value.
new_min = math.floor(max(mean - d, data.min()))
new_max = math.ceil(min(mean + d, data.max()))
# Convert any values smaller than new_min to new_min, and any
# values larger than new_max to new_max.
data = np.clip(data, new_min, new_max)
# Scale the data.
data = (data - data.min()) / (new_max - new_min)
return data
#open image
img = gdal.Open(r'/Users/Rebekah/ThesisData/TestImages/OG/OG_1234.tif')
# get geotill driver
gtiff_driver = gdal.GetDriverByName('GTiff')
# read in bands
red = img.GetRasterBand(1).ReadAsArray()
green = img.GetRasterBand(2).ReadAsArray()
blue = img.GetRasterBand(3).ReadAsArray()
# create alpha band where a 0 indicates a transparent pixel and 1 is a opaque pixel
# (this is from class and i dont FULLY understand it)
alpha = np.where(red + green + blue ==0, 0, 1).astype(np.byte)
# apply the 1 standard deviation stretch
red_stretched = std_stretch_data(red, 1)
green_stretched = std_stretch_data(green, 1)
blue_stretched = std_stretch_data(blue, 1)
# create empty tif file
NewImg = gtiff_driver.Create('/Users/riemann/ThesisData/TestImages/FCI_tests/1234_devst1.tif', img.RasterXSize, img.RasterYSize, 4, gdal.GDT_Byte)
if NewImg is None:
raise IOerror('could not create new raster')
# set the projection and geo transform of the new raster to be the same as the original
NewImg.SetProjection(img.GetProjection())
NewImg.SetGeoTransform(img.GetGeoTransform())
# write new bands to the new raster
band1 = NewImg.GetRasterBand(1)
band1.WriteArray(red_stretched)
band2 = NewImg.GetRasterBand(2)
band2.WriteArray(green_stretched)
band3= NewImg.GetRasterBand(3)
band3.WriteArray(blue_stretched)
alpha_band = NewImg.GetRasterBand(4)
alpha_band.WriteArray(alpha)
del band1, band2, band3, img, alpha_band
I am not entirely sure how to go from here and create a new file displaying the stretch on the different bands.
The image is just a 4 band raster (NAIP) downloaded from earthexplorer, I can upload the specific image I am using for my test if needed but there is nothing inherently special about this file compared to other NAIP images.
You should close the new Dataset (NewImg) as well by either adding it to the del list you already have, or setting it to None.
That properly closes the file and makes sure all data is written to disk.
There is however another issue, you are scaling your data between 0 and 1, but storing it as a Byte. So either change the output datatype from gdal.GDT_Byte to something like gdal.GDT_Float32. Or multiply your scaled data to fit the output datatype, in the case of Byte multiple with 255 (don't forget the alpha), you should properly round it for accuracy, GDAL will otherwise truncate to the nearest integer.
You can use np.iinfo() to check what the range of a datatype is, in case you are unsure what multiplication to use for other datatypes.
Depending on your use case, it might be easiest to use gdal.Translate for the scaling. If you would modify your scaling function a little to return the scaling parameteters instead of the data, you could use something like:
ds = gdal.Translate(output_file, input_file, outputType=gdal.GDT_Byte, scaleParams=[
[old_min_r, old_max_r, new_min_r, new_max_r], # red
[old_min_g, old_max_g, new_min_g, new_max_g], # green
[old_min_b, old_max_b, new_min_b, new_max_b], # blue
[old_min_a, old_max_a, new_min_a, new_max_a], # alpha
])
ds = None
You could also add the exponents keyword for non-linear stretching.
Using gdal.Translate would save you from all the standard file creation boilerplate, you still would need to think about the datatype, since that might change compared to the input file.

How to adaptively split an image into regions and set a different text orientation for each one?

Input-Sample
I am trying to pre-process my images in order to improve the ocr quality. However, I am stuck with a problem.
The Images I am dealing with contain different text orientations within the same image (2 pages, 1st is vertical, the 2nd one is horizontally oriented and they are scanned to the same image.
The text direction is automatically detected for the first part. nevertheless, the rest of the text from the other page is completely missed up.
I was thinking of creating a zonal template to detect the regions of interest but I don't know how.
Or automatically detect the border and split the image adaptively then flip the splitted part to achieve the required result.
I could set splitting based on a fixed pixel height but it is not constant as well.
from tesserocr import PyTessBaseAPI, RIL
import cv2
from PIL import Image
with PyTessBaseAPI() as api:
filePath = r'sample.jpg'
img = Image.open(filePath)
api.SetImage(img)
boxes = api.GetComponentImages(RIL.TEXTLINE, True)
print('Found {} textline image components.'.format(len(boxes)))
for i, (im, box, _, _) in enumerate(boxes):
# im is a PIL image object
# box is a dict with x, y, w and h keys
api.SetRectangle(box['x'], box['y'], box['w'], box['h'])
ocrResult = api.GetUTF8Text()
conf = api.MeanTextConf()
for box in boxes:
box = boxes[0][1]
x = box.get('x')
y = box.get('y')
h = box.get('h')
w = box.get('w')
cimg = cv2.imread(filePath)
crop_img = cimg[y:y+h, x:x+w]
cv2.imshow("cropped", crop_img)
cv2.waitKey(0)
output image
as you can see i can apply an orientation detection but I wount get any meaningful text out of such an image.
Try Tesseract API method GetComponentImages and then DetectOrientationScript on each component image.

Creating a greyscale image with a Matrix in python

I'm Marius, a maths student in the first year.
We have recieved a team-assignment where we have to implement a fourier transformation and we chose to try to encode the transformation of an image to a JPEG image.
to simplify the problem for ourselves, we chose to do it only for pictures that are greyscaled.
This is my code so far:
from PIL import Image
import numpy as np
import sympy as sp
#
#ALLEMAAL INFORMATIE GEEN BEREKENINGEN
img = Image.open('mario.png')
img = img.convert('L') # convert to monochrome picture
img.show() #opens the picture
pixels = list(img.getdata())
print(pixels) #to see if we got the pixel numeric values correct
grootte = list(img.size)
print(len(pixels)) #to check if the amount of pixels is correct.
kolommen, rijen = img.size
print("het aantal kolommen is",kolommen,"het aantal rijen is",rijen)
#tot hier allemaal informatie
pixelMatrix = []
while pixels != []:
pixelMatrix.append(pixels[:kolommen])
pixels = pixels[kolommen:]
print(pixelMatrix)
pixelMatrix = np.array(pixelMatrix)
print(pixelMatrix.shape)
Now the problem forms itself in the last 3 lines. I want to try to convert the matrix of values back into an Image with the matrix 'pixelMatrix' as it's value.
I've tried many things, but this seems to be the most obvious way:
im2 = Image.new('L',(kolommen,rijen))
im2.putdata(pixels)
im2.show()
When I use this, it just gives me a black image of the correct dimensions.
Any ideas on how to get back the original picture, starting from the values in my matrix pixelMatrix?
Post Scriptum: We still have to implement the transformation itself, but that would be useless unless we are sure we can convert a matrix back into a greyscaled image.

tesseract ocr is not working on image which have text length of only 2 or less. Works fine for Image with text length greater than 3

import pytesseract
from PIL import Image
def textFromTesseractOCR(croppedImage):
for i in range(14):
text = pytesseract.image_to_string(croppedImage, lang = 'eng', boxes = False ,config = '--psm '+ str(i) +' --oem 3')
print("PSM Mode", i)
print("Text detected: ",text)
imgPath = "ImagePath" #you can use image I have uploaded
img = Image.open(imgPath)
textFromTesseractOCR(img)
I am working on extracting Table data from PDF. For this I am converting pdf to png. Detecting Lines, ascertaining table by line intersection and then cropping individual cells to get their text.
This all works fine, but tesseract is not working on cells image which has text of length 2 or less.
Works for this image:
Result from tesseract:
Does not work for this image:
Result from tesseract: return empty string.
It also returns empty for numbers of text length 2 or less.
I have tried resizing the image(which I knew wouldn't work), also tried appending dummy text to the image but the result was bad(was working only for few and I didn't the exact location to append the dummy text in the image)
It would be great if someone could help me with this.
So I finally came with a workaround for this situation. The situation being tesseract-OCR giving empty string when the image contains only 1 or 2 length string(eg "1" or "25").
To get output in this situation I appended the same image multiple time at the original image so as to make its length greater than 2. For example, if the original image contained only "3", I appended "3" image(the same image) 4 more times and thereby making it an image which contains the text "33333". We then give this image to tesseract which gives output "33333"(most of the times).Then we just have to replace space with blank in the text output from the Tesseract and divide the resulting string length by 5 to get the index up to which we would want to text out from the whole text.
Please see code for reference, hope this helps:
import pytesseract ## pip3 install pytesseract
Method which calls tesseract for OCR or calls our workaround code if we get an empty string from tesseract output.
def textFromTesseractOCR(croppedImage):
text = pytesseract.image_to_string(croppedImage)
if text.strip() == '': ### program that handles our problem
if 0 not in croppedImage:
return ""
yDir = 3
xDir = 3
iterations = 4
img = generate_blocks_dilation(croppedImage, yDir, xDir, iterations)
## we dilation to get only the text portion of the image and not the whole image
kernelH = np.ones((1,5),np.uint8)
kernelV = np.ones((5,1),np.uint8)
img = cv2.dilate(img,kernelH,iterations = 1)
img = cv2.dilate(img,kernelV,iterations = 1)
image = cropOutMyImg(img, croppedImage)
concateImg = np.concatenate((image, image), axis = 1)
concateImg = np.concatenate((concateImg, image), axis = 1)
concateImg = np.concatenate((concateImg, image), axis = 1)
concateImg = np.concatenate((concateImg, image), axis = 1)
textA = pytesseract.image_to_string(concateImg)
textA = textA.strip()
textA = textA.replace(" ","")
textA = textA[0:int(len(textA)/5)]
return textA
return text
Method for dilation.This method is used to dilate only the text region of the image
def generate_blocks_dilation(img, yDir, xDir, iterations):
kernel = np.ones((yDir,xDir),np.uint8)
ret,img = cv2.threshold(img, 0, 1, cv2.THRESH_BINARY_INV)
return cv2.dilate(img,kernel,iterations = iterations)
Method to crop the dilated part of the image
def cropOutMyImg(gray, OrigImg):
mask = np.zeros(gray.shape,np.uint8) # mask image the final image without small pieces
_ , contours, hierarchy = cv2.findContours(gray,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if cv2.contourArea(cnt)!=0:
cv2.drawContours(mask,[cnt],0,255,-1) # the [] around cnt and 3rd argument 0 mean only the particular contour is drawn
# Build a ROI to crop the QR
x,y,w,h = cv2.boundingRect(cnt)
roi=mask[y:y+h,x:x+w]
# crop the original QR based on the ROI
QR_crop = OrigImg[y:y+h,x:x+w]
# use cropped mask image (roi) to get rid of all small pieces
QR_final = QR_crop * (roi/255)
return QR_final
I tried running tesseract on given 2 image but it does not returns text in shorter text image.
Another thing you can try is "Train a machine learning model (probably neural net) to on alphabets, numbers and special character, then when you want to get text from image, feed that image to model and it will predict text/characters."
Training dataset would look like :
Pair of (Image of character, 'character').
First element of pair is independent variable for model.
Second element of pair is corresponding character present in that image. It will be dependent variable for model.

Resources