I have an image of a graph. I perform some preprocessing functions on the image in order to extract the graph line (which works). I then, however, try to find the contour of the graph line that is found and saved as a separate image. When I do this, however, I do not get the desired results.
Graph line extracted
Contour found of the above image
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("/Users/2020shatgiskessell/Desktop/graph_extracting/Test_Graphs/Graph2.jpg")
h,w = img.shape[:2]
mask = np.zeros((h,w), np.uint8)
mask2 = mask = np.zeros((h,w), np.uint8)
def find_contours(image):
# Transform to gray colorspace and threshold the image
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# erod then dialate image (for denoising)
kernel = np.ones((2,2),np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
#Find contours in order of hiarchy
#CHAIN_APPROX_NONE gives all the points on the contour
_, contours, hierarchy = cv2.findContours(opening,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
return contours
#---------------------------------------------------------------
#CLEAN UP IMAGE AND JUST EXTRACT LINE
#get the biggest contour
cnt = max(find_contours(img), key=cv2.contourArea)
cv2.drawContours(mask, [cnt], 0, 255, -1)
# Perform a bitwise operation
res = cv2.bitwise_and(img, img, mask=mask)
# Threshold the image again
gray = cv2.cvtColor(res,cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# Find all non white pixels of image
non_zero = cv2.findNonZero(thresh)
# Transform all other pixels in non_white to white
for i in range(0, len(non_zero)):
first_x = non_zero[i][0][0]
first_y = non_zero[i][0][1]
first = res[first_y, first_x]
res[first_y, first_x] = 255
# Display the image
cv2.imwrite("extractedline.png", res)
#-------------------------------------------------------
#GET CONTOUR OF EXTRACTED LINE - NOT WORKING
i = 0
#Display contours
for contour in find_contours(res):
#approximate the contour shape
cv2.drawContours(mask2, [contour], 0, 255, -1)
res2 = cv2.bitwise_and(res,res,mask=mask2)
i = i+1
print (i)
cv2.imshow('after', mask2)
Related
The output rank the 8 objects contours from biggest to smallest(1-8), how can I make it so that I perform a function on each of the contours individually based on its rank. For example to mask only contour 1(biggest contour)
import cv2
import numpy as np
import matplotlib.pyplot as plt
#Read image
image1 = cv2.imread('C:\\Users\\marnes\\Downloads\\starch-staining-patterns-in-Honeycrisp-
applesx.png')
#make a copy of the source image
image1_copy = image1.copy()
#convert image to grayscale
gray = cv2.cvtColor(image1_copy, cv2.COLOR_BGR2GRAY)
#invert the color
gray_inverted = cv2.bitwise_not(gray)
#create binary threshold image
_, binary = cv2.threshold(gray_inverted, 120, 200, cv2.THRESH_BINARY)
#convert image to grayscale
gray = cv2.cvtColor(image1_copy, cv2.COLOR_BGR2GRAY)
#find all contours in the image
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
#sort contours in decreasing order
sorted_contours = sorted(contours, key=cv2.contourArea, reverse= True)
# Draw largest 8 contours
for i, cont in enumerate(sorted_contours[:8],1):
# Draw the contour
cv2.drawContours(image1_copy, cont, -1, (0,255,0), 3)
# Display the position of contour in sorted list
cv2.putText(image1_copy, str(i), (cont[0,0,0], cont[0,0,1]+40), cv2.FONT_HERSHEY_SIMPLEX,
1, ( 255, 255, 255),4)
#Display the results
cv2.imshow('image', image1_copy)
cv2.waitKey(0)
I have the following image of a lego board with some bricks on it
Now I am trying to detect the thick black lines (connecting the white squares) with OpenCV. I have already experimented a lot with HoughLinesP, converted the image to gray or b/w before, applied blur, ...
Nonthing led to usable results.
# Read image
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
# Resize Image
img = cv2.resize(img, (0,0), fx=0.25, fy=0.25)
# Initialize output
out = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
# Median blurring to get rid of the noise; invert image
img = cv2.medianBlur(img, 5)
# Adaptive Treshold
bw = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv2.THRESH_BINARY,15,8)
# HoughLinesP
linesP = cv2.HoughLinesP(bw, 500, np.pi / 180, 50, None, 50, 10)
# Draw Lines
if linesP is not None:
for i in range(0, len(linesP)):
l = linesP[i][0]
cv2.line(out, (l[0], l[1]), (l[2], l[3]), (0,0,255), 3, cv2.LINE_AA)
The adaptive treshold lets you see edges quite well, but with HoughLinesP you don't get anything usable out of it
What am I doing wrong?
Thanks, both #fmw42 and #jeru-luke for your great solutions to this problem! I liked isolating / masking the green board, so I combined both:
import cv2
import numpy as np
img = cv2.imread("image.jpg")
scale_percent = 50 # percent of original size
width = int(img.shape[1] * scale_percent / 100)
height = int(img.shape[0] * scale_percent / 100)
dim = (width, height)
# resize image
img = cv2.resize(img, dim, interpolation = cv2.INTER_AREA)
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
a_component = lab[:,:,1]
# binary threshold the a-channel
th = cv2.threshold(a_component,127,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]
# numpy black
black = np.zeros((img.shape[0],img.shape[1]),np.uint8)
# function to obtain the largest contour in given image after filling it
def get_region(image):
contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
c = max(contours, key = cv2.contourArea)
mask = cv2.drawContours(black,[c],0,255, -1)
return mask
mask = get_region(th)
# turning the region outside the green block white
green_block = cv2.bitwise_and(img, img, mask = mask)
green_block[black==0]=(255,255,255)
# median blur
median = cv2.medianBlur(green_block, 5)
# threshold on black
lower = (0,0,0)
upper = (15,15,15)
thresh = cv2.inRange(median, lower, upper)
# apply morphology open and close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (29,29))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# filter contours on area
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
result = green_block.copy()
for c in contours:
area = cv2.contourArea(c)
if area > 1000:
cv2.drawContours(result, [c], -1, (0, 0, 255), 2)
# view result
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Here I am presenting a repeated segmentation approach using color.
This answer is based on the usage of LAB color space
1. Isolating the green lego block
img = cv2.imread(image_path)
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
a_component = lab[:,:,1]
# binary threshold the a-channel
th = cv2.threshold(a_component,127,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]
th
# function to obtain the largest contour in given image after filling it
def get_region(image):
contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
c = max(contours, key = cv2.contourArea)
black = np.zeros((image.shape[0], image.shape[1]), np.uint8)
mask = cv2.drawContours(black,[c],0,255, -1)
return mask
mask = get_region(th)
mask
# turning the region outside the green block white
green_block = cv2.bitwise_and(img, img, mask = mask)
green_block[black==0]=(255,255,255)
green_block
2. Segmenting the road
To get an approximate region of the road, I subtracted the mask and th.
cv2.subtract() performs arithmetic subtraction, where cv2 will take care of negative values.
road = cv2.subtract(mask,th)
# `road` contains some unwanted spots/contours which are removed using the function "get_region"
only_road = get_region(road)
only_road
Masking only the road segment with the original image gives
road_colored = cv2.bitwise_and(img, img, mask = only_road)
road_colored[only_road==0]=(255,255,255)
road_colored
From the above image only the black regions (road) are present, which is easy to segment:
# converting to grayscale and applying threshold
th2 = cv2.threshold(road_colored[:,:,1],127,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]
# using portion of the code from fmw42's answer, to get contours above certain area
contours = cv2.findContours(th2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
result = img.copy()
for c in contours:
area = cv2.contourArea(c)
if area > 1000:
cv2.drawContours(result, [c], -1, (0, 0, 255), 4)
result
Note:
To clean up the end result, you can apply morphological operations on th2 before drawing contours.
Here is one way to do that in Python/OpenCV.
Read the image
Apply median blur
Threshold on black color using cv2.inRange()
Apply morphology to clean it up
Get contours and filter on area
Draw contours on input
Save the result
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('black_lines.jpg')
# median blur
median = cv2.medianBlur(img, 5)
# threshold on black
lower = (0,0,0)
upper = (15,15,15)
thresh = cv2.inRange(median, lower, upper)
# apply morphology open and close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (29,29))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# filter contours on area
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
result = img.copy()
for c in contours:
area = cv2.contourArea(c)
if area > 1000:
cv2.drawContours(result, [c], -1, (0, 0, 255), 2)
# save result
cv2.imwrite("black_lines_threshold.jpg", thresh)
cv2.imwrite("black_lines_morphology.jpg", morph)
cv2.imwrite("black_lines_result.jpg", result)
# view result
cv2.imshow("threshold", thresh)
cv2.imshow("morphology", morph)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold image:
Morphology image:
Result:
First of all this is my original image which I try to detect the defects (parallel lines) on a brushed aluminium surface.
Here is the steps I take:
Gaussian Blur
Dilate the image
Converting the image to grayscale
Morph Close Operation
Dilate again
Difference of the image
Canny Edge Detection
Finding the contours
Drawing a green line around the contours
Here is my code:
import numpy as np
import cv2
from matplotlib import pyplot as plt
import imutils
path = ''
path_output = ''
img_bgr = cv2.imread(path)
plt.imshow(img_bgr)
# bgr to rgb
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
# Converting to grayscale
img_just_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
# Displaying the grayscale image
plt.imshow(img_just_gray, cmap='gray')
# Gaussian Blur
ksize_w = 13
ksize_h = 13
img_first_gb = cv2.GaussianBlur(img_rgb, (ksize_w,ksize_h), 0, 0, cv2.BORDER_REPLICATE);
plt.imshow(img_first_gb)
# Dilate the image
dilated_img = cv2.dilate(img_first_gb, np.ones((11,11), np.uint8))
plt.imshow(dilated_img)
# Converting to grayscale
img_gray_operated = cv2.cvtColor(dilated_img, cv2.COLOR_BGR2GRAY)
# Displaying the grayscale image
plt.imshow(img_gray_operated, cmap='gray')
# closing:
kernel_closing = np.ones((7,7),np.uint8)
img_closing = cv2.morphologyEx(img_gray_operated, cv2.MORPH_CLOSE, kernel_closing)
plt.imshow(img_closing, cmap='gray')
# dilation:
# add pixels to the boundaries of objects in an image
kernel_dilation = np.ones((3,3),np.uint8)
img_dilation2 = cv2.dilate(img_closing, kernel_dilation, iterations = 1)
plt.imshow(img_dilation2, cmap='gray')
diff_img = 255 - cv2.absdiff(img_just_gray, img_dilation2)
plt.imshow(diff_img, cmap='gray')
# canny
edgesToFindImage = img_dilation2
v = np.median(img_just_gray)
#print(v)
sigma = 0.33
lower_thresh = int(max(0,(1.0-sigma)*v))
higher_thresh = int(min(255,(1.0+sigma)*v))
img_edges = cv2.Canny(edgesToFindImage, lower_thresh, higher_thresh)
plt.imshow(img_edges, cmap='gray')
kernel_dilation2 = np.ones((2,2),np.uint8)
img_dilation2 = cv2.dilate(img_edges, kernel_dilation, iterations = 2)
plt.imshow(img_dilation2, cmap='gray')
# find contours
contoursToFindImage = img_dilation2
(_, cnts, _) = cv2.findContours(contoursToFindImage.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
print(type(cnts))
print(len(cnts))
# -1 for all
cntsWhichOne = -1
# -1 for infill
# >0 for edge thickness
cntsInfillOrEdgeThickness = 3
img_drawing_contours_on_rgb_image = cv2.drawContours(img_rgb.copy(), cnts, cntsWhichOne, (0, 255, 0), cntsInfillOrEdgeThickness)
plt.imshow(img_drawing_contours_on_rgb_image)
and this is the result.
How can I improve this detection? Is there a more effective method to detect lines?
Here is one way in Python OpenCV. You are close, you should use adaptive thresholding, morphology to clean up the small regions and skip the canny edges.
Input:
import cv2
import numpy as np
# load image
img = cv2.imread('scratches.jpg')
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# adaptive threshold
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, -35)
# apply morphology
kernel = np.ones((3,30),np.uint8)
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
kernel = np.ones((3,35),np.uint8)
morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel)
# get hough line segments
threshold = 25
minLineLength = 10
maxLineGap = 20
lines = cv2.HoughLinesP(morph, 1, 30*np.pi/360, threshold, minLineLength, maxLineGap)
# draw lines
linear1 = np.zeros_like(thresh)
linear2 = img.copy()
for [line] in lines:
x1 = line[0]
y1 = line[1]
x2 = line[2]
y2 = line[3]
cv2.line(linear1, (x1,y1), (x2,y2), 255, 1)
cv2.line(linear2, (x1,y1), (x2,y2), (0,0,255), 1)
print('number of lines:',len(lines))
# save resulting masked image
cv2.imwrite('scratches_thresh.jpg', thresh)
cv2.imwrite('scratches_morph.jpg', morph)
cv2.imwrite('scratches_lines1.jpg', linear1)
cv2.imwrite('scratches_lines2.jpg', linear2)
# display result
cv2.imshow("thresh", thresh)
cv2.imshow("morph", morph)
cv2.imshow("lines1", linear1)
cv2.imshow("lines2", linear2)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold image:
Morphology cleaned image:
Lines on original image:
Lines on black background:
How To Add Color Inside The Edges - Python (OpenCV)
I am Trying to remove the background From this image ,
Partatily i Succeed From the help of Internet,I created edge of image using canny,
But i want to add white background inside the object edge (inside the object edges) ,
For better Output
This Is the code for Remove Background ,That i Created
Inputed Image
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('img/aa.jpg')
original=img.copy()
l = int(max(5, 6))
u = int(min(6, 6))
edges = cv.GaussianBlur(img, (21, 51),3)
edges = cv.cvtColor(edges , cv.COLOR_BGR2GRAY)
mask = np.zeros(img.shape, dtype=np.uint8)
edges = cv.Canny(edges,l,u)
edges = cv.dilate(edges, None)
edges = cv.erode(edges, None)
_,thresh=cv.threshold(edges,0,255,cv.THRESH_BINARY + cv.THRESH_OTSU)
kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (5,5))
morphed = cv.morphologyEx(thresh, cv.MORPH_CLOSE, kernel)
dilate=cv.dilate(morphed, None, iterations = 1) #change iteration
mask=dilate
(cnts, _) =cv.findContours(dilate, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
for contour in cnts:
# (x, y, w, h) = cv.boundingRect(contour)
if cv.contourArea(contour) < 2000:
continue
cv.drawContours(mask ,contour, -1, (255, 255, 255), 3)
result = cv.bitwise_and(original, original, mask=mask)
result[dilate==0] = (0,0,0)
img1=cv.resize(mask,(600,400))
cv.imshow(' Image', img1)
img=cv.resize(result,(600,400))
cv.imshow('Original Image', img)
cv.waitKey()
OUTPUT
Any other thoughts would be greatly appreciated!
Here is how I would do that in Python/OpenCV.
Read the input
Convert to HSV color space
Apply color thresholding on the background color in hsv to create a mask
Invert the mask
Apply morphology to fill the holes and remove extraneous regions
Use mask to make background of images white
Input:
import cv2
import numpy as np
# load image and get dimensions
img = cv2.imread("man_face.jpeg")
# convert to hsv
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
# threshold using inRange
range1 = (50,0,50)
range2 = (120,120,170)
mask = cv2.inRange(hsv,range1,range2)
# invert mask
mask = 255 - mask
# apply morphology closing and opening to mask
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15,15))
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
result = img.copy()
result[mask==0] = (255,255,255)
# write result to disk
cv2.imwrite("man_face_mask.png", mask)
cv2.imwrite("man_face_white_background.jpg", result)
# display it
cv2.imshow("mask", mask)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Mask image:
Result:
I am using the code below to remove the backroung of the images and highlight only my region of interest (ROI), however, the algorithm behaves in a wrong way in some images, discarding the stain (ROI) and deleting along with the background.
import numpy as np
import cv2
#Read the image and perform threshold
img = cv2.imread('photo.bmp')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray,5)
_,thresh = cv2.threshold(blur,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
#Search for contours and select the biggest one
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
#Create a new mask for the result image
h, w = img.shape[:2]
mask = np.zeros((h, w), np.uint8)
#Draw the contour on the new mask and perform the bitwise operation
cv2.drawContours(mask, [cnt],-1, 255, -1)
res = cv2.bitwise_and(img, img, mask=mask)
#Display the result
cv2.imwrite('photo.png', res)
#cv2.imshow('img', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
I don't know if I understand correctly because when I run your code I do not get the output you posted (exit). If you would like to obtain only the mole it can't be done by simply thresholding because the mole is too near the border plus if you look at your image closley you will see that it has some sort of frame. However there is a simple way to do this for this image but it may not work in other cases. You can draw a fake border over your image and seperate the ROI from other noise area. Then make a threshold for which contour you wish to display. Cheers!
Example:
#Import all necessery libraries
import numpy as np
import cv2
#Read the image and perform threshold and get its height and weight
img = cv2.imread('moles.png')
h, w = img.shape[:2]
# Transform to gray colorspace and blur the image.
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(5,5),0)
# Make a fake rectangle arround the image that will seperate the main contour.
cv2.rectangle(blur, (0,0), (w,h), (255,255,255), 10)
# Perform Otsu threshold.
_,thresh = cv2.threshold(blur,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# Create a mask for bitwise operation
mask = np.zeros((h, w), np.uint8)
# Search for contours and iterate over contours. Make threshold for size to
# eliminate others.
_, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
for i in contours:
cnt = cv2.contourArea(i)
if 1000000 >cnt > 100000:
cv2.drawContours(mask, [i],-1, 255, -1)
# Perform the bitwise operation.
res = cv2.bitwise_and(img, img, mask=mask)
# Display the result.
cv2.imwrite('mole_res.jpg', res)
cv2.imshow('img', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result: