How to remove all portrait pictures from a document - python

I am working on OCRing a document image. I want to detect all pictures and remove from the document image. I want to retain tables in the document image. Once I detect pictures I will remove and then want to OCR. I tried to find contour tried to detect all the bigger areas. unfortunately it detects tables also. Also how to remove the objects keeping other data in the doc image. I am using opencv and python
Here's my code
import os
from PIL import Image
import pytesseract
img = cv2.imread('block2.jpg' , 0)
mask = np.ones(img.shape[:2], dtype="uint8") * 255
ret,thresh1 = cv2.threshold(img,127,255,0)
contours, sd = cv2.findContours(thresh1,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
areacontainer = []
for cnt in contours:
area = cv2.contourArea(cnt)
areacontainer.append(area)
avgArea = sum(areacontainer)/len(areacontainer)
[enter code here][1]
for c in contours:# average area heuristics
if cv2.contourArea(c)>6*avgArea:
cv2.drawContours(mask, [c], -1, 0, -1)
binary = cv2.bitwise_and(img, img, mask=mask) # subtracting
cv2.imwrite("bin.jpg" , binary)
cv2.imwrite("mask.jpg" , mask)

Here's an approach:
Convert image to grayscale and Gaussian blur
Perform canny edge detection
Perform morphological operations to smooth image
Find contours and filter using a minimum/maximum threshold area
Remove portrait images
Here's the detected portraits highlighted in green
Now that we have the bounding box ROIs, we can effectively remove the pictures by filling them in with white. Here's the result
import cv2
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
canny = cv2.Canny(blur, 120, 255, 1)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
close = cv2.morphologyEx(canny, cv2.MORPH_CLOSE, kernel, iterations=2)
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
area = cv2.contourArea(c)
if area > 15000 and area < 35000:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(image, (x, y), (x + w, y + h), (255,255,255), -1)
cv2.imshow('image', image)
cv2.waitKey()

Related

How to crop and save segmented objects from an image?

I have a hyperspectral image. In each image, there are many objects. I have to segment, crop, and save them as separate images. The segmented image after applying the thresholding is as below:
The problem is that I have to crop the segmented objects and save them. How it can be done?
Here's a simple approach:
Obtain binary image. Load the image, convert to grayscale, and Otsu's threshold to obtain a binary image.
Remove noise. We morphological operations to remove any particles of noise in the image.
Extract objects. From here we find contours, obtain each bounding rectangle then extract and save each ROI using Numpy slicing.
Detected objects
Saved ROIs
import cv2
import numpy as np
# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.jpg')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# Morph open to remove noise
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
# Find contours, obtain bounding box, extract and save ROI
ROI_number = 0
cnts = cv2.findContours(opening, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
ROI = original[y:y+h, x:x+w]
cv2.imwrite('ROI_{}.png'.format(ROI_number), ROI)
ROI_number += 1
cv2.imshow('image', image)
cv2.imshow('thresh', thresh)
cv2.imshow('opening', opening)
cv2.waitKey()

Extract numbers and letters from license plate image with Python OpenCV

I want to detect and then extract letters and numbers from this image. I have just started to learn OpenCV and I think that this can be done with that lib. You have the image that I used and desired output below.
This is the code that I have:
import cv2
# read original image
img = cv2.imread('image.jpg')
cv2.imshow('original', img)
cv2.waitKey(0)
# convert it to gray and apply filter
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #convert to grey scale
gray = cv2.bilateralFilter(gray, 11, 17, 17)
cv2.imshow('gray', gray)
cv2.waitKey(0)
#apply treshold
thresh = cv2.threshold(gray, 10, 255, cv2.THRESH_OTSU)[1]
cv2.imshow('thresh', thresh)
cv2.waitKey(0)
This is the image:
My goal is to get separated images of each letter and number (I did this in paint):
So, what should I do to get this?
It would be perfect to keep the same order of letters and numbers, for example:
MXF51051
Here's an approach using simple thresholding + contour filtering
Convert image to grayscale and Otsu's threshold
Find contours and filter using contour area
Extract and save ROI
We begin by converting to grayscale and then Otsu's threshold to obtain a binary image
Next we find contours using cv2.findContours(). To keep the same order of letters/numbers, we use imutils.contours.sort_contours() with the left-to-right parameter to ensure that when we iterate through the contours, we have each contour in the correct order. For each contour, we filter using a minimum and maximum area threshold to ensure that we only keep contours with the desired text. Once we have the filtered ROI, we extract/save the ROI using Numpy slicing. Here's the filtered mask with only the desired text
Detected numbers and letters
The extracted ROIs in the correct order
import cv2
import numpy as np
from imutils import contours
image = cv2.imread('1.jpg')
mask = np.zeros(image.shape, dtype=np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
(cnts, _) = contours.sort_contours(cnts, method="left-to-right")
ROI_number = 0
for c in cnts:
area = cv2.contourArea(c)
if area < 800 and area > 200:
x,y,w,h = cv2.boundingRect(c)
ROI = 255 - thresh[y:y+h, x:x+w]
cv2.drawContours(mask, [c], -1, (255,255,255), -1)
cv2.imwrite('ROI_{}.png'.format(ROI_number), ROI)
ROI_number += 1
cv2.imshow('mask', mask)
cv2.imshow('thresh', thresh)
cv2.waitKey()

OpenCV: How to remove text from background

Right now I am trying to create one program, which remove text from background but I am facing a lot of problem going through it
My approach is to use pytesseract to get text boxes and once I get boxes, I use cv2.inpaint to paint it and remove text from there. In short:
d = pytesseract.image_to_data(img, output_type=Output.DICT) # Get text
n_boxes = len(d['level']) # get boxes
for i in range(n_boxes): # Looping through boxes
# Get coordinates
(x, y, w, h) = (d['left'][i], d['top'][i], d['width'][i], d['height'][i])
crop_img = img[y:y+h, x:x+w] # Crop image
gray = cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY)
gray = inverte(gray) # Inverse it
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)[1]
dst = cv2.inpaint(crop_img, thresh, 10, cv2.INPAINT_TELEA) # Then Inpaint
img[y:y+h, x:x+w] = dst # Place back cropped image back to the source image
Now the problem is that I am not able to remove text completely
Image:
Now I am not sure what other method I can use to remove text from image, I am new to this that's why I am facing problem. Any help is much appreciated
Note: Image looks stretched because I resized it to show it in screen size
Original Image:
Here's an approach using morphological operations + contour filtering
Convert image to grayscale
Otsu's threshold to obtain a binary image
Perform morph close to connect words into a single contour
Dilate to ensure that all bits of text are contained in the contour
Find contours and filter using contour area
Remove text by "filling" in the contour rectangle with the background color
I used chrome developer tools to determine the background color of the image which was (222,228,251). If you want to dynamically determine the background color, you could try finding the dominant color using k-means. Here's the result
import cv2
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,3))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, close_kernel, iterations=1)
dilate_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,3))
dilate = cv2.dilate(close, dilate_kernel, iterations=1)
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
area = cv2.contourArea(c)
if area > 800 and area < 15000:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(image, (x, y), (x + w, y + h), (222,228,251), -1)
cv2.imshow('image', image)
cv2.waitKey()

How to invert the background of square headers from black to white in an image using python?

I am trying to make the background of the square headers (The black bar that contains TERMS PONUMBER PROJECT) white and the text within black.
I have tried using the findContours method to find the contours and then crop and invert them so that I get them in the black text and white background form. But the problem is I am not having any idea on how to proceed ahead or is there any better approach to this
image =cv2.imread("default.jpg")
gray=cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
th, thresh = cv2.threshold(gray,1, 255, cv2.THRESH_BINARY_INV)
kernel = cv2.getStructuringElemnt(cv2.MORPH_ELLIPSE,(7,7))
morp_image = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
contours = cv2.findContours(morp_image,
cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
cnts = sorted(contours,key=cv2.contourArea)[-1]
The code above does find each such contour on an individual basis like if I change the [-1] in the last line of the code to [-2], it will find the next contour but I want to find all such areas in the image in a single go and make the background of such areas white while changing the text to black.
Thanks
Here's a simple approach
Convert image to grayscale and Gaussian blur
Otsu's threshold to obtain binary image
Find contours
Filter using the number of corners and contour area
Extract ROI, invert ROI, and replace into original image
The idea is that if the contour has 4 corners, it must be a square/rectangle. In addition, we filter using a minimum contour area to ignore noise. If the contour passes our filter then we have a desired ROI to invert. The detected ROIs
Now we extract each ROI using Numpy slicing. Here's each ROI before and after inverting
Now we simply replace each inverted ROI back into the original image to get our result
import cv2
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.015 * peri, True)
area = cv2.contourArea(c)
if len(approx) == 4 and area > 1000:
x,y,w,h = cv2.boundingRect(c)
ROI = 255 - image[y:y+h,x:x+w]
image[y:y+h, x:x+w] = ROI
cv2.imshow('image', image)
cv2.imwrite('image.png', image)
cv2.waitKey()

How do I make masks to set all of image background, except the text, to white?

I'm trying to extract the text in this region to run OCR, but the stray black edges are interfering with some results. Is there a way to isolate this text?
After finding this contour, I've cropped it out of the original image with a black background mask. I'm not too sure how to change the background to white, nor can I figure out a way to get rid of the black edges around the contour. Thresholding the image seems to get rid of some of the black pixels in the text, which I don't want.
Ideally the output should be simply the black text, and a white background.
This is a section in the code of the original masking that I've attempted-
mask = np.ones(orig_img.shape).astype(orig_img.dtype)
cv2.fillPoly(mask, [cnt], (255,255,255))
cropped_contour = cv2.bitwise_and(orig_img, mask)
To isolate the text, one approach is to obtain the bounding box coordinates of the desired ROI and then mask that ROI onto a blank white image. The main idea is:
Convert image to grayscale
Threshold image
Dilate image to connect text as a single bounding box
Find contours and filter used contour area to find ROI
Place ROI onto mask
Threshold image (left) then dilate to connect text (right)
You can find contours using cv2.boundingRect() then once you have the ROI, you can place this ROI onto the mask with
mask = np.zeros(image.shape, dtype='uint8')
mask.fill(255)
mask[y:y+h, x:x+w] = original_image[y:y+h, x:x+w]
Find contours then filter for ROI (left), final result (right)
Depending on your image size, you may need to adjust the filter for the contour area.
import cv2
import numpy as np
original_image = cv2.imread('1.png')
image = original_image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
dilate = cv2.dilate(thresh, kernel, iterations=5)
# Find contours
cnts = cv2.findContours(dilate, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
# Create a blank white mask
mask = np.zeros(image.shape, dtype='uint8')
mask.fill(255)
# Iterate thorugh contours and filter for ROI
for c in cnts:
area = cv2.contourArea(c)
if area < 15000:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
mask[y:y+h, x:x+w] = original_image[y:y+h, x:x+w]
cv2.imshow("mask", mask)
cv2.imshow("image", image)
cv2.imshow("dilate", dilate)
cv2.imshow("thresh", thresh)
cv2.imshow("result", image)
cv2.waitKey(0)

Categories