I am trying to extract only striped region from this image.
This is the region I want to extract.
There could be multiple approaches and perhaps their combination.
Use thresholding,morphology,grabcut operations to extract stripes
Gabor filter
Fourier transformation
How do I detect the orientation of the stripes from a fft2 transofrmation.
import numpy as np
import cv2
import os
import sys
from matplotlib import pyplot as plt
plt.figure(figsize=(12, 12))
gray = cv2.imread('zebra.jpg',0)
f = np.fft.fft2(gray)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20*np.log(np.abs(fshift))
imgs_comb = np.hstack([gray,magnitude_spectrum])
plt.axis('off')
plt.title('magnitude_spectrum')
plt.imshow(imgs_comb,cmap='gray')
plt.show()
The set of images are unique with respect to their pavement type, paint recency.often time paint is worn out. Despite all these variations, FFT images are consistently giving me correct orientation and frequnecy. The result appears promising in that I could visually see the frequency representative of the pattern as well as their orientation(dominant vertical pattern in the image).
How do we use fft image for filtering out other regions ?
Appreciate other suggestions using other approaches.
Instead of using fft2, here's an approach using thresholding + morphology. The idea is to obtain a binary image through Otsu's thresholding then detect the horizontal lines using morphology. From here we draw the detected lines onto a mask then perform additional morphological operations to combine the stripes into a single contour. From here we find the bounding rectangle and extract the ROI
Otsu's threshold -> Draw lines onto mask
-> Opening/Closing -> Detected ROI -> Extracted ROI
import cv2
import numpy as np
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_OTSU + cv2.THRESH_BINARY)[1]
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (30,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
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(mask, [c], -1, (255,255,255), 5)
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,5))
close = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=3)
opening = cv2.morphologyEx(close, cv2.MORPH_OPEN, kernel, iterations=2)
x,y,w,h = cv2.boundingRect(opening)
ROI = image[y:y+h, x:x+w]
cv2.imshow('thresh', thresh)
cv2.imshow('mask', mask)
cv2.imshow('opening', opening)
cv2.imshow('ROI', ROI)
cv2.waitKey()
Related
I try to use python, NumPy, and OpenCV to analyze the image below and just draw a circle on each object found. The idea here is not to identify the bug only identify any object that is different from the background.
Original Image:
Here is the code that I'm using.
import cv2
import numpy as np
img = cv2.imread('per.jpeg', cv2.IMREAD_GRAYSCALE)
if cv2.__version__.startswith('2.'):
detector = cv2.SimpleBlobDetector()
else:
detector = cv2.SimpleBlobDetector_create()
keypoints = detector.detect(img)
print(len(keypoints))
imgKeyPoints = cv2.drawKeypoints(img, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
status = cv2.imwrite('teste.jpeg',imgKeyPoints)
print("Image written to file-system : ",status)
But the problem is that I'm getting only a greyscale image as result without any counting or red circle, as shown below:
Since I'm new to OpenCV and object recognition world I'm not able to identify what is wrong, and any help will be very appreciated.
Here is one way in Python/OpenCV.
Threshold on the bugs color in HSV colorspace. Then use morphology to clean up the threshold. Then get contours. Then find the minimum enclosing circle around each contour. Then bias the radius to make a bit larger and draw the circle around each bug.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('bugs.jpg')
# convert image to hsv colorspace
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# threshold on bugs color
lower=(0,90,10)
upper=(100,250,170)
thresh = cv2.inRange(hsv, lower, upper)
# apply morphology to clean up
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (6,6))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# get external contours
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
result = img.copy()
bias = 10
for cntr in contours:
center, radius = cv2.minEnclosingCircle(cntr)
cx = int(round(center[0]))
cy = int(round(center[1]))
rr = int(round(radius)) + bias
cv2.circle(result, (cx,cy), rr, (0, 0, 255), 2)
# save results
cv2.imwrite('bugs_threshold.jpg', thresh)
cv2.imwrite('bugs_cleaned.jpg', morph)
cv2.imwrite('bugs_circled.jpg', result)
# display results
cv2.imshow('thresh', thresh)
cv2.imshow('morph', morph)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold Image:
Morphology Cleaned Image:
Resulting Circles:
I have subsets of images that contains digits. Each subset is read by Tesseract for OCR. Unfortunately for some images the cropping from the original image isn't optimal.
Hence some artifacts/remains at the top and bottom of the image and hamper Tesseract to recognize characters on the image. Then I would like to get rid of these artifacts and get to a similar result:
First I considered a simple approach: I set the first row of pixels as the reference: if an artifact was found along the x-axis (i.e., a white pixel if the image is binarized), I removed it along the y-axis until the next black pixel. Code for this approach is the one below:
import cv2
inp = cv2.imread("testing_file.tif")
inp = cv2.cvtColor(inp, cv2.COLOR_BGR2GRAY)
_,inp = cv2.threshold(inp, 150, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
ax = inp.shape[1]
ay = inp.shape[0]
out = inp.copy()
for i in range(ax):
j = 0
while j in range(ay):
if out[j,i] == 255:
out[j,i] = 0
else:
break
j+=1
out = cv2.bitwise_not(out)
cv2.imwrite('output.png',out)
But the result isn't good at all:
Then I stumbled across the flood_fill function from scipy (here) but found out it was too much time consuming and still not efficient. A similar question was asked on SO here but didn't help so much. Maybe a k-nearest neighbor approach could be considered? I also found out that methods that consist in merging neighbors pixels under some criteria were called growing methods, among which the single linkage is the most common (here).
What would you recommend to remove the upper and lower artifacts?
Here's a simple approach:
Convert image to grayscale
Otsu's threshold to obtain binary image
Cerate special horizontal kernel and dilate
Detect horizontal lines, sort for largest contour, and draw onto mask
Bitwise-and
After converting to grayscale, we Otsu's threshold to get a binary image
# Read in image, convert to grayscale, and Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
Next we create a long horizontal kernel and dilate to connect the numbers together
# Create special horizontal kernel and dilate
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (70,1))
dilate = cv2.dilate(thresh, horizontal_kernel, iterations=1)
From here we detect horizontal lines and sort for the largest contour. The idea is that the largest contour will be the middle section of the numbers where the numbers are all "complete". Any smaller contours will be partial or cut off numbers so we filter them out here. We draw this largest contour onto a mask
# Detect horizontal lines, sort for largest contour, and draw on mask
mask = np.zeros(image.shape, dtype=np.uint8)
detected_lines = cv2.morphologyEx(dilate, cv2.MORPH_OPEN, horizontal_kernel, iterations=1)
cnts = cv2.findContours(detected_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
for c in cnts:
cv2.drawContours(mask, [c], -1, (255,255,255), -1)
break
Now that we have the outline of the desired numbers, we simply bitwise-and with our original image and color the background white to get our result
# Bitwise-and to get result and color background white
mask = cv2.cvtColor(mask,cv2.COLOR_BGR2GRAY)
result = cv2.bitwise_and(image,image,mask=mask)
result[mask==0] = (255,255,255)
Full code for completeness
import cv2
import numpy as np
# Read in image, convert to grayscale, and Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Create special horizontal kernel and dilate
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (70,1))
dilate = cv2.dilate(thresh, horizontal_kernel, iterations=1)
# Detect horizontal lines, sort for largest contour, and draw on mask
mask = np.zeros(image.shape, dtype=np.uint8)
detected_lines = cv2.morphologyEx(dilate, cv2.MORPH_OPEN, horizontal_kernel, iterations=1)
cnts = cv2.findContours(detected_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
for c in cnts:
cv2.drawContours(mask, [c], -1, (255,255,255), -1)
break
# Bitwise-and to get result and color background white
mask = cv2.cvtColor(mask,cv2.COLOR_BGR2GRAY)
result = cv2.bitwise_and(image,image,mask=mask)
result[mask==0] = (255,255,255)
cv2.imshow('thresh', thresh)
cv2.imshow('dilate', dilate)
cv2.imshow('result', result)
cv2.waitKey()
I have an image with measurements that I need to read with python and right now it reads the most text but not all because some lines are in the way. I cant use the original image so I made an image that looks like the one I'm using.
def erode(img):
kernel = np.ones((3,3), np.uint8)
eroded = cv2.erode(img, kernel, iterations=1)
gray = cv2.cvtColor(eroded,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize = 3)
minLineLength = 10
maxLineGap = 1
lines = cv2.HoughLinesP(edges,1,np.pi/180,120,minLineLength,maxLineGap)
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(eroded,(x1,y1),(x2,y2),(255,255,255),7)
I have tried using the OpenCV function houghLinesP and drawing a line over these but this doesn't remove all lines and still leaves some dots all over the place like this:
what I want is to give something like this as input:
and get something like this as an output:
the reason I need to remove all the lines but not change the
text is because I need to save the text coordinates.
The idea is to dilate and connect the text together to form a single contour. From here we can find contours and filter using a minimum threshold area. If it passes this filter then we have a desired text ROI to keep and we draw this ROI onto a mask
import cv2
import numpy as np
image = cv2.imread('3.png')
mask = np.ones(image.shape, dtype=np.uint8) * 255
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=3)
cnts = cv2.findContours(dilate, 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 < 5000:
x,y,w,h = cv2.boundingRect(c)
mask[y:y+h, x:x+w] = image[y:y+h, x:x+w]
cv2.imshow('thresh', thresh)
cv2.imshow('dilate', dilate)
cv2.imshow('mask', mask)
cv2.waitKey()
I am trying to clear out the grids so that I can use the plot alone in a different process. I am trying to solve it using opencv. I have both actual and inverted image. I am not expert in python. Any help could be great.
Thanks in advance
Actual image
inverted image
You can use the opening or closing of your image (depending if you are using the normal or inverted image). Opening will first erode your image and then dilate it. This will remove small/thin objects assuming bright objects over black background.
For example, in the case of your inverted image, use
out = cv2.morphologyEx(src, MORPH_OPEN)
For more information check out this tutorial
Here's an approach that uses a combination of filtering techniques and masks.
Convert image to grayscale and median blur
Adaptive threshold image
Perform morphological transformations
Find contours and filter using contour area
Create a mask to keep the desired ROI sections
Bitwise-and to extract plot
Here's the result
import cv2
import numpy as np
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray, 15)
thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,11,3)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
erode = cv2.erode(thresh, kernel, iterations=1)
dilate = cv2.dilate(erode, kernel, iterations=3)
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
mask = np.zeros(image.shape, dtype=np.uint8)
for c in cnts:
area = cv2.contourArea(c)
if area > 850:
cv2.drawContours(mask, [c], -1, (255,255,255), -1)
mask = cv2.dilate(mask, kernel, iterations=1)
image = 255 - image
result = 255 - cv2.bitwise_and(mask, image)
cv2.imshow('result', result)
cv2.waitKey(0)
2nd Approach
Here's an alternative approach which is the same as the 1st approach but uses specialized horizontal and vertical kernels for filtering instead. This approach is probably more robust. Instead of using guess and check morphological transformations, we have dedicated kernels that filter out the horizontal/vertical grid lines.
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,5))
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,1))
Here's the mask result after going through each kernel
The result is pretty much the same but slightly cleaner :)
import cv2
import numpy as np
image = cv2.imread('1.png',0)
blur = cv2.GaussianBlur(image, (5,5), 0)
thresh = cv2.threshold(blur, 130, 255, cv2.THRESH_BINARY_INV)[1]
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,5))
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,1))
remove_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel)
remove_vertical = cv2.morphologyEx(remove_horizontal, cv2.MORPH_OPEN, horizontal_kernel)
cnts = cv2.findContours(remove_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
mask = np.ones(image.shape, dtype=np.uint8)
for c in cnts:
area = cv2.contourArea(c)
if area > 50:
cv2.drawContours(mask, [c], -1, (255,255,255), -1)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
mask = cv2.dilate(mask, kernel, iterations=1)
image = 255 - image
result = 255 - cv2.bitwise_and(mask, image)
cv2.imshow('result', result)
cv2.imwrite('result.png', result)
cv2.imshow('mask', mask)
cv2.waitKey(0)
Here is a pretty simple solution using Imagemagick. But the same concepts can be use from OpenCV. Sorry, I am not fluent with OpenCV.
Threshold the image
Perform morphologic close
Use connected components processing to remove extraneous features
Input:
kernel="5x5: 0,0,0,0,0 1,1,1,1,1 1,1,1,1,1 1,1,1,1,1 0,0,0,0,0"
convert img.png -threshold 75% \
-morphology close "$kernel" \
-define connected-components:area-threshold=100 \
-define connected-components:mean-color=true \
-connected-components 4 result.png
ADDITION:
Here is Python Wand code to do the same thing. Python Wand is based upon Imagemagick. It will require Wand 0.5.6 (when available) and Imagemagick 7.
#!/bin/python3.7
from wand.image import Image
with Image(filename='curve.png') as img:
krnl="5x5: 0,0,0,0,0 1,1,1,1,1 1,1,1,1,1 1,1,1,1,1 0,0,0,0,0"
img.threshold(threshold=0.75)
img.morphology(method='close',kernel=krnl)
img.connected_components(connectivity=4, area_threshold=100, mean_color=True)
img.save(filename='curve_proc.png')
Hello, for a personal project I need to crop out extract this underwater gate from an image, and leave out anything other than the gate. The image is colored here but I can assume that the image of the gate I receive will only be lined, with the gate being white lines and the background being black. Could anyone give me any advice about how to go about this solution? I'm a novice when it comes to OpenCV so I'm a bit lost.
Here's the main idea
Gaussian blur image and extract blue channel
Threshold image with cv2.threshold()
Erode to remove black lines and isolate gate with cv2.erode()
Find contours and filter for gate contour using cv2.findContours() and cv2.contourArea()
Create a mask and dilate image using cv2.dilate()
Crop gate using cv2.bitwise_and()
import cv2
import numpy as np
# Load in image and create copy
image = cv2.imread('1.png')
original = image.copy()
# Gaussian blur and extract blue channel
blur = cv2.GaussianBlur(image, (3,3), 0)
blue = blur[:,:,0]
# Threshold image and erode to isolate gate contour
thresh = cv2.threshold(blue,135, 255, cv2.THRESH_BINARY_INV)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
erode = cv2.erode(thresh, kernel, iterations=4)
# Create a mask and find contours
mask = np.zeros(original.shape, dtype=np.uint8)
cnts = cv2.findContours(erode, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
# Filter for gate contour using area and draw onto mask
for c in cnts:
area = cv2.contourArea(c)
if area > 6000:
cv2.drawContours(mask, [c], -1, (255,255,255), 2)
# Dilate to restore contour and mask it with original image
dilate = cv2.dilate(mask, kernel, iterations=7)
result = cv2.bitwise_and(original, dilate)
cv2.imshow('thresh', thresh)
cv2.imshow('erode', erode)
cv2.imshow('mask', mask)
cv2.imshow('dilate', dilate)
cv2.imshow('result', result)
cv2.waitKey()