Fetching text specifically digits from an image OpenCV - python-3.x

I am trying to fetch label 0026 from the attached image:
Initial input image
I tried below code initially to fetch the text:
import pytesseract
BGR = cv2.imread('C:/Users/Choudharyp/CV/11952/01_A_parcel_layer_single_parcel.png')
RGB = cv2.cvtColor(BGR, cv2.COLOR_BGR2RGB)
lower = np.array([175, 125, 45], dtype="uint8")
upper = np.array([255, 255, 255], dtype="uint8")
mask = cv2.inRange(RGB, lower, upper)
img = cv2.bitwise_and(RGB, RGB, mask=mask)
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
gray = 255 - gray
emp = np.full_like(gray, 255)
emp -= gray
emp[emp==0] = 255
emp[emp<100] = 0
gauss = cv2.GaussianBlur(emp, (3,3), 1)
gauss[gauss<220] = 0
text = pytesseract.image_to_string(gauss, config='outputbase digits')
print("Text=>",text)
This did not work possibly because I need to remove green lines from the image.
Hence I first wrote below code to remove green lines from the image and extract only the black colors in the image (this works fine):
# Imports
import cv2
import numpy as np
import pytesseract
# Read image
imagePath = "C:/Users/Choudharyp/CV/11952/" #insert your own loctaion
inputImage = cv2.imread(imagePath + "01_A_parcel_layer_single_parcel.png")
# Conversion to CMYK (just the K channel):
# Convert to float and divide by 255:
imgFloat = inputImage.astype(np.float64) / 255.
# Calculate channel K:
kChannel = 1 - np.max(imgFloat, axis=2)
# Convert back to uint 8:
kChannel = (255 * kChannel).astype(np.uint8)
# Threshold image:
binaryThresh = 190
_, binaryImage = cv2.threshold(kChannel, binaryThresh, 255, cv2.THRESH_BINARY)
cv2.imshow('Black_LettersOnly', binaryImage)
cv2.waitKey(0)
Output looks like below :
Image with only black label
The label 0026 however is too small in the image.
Then, I used the same code as above to fetch the text from the image, however it still doesn't work. Can someone suggest what else I could do to start fetching the labels from the image ?

In binaryImage you get white text on black background. However, Tesseract is optimized to detect dark text on bright background and apparently it works perfectly fine in this case, when you simply invert your image:
...
text = pytesseract.image_to_string(255-binaryImage, config='outputbase digits')
print("Text=>", text.strip())
>>> Text=> 0026
However, if you want it even simpler, I'd prefer HSV color space and threshold the value channel with a one-liner to maintain just the non-colored/black pixels:
...
inputImageHSV = cv2.cvtColor(inputImage, cv2.COLOR_BGR2HSV)
binaryImage = (inputImageHSV[..., 2] > 120).astype(np.uint8) * 255
text = pytesseract.image_to_string(binaryImage, config='outputbase digits')
print("Text=>", text.strip())
>>> Text=> 0026

Related

Image mask not working when I invert it using bitwise_not (OpenCV-Python)

I am trying to use bitwise operations. In the code below I use 2 images (img1, img2). I create two masks using img2 (gray_inv and gray_test).
img1 = cv2.imread('Computer-Vision-with-Python/DATA/dog_backpack.jpg')
img1 = cv2.cvtColor(img1,cv2.COLOR_BGR2RGB)
img1 = img1[0:600,0:600]
img2 = cv2.imread('Computer-Vision-with-Python/DATA/watermark_no_copy.png')
img2 = cv2.cvtColor(img2,cv2.COLOR_BGR2RGB)
img2 = cv2.resize(img2,(600,600))
gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
gray_inv = cv2.bitwise_not(gray)
gray_test = cv2.bitwise_not(gray_inv)
I use a bitwise_or function merge with img1. The first mask works fine. However the second one does not. Am I missing out on something ? Ideally since the they are inverse gray_inv should have showed the background with the text in black.
plt.imshow(cv2.bitwise_or(img1,img1, mask=gray_inv))
plt.imshow(cv2.bitwise_or(img1,img1, mask=gray_test))
You need to "binarize" the mask.
For your code to work properly, the mask should be a binary image.
In OpenCV, the convention of a binary image is an image with all values 0 or 255 (and type np.uint8).
Using only 0 and 255 is not a "must", but when using cv2.bitwise_not(mask), it's important for the mask to be binary (then all zeros are inverted to 255, and all 255 are inverted to zeros).
The code gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY), converts img2 to grayscale, but not to a binary image (not all values are 0 and 255).
You may apply thresholding for converting gray to binary:
thres_gray = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)[1] # threshold (binarize) the image
Use thres_gray instead of gray:
gray_inv = cv2.bitwise_not(thres_gray)
gray_test = cv2.bitwise_not(gray_inv)
The following code sample demonstrates the solution:
import cv2
import numpy as np
img1 = cv2.imread('img1.png')
img1 = cv2.resize(img1, (100,100))
img2 = cv2.imread('img2.png')
img2 = cv2.resize(img2, (100,100))
gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
thres_gray = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)[1] # threshold (binarize) the image
gray_inv = cv2.bitwise_not(thres_gray)
gray_test = cv2.bitwise_not(gray_inv)
out2 = cv2.bitwise_or(img1, img1, mask=gray_inv)
out3 = cv2.bitwise_or(img1, img1, mask=gray_test)
cv2.imshow('out2', out2)
cv2.imshow('out3', out3)
cv2.waitKey()
cv2.destroyAllWindows()
out2:
out3:
For the effect you’re describing you don’t want a bit-wise OR. You want to multiply the values. So output = input1 * input2 / 255.

Display only bounding box region and neglect other part

I have this code of haarcascade which detect face and draw bounding box around it. I want to display only bounding box area in the original image in its original place and black out all other part just like we do it in color detection from opencv. Is there any way to do so?
cascPath = "haarcascade_frontalface_default.xml"
image = cv2.imread(imagePath)
faceCascade = cv2.CascadeClassifier(cascPath)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
=
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30),
flags = cv2.CASCADE_SCALE_IMAGE
)
print("Found {0} faces!".format(len(faces)))
for ((x, y, w, h),i) in zip(faces,range(len(faces))):
a=cv2.rectangle(image, (x, y), (x+w, y+h), 2)
roi_color=image[y:y+h, x:x+w]
I would try to crop the ROI and then put it in a img which is a all black rectangle. In Pillow this is very easy to do.
as I don't have face images is hard to reproduce your code. I will use some random image but It should look something like this:
I put blue color just to highlight the background, but it's just a matter to change it to whatever color you want
from PIL import Image
img = Image.open('watch.jpeg', 'r')
img_w, img_h = img.size
left = img_w/8
top = img_h/8
right = 3 * img_w/8
bottom = 3 * img_h/8
cropped_img = img.crop((left, top, right, bottom))
cropped_img.save("cropped.png")
background = Image.new('RGB', (1440, 900), (0, 0, 255))
bg_w, bg_h = background.size
offset = ((bg_w - img_w) // 2, (bg_h - img_h) // 2)
background.paste(cropped_img, offset)
background.save('out.png')
input image:
output image:

How to delete a certain part of an Image?

I have an image:
Original Image
I want to remove the grey mesh part of the image without affecting the rest of the image i.e., the part inside the black circle.
I have written a code for that
import cv2
import numpy as np
from PIL import Image
imag = Image.open('results.jpg')
imag.show()
pixelMap = imag.load()
img = Image.new( imag.mode, imag.size)
pixelsNew = img.load()
for i in range(img.size[0]):
for j in range(img.size[1]):
if (( pixelMap[i,j]> (200,0,0)) and (pixelMap[i,j]< (240,0,0))):
pixelsNew[i,j] = (255,255,255)
else:
pixelsNew[i,j] = pixelMap[i,j]
img.show()
with this code I have got the following output image:
Output Image
But, Some of the pixels inside the black circle were also changed to white, which is not what I want. I would like to know how can this problem be solved.
You can find the indices of black circle and assign values to the pixels that are either to the left or to the right of black circle. Below is the sample code for this
import cv2
import numpy as np
# read the image
img = cv2.imread('original.png')
cv2.imshow("Image", img)
# convert image to numpy array and also to grayscale
img = np.array(img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# get height and width of image
[rows, cols] = gray.shape
# now extract one row from image, find indices of black circle
# and make those pixels white which are to the left/right
# of black cirlce
for i in range(rows):
row = gray[i, :] # extract row of image
indices = np.where(row == 0) # find indices of black circle
indices = indices[0]
# if indices are not empty
if len(indices) > 0:
# find starting/ending column index
si = indices[0]
ei = indices[len(indices)-1]
# assign values to the range of pixels
img[i, 0:si-1] = [255, 255, 255]
img[i, ei+1:] = [255, 255, 255]
# if indices is empty then make whole row white
else:
img[i,:] = [255, 255, 255]
cv2.imshow("Modified Image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Input Image
Output Image

How to clear numbers from the image using openCV?

I'm trying to remove numbers which are laying inside the circular part of image, numbers are in black in color and background varies between red,yellow, blue and green.
I am using opencv to remove those numbers. I used a mask which extracts numbers from image, with help of cv2.inpaint tried to remove those numbers from images.
For my further analysis I required to have clear image. But my current approach gives distorted image and numbers are not completely removed.
I tried changing the threshold values, lowering will neglect numbers from dark shaded area such as from green and red.
import cv2
img = cv2.imread('scan_1.jpg')
mask = cv2.threshold(img,50,255,cv2.THRESH_BINARY_INV)[1][:,:,0]
cv2.imshow('mask', mask)
cv2.waitKey(0)
cv2.destroyAllWindows()
dst = cv2.inpaint(img, mask, 5, cv2.INPAINT_TELEA)
cv2.imshow('dst',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('ost_1.jpg',dst)
Input images: a) scan_1.jpg
b) scan_2.jpg
Output images: a) ost_1.jpg
b) ost_2.jpg
Expected Image: Circles can ignored, but something similar to it is required.
Here is my attempt, a better/easier solution might be acquired if you do not care about preserving texts outside of your circle.
import cv2
import numpy as np
# connectivity method used for finding connected components, 4 vs 8
CONNECTIVITY = 4
# HSV threshold for finding black pixels
H_THRESHOLD = 179
S_THRESHOLD = 255
V_THRESHOLD = 150
# read image
img = cv2.imread("a1.jpg")
img_height = img.shape[0]
img_width = img.shape[1]
# save a copy for creating resulting image
result = img.copy()
# convert image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# found the circle in the image
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1.7, minDist= 100, param1 = 48, param2 = 100, minRadius=70, maxRadius=100)
# draw found circle, for visual only
circle_output = img.copy()
# check if we found exactly 1 circle
num_circles = len(circles)
print("Number of found circles:{}".format(num_circles))
if (num_circles != 1):
print("invalid number of circles found ({}), should be 1".format(num_circles))
exit(0)
# save center position and radius of found circle
circle_x = 0
circle_y = 0
circle_radius = 0
if circles is not None:
# convert the (x, y) coordinates and radius of the circles to integers
circles = np.round(circles[0, :]).astype("int")
for (x, y, radius) in circles:
circle_x, circle_y, circle_radius = (x, y, radius)
cv2.circle(circle_output, (circle_x, circle_y), circle_radius, (255, 0, 0), 4)
print("circle center:({},{}), radius:{}".format(x,y,radius))
# keep a median filtered version of image, will be used later
median_filtered = cv2.medianBlur(img, 21)
# Convert BGR to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# define range of black color in HSV
lower_val = np.array([0,0,0])
upper_val = np.array([H_THRESHOLD,S_THRESHOLD,V_THRESHOLD])
# Threshold the HSV image to get only black colors
mask = cv2.inRange(hsv, lower_val, upper_val)
# find connected components
components = cv2.connectedComponentsWithStats(mask, CONNECTIVITY, cv2.CV_32S)
# apply median filtering to found components
#centers = components[3]
num_components = components[0]
print("Number of found connected components:{}".format(num_components))
labels = components[1]
stats = components[2]
for i in range(1, num_components):
left = stats[i, cv2.CC_STAT_LEFT] - 10
top = stats[i, cv2.CC_STAT_TOP] - 10
width = stats[i, cv2.CC_STAT_WIDTH] + 10
height = stats[i, cv2.CC_STAT_HEIGHT] + 10
# iterate each pixel and replace them if
#they are inside circle
for row in range(top, top+height+1):
for col in range(left, left+width+1):
dx = col - circle_x
dy = row - circle_y
if (dx*dx + dy*dy <= circle_radius * circle_radius):
result[row, col] = median_filtered[row, col]
# smooth the image, may be necessary?
#result = cv2.blur(result, (3,3))
# display image(s)
cv2.imshow("img", img)
cv2.imshow("gray", gray)
cv2.imshow("found circle:", circle_output)
cv2.imshow("mask", mask)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result for a1:

Set white background for a png instead of transparency with OpenCV

I have a OpenCv Image likewise;
opencvImage = cv2.cvtColor(numpy_image, cv2.COLOR_RGBA2BGRA)
Then with the following code piece, I want to remove the transparency and set a White background.
source_img = cv2.cvtColor(opencvImage[:, :, :3], cv2.COLOR_BGRA2GRAY)
source_mask = opencvImage[:,:,3] * (1 / 255.0)
background_mask = 1.0 - source_mask
bg_part = (background_color * (1 / 255.0)) * (background_mask)
source_part = (source_img * (1 / 255.0)) * (source_mask)
result_image = np.uint8(cv2.addWeighted(bg_part, 255.0, source_part, 255.0, 0.0))
Actually, I am able to set the background white, however, the actual image color is change, as well.
I believe COLOR_BGRA2GRAY methods causes this problem. That's why, I tried to use IMREAD_UNCHANGED method, but I have this error : unsupported color conversion code in function 'cvtColor’
Btw, I am open to any solution, I just share my code - might need a small fix.
Here's a basic script that will replace all fully transparent pixels with white and then remove the alpha channel.
import cv2
#load image with alpha channel. use IMREAD_UNCHANGED to ensure loading of alpha channel
image = cv2.imread('your image', cv2.IMREAD_UNCHANGED)
#make mask of where the transparent bits are
trans_mask = image[:,:,3] == 0
#replace areas of transparency with white and not transparent
image[trans_mask] = [255, 255, 255, 255]
#new image without alpha channel...
new_img = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)
I do not know exactly what that error is, but I was testing just now a possible solution for you. Even it is in C++, I guess you can convert it easily to python.
/* Setting data info */
std::string test_image_path = "Galicia.png";
/* General variables */
cv::namedWindow("Input image", cv::WINDOW_NORMAL);
cv::namedWindow("Input image R", cv::WINDOW_NORMAL);
cv::namedWindow("Input image G", cv::WINDOW_NORMAL);
cv::namedWindow("Input image B", cv::WINDOW_NORMAL);
cv::namedWindow("Input image A", cv::WINDOW_NORMAL);
cv::namedWindow("Output image", cv::WINDOW_NORMAL);
/* Process */
cv::Mat test_image = cv::imread(test_image_path, cv::IMREAD_UNCHANGED);
std::cout << "Image type: " << test_image.type() << std::endl;
// Split channels of the png files
std::vector<cv::Mat> pngChannels(4);
cv::split(test_image, pngChannels);
cv::imshow("Input image", test_image);
cv::imshow("Input image R", pngChannels[0]);
cv::imshow("Input image G", pngChannels[1]);
cv::imshow("Input image B", pngChannels[2]);
cv::imshow("Input image A", pngChannels[3]);
// Set to 255(white) the RGB channels where the Alpha channel(mask) is 0(transparency)
pngChannels[0].setTo(cv::Scalar(255), pngChannels[3]==0);
pngChannels[1].setTo(cv::Scalar(255), pngChannels[3]==0);
pngChannels[2].setTo(cv::Scalar(255), pngChannels[3]==0);
// Merge again the channels
cv::Mat test_image_output;
cv::merge(pngChannels, test_image_output);
// Show the merged channels.
cv::imshow("Output image", test_image_output);
// For saving with changes, conversion is needed.
cv::cvtColor(test_image_output, test_image_output, cv::COLOR_RGBA2RGB);
cv::imwrite("Galicia_mod.png", test_image_output);
I complement the code with this screenshot that may help you to understand better my solution:
Best Wishes,
Arritmic
All previous answers use binarizing but mask can be non binary. In that case you can use alpha blending with white background
def alpha_blend_with_mask(foreground, background, mask): # modified func from link
# Convert uint8 to float
foreground = foreground.astype(float)
background = background.astype(float)
# Normalize the mask mask to keep intensity between 0 and 1
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
mask = mask.astype(float) / 255
# Multiply the foreground with the mask matte
foreground = cv2.multiply(mask, foreground)
# Multiply the background with ( 1 - mask )
background = cv2.multiply(1.0 - mask, background)
# Add the masked foreground and background.
return cv2.add(foreground, background).astype(np.uint8)
img_with_white_background = alpha_blend_with_mask(img[..., :3], np.ones_like(clip_img) * 255, img[..., 3])
this worked for me..
# import cv2
# #load image with alpha channel. use IMREAD_UNCHANGED to ensure loading of alpha channel
image = cv2.imread('/content/test1.jpg')
#make mask of where the transparent bits are
transp_mask = image[:,:,:3] == 0
transp_mask = image[:,:,:3] == 1 # swap
#replace areas of transparency with white and not transparent
image[transp_mask] = [100]
#new image without alpha channel...
new_img = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)
cv2.imwrite('testingnew.jpg',new_img) converted to binary img
print(new_img.shape)
plt.imshow(new_img)
transparent image
output
output2
real img
the answer by #user1269942 leave black edges and makes an unnecessary contour around the image.
I needed to fill the background to this image
This was the image I needed to convert
this is the image after following the steps in accepted answer
However If we do masking based on a threshold value we can reduce that unnecessary contour based on how much we choose threshold value. I have choosen 75 in my case.
So instead of trans_mask = image[:,:,3] == 0
If we do
img2gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
ret, trans_mask = cv2.threshold(img2gray, 75, 255, cv2.THRESH_BINARY)
trans_mask = trans_mask == 0
def fillColor(imageFile, color):
image = cv2.imread(imageFile, cv2.IMREAD_UNCHANGED)
#trans_mask = image[:,:,3] == 0
img2gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
ret, trans_mask = cv2.threshold(img2gray, 75, 255, cv2.THRESH_BINARY)
trans_mask = trans_mask == 0
image[trans_mask] = color
new_img = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)
return new_img
The Output Image

Resources