How to remove small object in image with Python - python

i have a problem with my python code. I want to make image processing with chest X-rays in order to obtain a lung pattern. but my code results still have little stains. how to get rid of these small objects
and this is my code
import cv2
import numpy as np
from skimage import morphology
im = cv2.imread('image.jpg')
ret, thresh = cv2.threshold(im, 150, 255, cv2.THRESH_BINARY)
kernel = np.ones((5, 5), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
cleaned = morphology.remove_small_objects(opening, min_size=62, connectivity=2)
cv2.imshow("cleaned", cleaned)
cv2.waitKey(0)
P.S :
when i try with the matlab code, the small object can be removed with this code
K=bwareaopen(~K,1500); %Remove small object (area) pixels less than 1500 pixels
and that code can remove small object well:

You can filter using contour area then apply morpholgical closing to fill the small holes in the image. Here's the result:
import cv2
# Load image, convert to grayscale, Gaussian blur, Otsu's threshold
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]
# Filter using contour area and remove small noise
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:
area = cv2.contourArea(c)
if area < 5500:
cv2.drawContours(thresh, [c], -1, (0,0,0), -1)
# Morph close and invert image
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
close = 255 - cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)
cv2.imshow('thresh', thresh)
cv2.imshow('close', close)
cv2.waitKey()

From the Documentation for bwareaopen you can find the algorithm that is used in the method, which is:
Determine the connected components:
CC = bwconncomp(BW, conn);
Compute the area of each component:
S = regionprops(CC, 'Area');
Remove small objects:
L = labelmatrix(CC);
BW2 = ismember(L, find([S.Area] >= P));
You could simply follow these steps to get to the result.

Related

Infill image gaps after Morph Open opencv

I have an input signature for which I want to remove the gridlines of same color.
So far I am using this python code to do that
import cv2
import numpy as np
## Load image and make B&W version
image = cv2.imread('Downloads/image.png')
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Remove horizontal
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50,1))
detected_lines = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detected_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(thresh, [c], -1, (0,0,0), 2)
cv2.imwrite('Downloads/out.png', thresh)
Which takes me to the following image.
Now I am trying to infill the gaps introduced, I tried using a vertical kernel and morph close but that infills the other spaces in images a lot.
Any ideas on how I can achieve the said infilling using a more sophisticated operation and get the infill done without tampering with the existing signature much.

Extract text with strikethrough from image

Here's an example image ->
I would like to extract text that has text-decoration/styling of strikethrough.
So for the above image I would like to extract - de location
How would I do this ?
Here's what I have so far using OpenCV and python :
import cv2
import numpy as np
import matplotlib.pyplot as plt
im = cv2.imread(<image>)
kernel = np.ones((1,44), np.uint8)
morphed = cv2.morphologyEx(im, cv2.MORPH_CLOSE, kernel)
plt.imshow(morphed)
This gives me the horizontal lines ->
I am new to image processing and hence having a difficult time isolating only the text that has strikethroughs.
Bonus -> Along with the strikethrough text, I would like to also extract neighboring text so that I can correctly style/mark the strikethrough text information back along with other text.
UPDATE 1 :
Based on the first answer I did the following : -
import cv2
# Load image, convert to grayscale, Otsu's threshold
image = cv2.imread('image.png')
result = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV +
cv2.THRESH_OTSU)[1]
# Detect horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(40,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN,
horizontal_kernel, iterations=10)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(result, [c], -1, (36,255,12), 2)
plt.imshow(result)
I was able to get this image -
I tried playing with the values for the horizontal kernel but no luck.
UPDATE 2:
I modified the above snippet further and got this -
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Load image, convert to grayscale, Otsu's threshold
result = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
kernel = np.ones((4,2),np.uint8)
erosion = cv2.erode(thresh,kernel,iterations = 1)
dilation = cv2.dilate(thresh,kernel,iterations = 1)
trans = dilation
# plt.imshow(erosion)
# Detect horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (8,1))
detect_horizontal = cv2.morphologyEx(trans, cv2.MORPH_OPEN, horizontal_kernel, iterations=10)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(result, [c], -1, (36,255,12), 2)
plt.imshow(result)
I was able to get this image -
And this solution applies to my other image types as well -
This is not a 100% accuracy solution (failed to get the de strikethrough text) but I like the performance so far.
Now, I am struggling with how to check if the neighboring pixels are black or white to isolate the strikethrough.
one way you can achieve this is:
Binarise the image (https://docs.opencv.org/master/d7/d4d/tutorial_py_thresholding.html)
Find horizontal lines (Horizontal Line detection with OpenCV)
For each line, check if the top and bottom pixels are white or not
If there are non white top and bottom pixels, that region corresponds to strikethrough
Do a connected component of the image (connected component labeling in python)
Check the label corresponding to the lines detected previously and mask that label to get the strike-through texts.
You can use a strikethrough property such as thickness. The thickness of the strikethrough line is less than the underline. It can be select by morphology and restore the connected components by morphological reconstruction.
import cv2
img = cv2.imread('juFpe.png', cv2.IMREAD_GRAYSCALE)
thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY_INV )[1]
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(1,5))
kernel2=cv2.getStructuringElement(cv2.MORPH_RECT,(8,8))
detect_thin = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
detect_thin = cv2.morphologyEx(detect_thin, cv2.MORPH_DILATE, kernel2)
marker=cv2.compare(detect_thin, thresh,cv2.CMP_LT) # thin lines
while True: #morphological reconstruction
tmp=marker.copy()
marker=cv2.dilate(marker, kernel2)
marker=cv2.min(thresh, marker)
difference = cv2.subtract(marker, tmp)
if cv2.countNonZero(difference) == 0:
break
cv2.imwrite('lines.png', marker)
Result:

How to highlight unusual marks on mobile devices

I'm working on sample task where I need to highlight unusual marks on any mobile devices. I'm trying with opencv python. But, I'm not getting actual contors for the unusual marks.
Input image is like:
And output image is expected as below:
I'm trying something like below, but it didn't work.
import cv2
from matplotlib import pyplot as plt
blurValue = 15
img_path = "input.jpg"
# reading the image
image = cv2.imread(img_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (blurValue, blurValue), 0)
edged = cv2.Canny(image, 100, 255)
#applying closing function
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))
closed = cv2.morphologyEx(edged, cv2.MORPH_CLOSE, kernel)
lower = np.array([4, 20, 93])
upper = np.array([83, 79, 166])
# hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# blur = cv2.GaussianBlur(hsv, (blurValue, blurValue), 0)
mask = cv2.inRange(closed, lower, upper)
result_1 = cv2.bitwise_and(frame, frame, mask = mask)
cnts = cv2.findContours(result_1.copy(), 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.02 * peri, True)
cv2.drawContours(image, [approx], -1, (0, 255, 0), 2)
plt.imshow(image)
plt.title("image")
plt.show()
Any help will be appreciated. Thank you.
My suggestion would be to use adaptive thresholding and filter on area (and possibly other characteristics). Here is my code and results using Python OpenCV.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread("iphone.jpg")
# convert img to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# apply gaussian blur
blur = cv2.GaussianBlur(gray, (29,29), 0)
# do adaptive threshold on gray image
thresh = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 51, 3)
# apply morphology open then close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (17, 17))
open = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
close = cv2.morphologyEx(open, cv2.MORPH_CLOSE, kernel)
# Get contours
cnts = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
result = img.copy()
for c in cnts:
area = cv2.contourArea(c)
if area < 10000 and area > 5000:
cv2.drawContours(result, [c], -1, (0, 255, 0), 2)
# write results to disk
cv2.imwrite("iphone_thresh.jpg", thresh)
cv2.imwrite("iphone_close.jpg", close)
cv2.imwrite("iphone_markings.jpg", result)
# display it
cv2.imshow("IMAGE", img)
cv2.imshow("THRESHOLD", thresh)
cv2.imshow("CLOSED", close)
cv2.imshow("RESULT", result)
cv2.waitKey(0)
Thresholded Image:
Morphology Processed Image:
Final Result:
I would also suggest that you align the image with a known clean iPhone image and create a mask of the camera and logo, etc., markings so that you can filter the results to exclude those (and perhaps even the border of the camera outline).

Counting special elements on image with OpenCV and Python

I want to count number of trees on this picture from above.
I know how to count elements, but till now I used images with white background, so counting is much easier. But on image like this i do not know what to do:
I converted the image to gray, and then done the threshold *(threshold value is done by hand, is there a way to find it automaticly?), my next idea is to find the 'centers' of black dots, or to 'group' them.
I also tried to change brightness and contrast but it didnt work.
What should I do?
This is the code that I wrote:
import cv2
import numpy as np
# Read image
img = cv2.imread('slika.jpg')
# Convert image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Show grayscale image
cv2.imshow('gray image', gray)
cv2.waitKey(0)
#BIG PROBLEM: IM FINDING VALUE OF `40` IN THE LINE BELOW MANUALLY
# Inverse binary threshold image with threshold at 40,
_, threshold_one = cv2.threshold(gray, 40 , 255, cv2.THRESH_BINARY_INV)
# Show thresholded image
cv2.imshow('threshold image', threshold_one)
cv2.waitKey(0)
# Find contours
contours, h = cv2.findContours(threshold_one, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print('Number of trees found:', len(contours)) #GIVES WRONG RESULT
# Iterate all found contours
for cnt in contours:
# Draw contour in original/final image
cv2.drawContours(img, [cnt], 0, (0, 0, 255), 1)
# Show final image
cv2.imshow('result image', img)
cv2.waitKey(0)
This is the image with treshold, I have tried to blur it (in order to connect black dots), but the final output is the same:
This is the result image:
Here's a rough method to estimate the number of trees. The idea to to model each tree as a blob then use contour filtering with a minimum threshold area to ignore the noise. To determine automatic threshold levels, you can use Otsu's threshold by appending cv2.THRESH_OTSU or Adaptive threshold with cv2.adaptiveThreshold(). This approach has problems when the trees are very close together since they form a single blob. Possible improvements could be to find the average area of each tree then find the floor with a large blob. You would probably need to train a classifier and use deep/machine learning for better accuracy
Trees: 102
import cv2
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)
opening = cv2.morphologyEx(close, cv2.MORPH_OPEN, kernel, iterations=2)
cnts = cv2.findContours(opening, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
trees = 0
for c in cnts:
area = cv2.contourArea(c)
if area > 50:
x,y,w,h = cv2.boundingRect(c)
cv2.drawContours(image, [c], -1, (36,255,12), 2)
trees += 1
print('Trees:', trees)
cv2.imshow('image', image)
cv2.waitKey()

OpenCV detecting a single symbol with multiple bounding boxes

I am using OpenCV to put bounding boxes on handwritten math equation inputs. Currently, my code sometimes places multiple smaller bounding boxes around different parts of a singular image instead of creating one large box around the image. I'm not sure why this is happening. My current code to filter the image and find the contours to draw the bounding box is as follows:
img = cv2.imread(imgpath)
morph = img.copy()
morph = cv2.fastNlMeansDenoising(img)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 1))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 15))
# take morphological gradient
gradient_image = cv2.morphologyEx(morph, cv2.MORPH_GRADIENT, kernel)
gray = cv2.cvtColor(gradient_image, cv2.COLOR_BGR2GRAY)
img_grey = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel)
blur = cv2.medianBlur(img_grey,3)
ret, thing = cv2.threshold(blur, 0.0, 255.0, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
img_dilation = cv2.dilate(thing, kernel, iterations=3)
conturs_lst = cv2.findContours(img_dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
An example of the actual result is as follows:
OG Image:
You have the right idea but I think you're overusing cv2.morphologyEx to continuously erode and dilate the image. You mention your problem:
Currently, my code sometimes places multiple smaller bounding boxes around different parts of a singular image instead of creating one large box around the image.
When you use cv2.findContours, its working correctly but since your contours are actually blobs instead of one interconnected singular image, it creates multiple bounding boxes. To remedy this problem, you can dilate the image to connect the blobs together.
I've rewrote your code without the extra cv2.morphologyEx repetitions. The main idea is as follows:
Convert the image into grayscale
Blur image
Threshold image to separate background from desired object
Dilate image to connect blobs to form a singular image
Find contours and filter contours using threshold min/max area
Threshold image to isolate desired sections. Note some of the contours have broken connections. To fix this, we dilate the image to connect the blobs.
Dilate image to form singular objects. Now note we have the unwanted horizontal section at the bottom, we can find contours and then filter using area to remove that section.
Results
import numpy as np
import cv2
original_image = cv2.imread("1.jpg")
image = original_image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
thresh = cv2.threshold(blurred, 160, 255, cv2.THRESH_BINARY_INV)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
dilate = cv2.dilate(thresh, kernel , iterations=4)
cv2.imshow("thresh", thresh)
cv2.imshow("dilate", dilate)
# Find contours in the image
cnts = cv2.findContours(dilate.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
contours = []
threshold_min_area = 400
threshold_max_area = 3000
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
area = cv2.contourArea(c)
if area > threshold_min_area and area < threshold_max_area:
# cv2.drawContours(original_image,[c], 0, (0,255,0), 3)
cv2.rectangle(original_image, (x,y), (x+w, y+h), (0,255,0),1)
contours.append(c)
cv2.imshow("detected", original_image)
print('contours detected: {}'.format(len(contours)))
cv2.waitKey(0)

Categories