I am trying to extract the vertical lines from the fabric image using hough lines in opencv. I applied contrast enhancement to enhance the lines and bilateral filtering to try and remove the other fabric textures. However, on applying the houghlines, the code detects lines all over the image. I tried playing around with the parameters for hough but the results were the same.
Input image after applying histogram equalization and bilateral filter:
Here is the image after applying the hough line, red representing the detected lines.
Output showing hough detections:
What is another approach I can try so that the hough does not start detecting the minute fabric patterns as lines as well?
Here is the code I have:
`
img1= cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img2 = cv2.equalizeHist(img1)
img3 = cv2.equalizeHist(img2)
img4 = cv2.equalizeHist(img3)
img5 = cv2.bilateralFilter(img4, 9, 75,75)
cv2.imshow("threshold",img5)
edges = cv2.Canny(img4,50,127,apertureSize = 3)
lines= cv2.HoughLines(edges, 1, math.pi/180.0, 200, np.array([]), 0, 0)
a,b,c = lines.shape
for i in range(a):
rho = lines[i][0][0]
theta = lines[i][0][1]
a = math.cos(theta)
b = math.sin(theta)
x0, y0 = a*rho, b*rho
pt1 = ( int(x0+1000*(-b)), int(y0+1000*(a)) )
pt2 = ( int(x0-1000*(-b)), int(y0-1000*(a)) )
cv2.line(img, pt1, pt2, (0, 0, 255), 2, cv2.LINE_AA)
cv2.imshow('image1',img)
cv2.waitKey(0)
cv2.destroyAllWindows()`
You need to threshold your equalized image, apply morphology to clean it up before doing the canny edge detection and hough line extraction. Using Python/OpenCV to do the following processing.
Input:
import cv2
import numpy as np
import math
# read image
img = cv2.imread('fabric_equalized.png')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray,165,255,cv2.THRESH_BINARY)[1]
# apply close to connect the white areas
kernel = np.ones((15,1), np.uint8)
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = np.ones((17,3), np.uint8)
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# apply canny edge detection
edges = cv2.Canny(img, 175, 200)
# get hough lines
result = img.copy()
lines= cv2.HoughLines(edges, 1, math.pi/180.0, 165, np.array([]), 0, 0)
a,b,c = lines.shape
for i in range(a):
rho = lines[i][0][0]
theta = lines[i][0][1]
a = math.cos(theta)
b = math.sin(theta)
x0, y0 = a*rho, b*rho
pt1 = ( int(x0+1000*(-b)), int(y0+1000*(a)) )
pt2 = ( int(x0-1000*(-b)), int(y0-1000*(a)) )
cv2.line(result, pt1, pt2, (0, 0, 255), 2, cv2.LINE_AA)
# save resulting images
cv2.imwrite('fabric_equalized_thresh.jpg',thresh)
cv2.imwrite('fabric_equalized_morph.jpg',morph)
cv2.imwrite('fabric_equalized_edges.jpg',edges)
cv2.imwrite('fabric_equalized_lines.jpg',result)
# show thresh and result
cv2.imshow("thresh", thresh)
cv2.imshow("morph", morph)
cv2.imshow("edges", edges)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Thresholded image:
Morphology cleaned image:
Edge image:
Resulting Hough Lines:
Related
I want to calculate the area of cultivated fields of agricultural land. For that, I want to find all polygons in the image. The process I have followed is as follow
Convert image to gray.
Apply bilateralFilter filter for smoothing
Apply dilate function to connect lines
Apply erode function to fine the lines and edges
Apply canny for edge detection
Find contours
Find those contours which have area greater than some threshold
Draw contour.
The problem is that I am not able to find all the polygons. I am missing some polygons and the test image is simplest one. The complex test image can have more missing polygons. Can anyone help me in this regard.
Code is here
import cv2
import numpy as np
from scipy import misc
from scipy.ndimage import gaussian_filter
from scipy.signal import medfilt2d
import random
image = cv2.imread('img3.jpeg')
image = cv2.bilateralFilter(image, 15, 80, 80,None)
cv2.imshow('smoth', image)
cv2.waitKey(0)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow('gray', gray)
cv2.waitKey(0)
# Find Canny edges
# edged = cv2.Canny(gray, 30, 200)
thr1=50
thr2=200
kernel = np.ones((5,5 ),np.float32)/49
# gray = cv2.filter2D(gray,-1,kernel)
# kernel = np.ones((3,3), np.uint8)
gray = cv2.dilate(gray, kernel, iterations=3)
cv2.imshow('Edged dilate', gray)
cv2.waitKey(0)
gray = cv2.erode(gray, kernel, iterations=1)
cv2.imshow('Edged erode', gray)
cv2.waitKey(0)
edged = cv2.Canny(gray, thr1, thr2)
cv2.imshow("CannyImg_"+str(thr1) + "_" + str(thr2), edged)
cv2.waitKey(0)
kernel = np.ones((3,3), np.uint8)
contours, hierarchy = cv2.findContours(edged,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print("Number of Contours found = " + str(len(contours)))
cntFound = 0
finalCnt = []
for cnt in contours :
area = cv2.contourArea(cnt)
print("area",area)
# Shortlisting the regions based on there area.
if area > 100:
# approx = cv2.approxPolyDP(cnt,
# 0.009 * cv2.arcLength(cnt, True), True)
approx = cv2.approxPolyDP(cnt,0.001 * cv2.arcLength(cnt, True), True)
M = cv2.moments(cnt)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
# Checking if the no. of sides of the selected region is 7.
# if(len(approx) == 7):
r=random.randint(0,255)
g=random.randint(0,255)
b=random.randint(0,255)
cv2.drawContours(image, [approx], -1, (r, g, b), 3)
cv2.putText(image, str(cntFound), (cX - 20, cY - 20),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (r, g, b), 2)
cntFound = 1 + cntFound
finalCnt.append(cnt)
print("Total found after area threshold = ", cntFound)
cv2.imshow('Contours', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
The test image is
and the result is shown in the below picture
Edit 1:
After adding Otsu's thresholding the results are a little better on the test image. The results are as follow
But with a different image, the results become so bad. The results are shown below. The left side is the original image and the right side is the result
Any suggestion or opinion is welcome.
Apologies as I'm very new to OpenCV and the world of image processing in general.
I'm using OpenCV in Python to detect contours/boxes in this image.
It almost manages to detect all contours, but for some odd reason it doesn't pick up the last row and column which are obvious contours. This image shows the bounding boxes for contours it manages to identify.
Not entirely sure why it's not able to easily pick up the remaining contours. I've researched similar questions but haven't found a suitable answer.
Here's my code.
import numpy as np
import cv2
import math
import matplotlib.pyplot as plt
#load image
img = cv2.imread(path)
#remove noise
img = cv2.fastNlMeansDenoisingColored(img, None, 10, 10, 7, 21)
#convert to gray scale
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#make pixels darker
_, img = cv2.threshold(img, 240, 255, cv2.THRESH_TOZERO)
#thresholding the image to a binary image
thresh, img_bin = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
#inverting the image
img_bin = 255 - img_bin
# countcol(width) of kernel as 100th of total width
kernel_len = np.array(img).shape[1]//100
# Defining a vertical kernel to detect all vertical lines of image
ver_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, kernel_len))
# Defining a horizontal kernel to detect all horizontal lines of image
hor_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_len, 1))
# A kernel of 2x2
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
#Use vertical kernel to detect and save the vertical lines in a jpg
image_1 = cv2.erode(img_bin, ver_kernel, iterations = 3)
vertical_lines = cv2.dilate(image_1, np.ones((10, 4),np.uint8), iterations = 30)
vertical_lines = cv2.erode(vertical_lines, np.ones((10, 4),np.uint8), iterations = 29)
#Use horizontal kernel to detect and save the horizontal lines in a jpg
image_2 = cv2.erode(img_bin, np.ones((1, 5),np.uint8), iterations = 5)
horizontal_lines = cv2.dilate(image_2, np.ones((2, 40),np.uint8), iterations = 20)
horizontal_lines = cv2.erode(horizontal_lines, np.ones((2, 39),np.uint8), iterations = 19)
# Combine horizontal and vertical lines in a new third image, with both having same weight.
img_vh = cv2.addWeighted(vertical_lines, 0.5, horizontal_lines, 0.5, 0.0)
rows, cols = img_vh.shape
#shift image so the enhanced lines overlap with original image
M = np.float32([[1,0,-30],[0,1,-21]])
img_vh = cv2.warpAffine(img_vh ,M,(cols,rows))
#Eroding and thesholding the image
img_vh = cv2.erode(~img_vh, kernel, iterations = 2)
thresh, img_vh = cv2.threshold(img_vh, 128, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
bitxor = cv2.bitwise_xor(img, img_vh)
bitnot = cv2.bitwise_not(bitxor)
#find contours
contours, _ = cv2.findContours(img_vh, cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
#create list empty list to append with contours less than a specified area
new_contours = []
for contour in contours:
if cv2.contourArea(contour) < 4000000:
new_contours.append(contour)
#get bounding boxes
bounding_boxes = [cv2.boundingRect(contour) for contour in new_contours]
#plot detected bounding boxes
img_og = cv2.imread(path)
for bounding_box in bounding_boxes:
x,y,w,h = bounding_box
img_plot = cv2.rectangle(img_og, (x, y), (x+w, y+h), (255, 0, 0) , 2)
plotting = plt.imshow(img_plot, cmap='gray')
plt.show()
Like #ypnos was suggesting, the dilation and erosion has most likely pushed the last line off the image in the "saving horizontal lines" section. So the image_vh wouldn't have the last row when it was being searched for contours. I tested (Note:1) this by viewing the image after each of your transformations.
Specifically, the number of iterations had been too much. You had used a reasonably sized kernel as it is. It gave perfect results with iterations = 2 on lines 43 and 44 of your code.
After modifying them to :
horizontal_lines = cv2.dilate(image_2, np.ones((2, 40), np.uint8), iterations=2)
horizontal_lines = cv2.erode(horizontal_lines, np.ones((2, 39), np.uint8), iterations=2)
the bounding box rectangles had shifted off the image a bit. That was fixed by changing line 51 of the code to:
M = np.float32([[1, 0, -30], [0, 1, -5]])
This was the result.
Note:
I test/debug using this function usually.
def test(image, title):
cv2.imshow(title, image)
cv2.waitKey(0)
cv2.destroyWindow(title)
The variable position and the handy waitkey calms me down.
This is the image and I want to fill the edges of this rectangle or square so that I could crop it using contours. What I have done so far is that i used canny edge detector to find edges and then using bitwise_or I get this rectangle filled a little but not completely. What to do to fill this rectangle or is there any way to directly crop this?
image = cv2.imread('C:/Users/hp/Desktop/segmentation/test3.jpeg')
img3 = img2 = image.copy()
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
img3 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
lower = np.array([155,25,0])
upper = np.array([179,255,255])
mask = cv2.inRange(image, lower, upper)
edges = cv2.Canny(mask, 1, 255, apertureSize=7)
result = cv2.bitwise_or(edges, mask)
Here is one way to extract the bounds of the rectangle white pixels in Python/OpenCV.
Read the input
Convert to gray
Threshold
Do Canny edge detection
Get Hough line segments and draw as white on black background
Get the bounds of the white pixels
Crop the input to the bounds
Input:
import cv2
import numpy as np
# load image as grayscale
img = cv2.imread('rect_lines.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)[1]
# apply canny edge detection
edges = cv2.Canny(thresh, 100, 200)
# get hough line segments
threshold = 100
minLineLength = 50
maxLineGap = 20
lines = cv2.HoughLinesP(thresh, 1, np.pi/360, threshold, minLineLength, maxLineGap)
# draw lines
linear = np.zeros_like(thresh)
for [line] in lines:
#print(line)
x1 = line[0]
y1 = line[1]
x2 = line[2]
y2 = line[3]
cv2.line(linear, (x1,y1), (x2,y2), (255), 1)
# get bounds of white pixels
white = np.where(linear==255)
xmin, ymin, xmax, ymax = np.min(white[1]), np.min(white[0]), np.max(white[1]), np.max(white[0])
#print(xmin,xmax,ymin,ymax)
# draw bounding box on input
bounds = img.copy()
cv2.rectangle(bounds, (xmin,ymin), (xmax,ymax), (0,0,255))
# crop the image at the bounds
crop = img[ymin:ymax, xmin:xmax]
# save resulting masked image
cv2.imwrite('rect_lines_edges.png', edges)
cv2.imwrite('rect_lines_hough.png', linear)
cv2.imwrite('rect_lines_bounds.png', bounds)
cv2.imwrite('rect_lines_crop.png', crop)
# display result, though it won't show transparency
cv2.imshow("thresh", thresh)
cv2.imshow("edges", edges)
cv2.imshow("lines", linear)
cv2.imshow("bounds", bounds)
cv2.imshow("crop", crop)
cv2.waitKey(0)
cv2.destroyAllWindows()
Canny edges:
Hough line segments:
Bounding box on input:
Cropped image:
I'm trying to use cv2.HoughLines to identify the skew angle of the words in this image.
However, after edge detection, it clearly has too much noise.
I've tried using cv2.medianBlur to remove the noise.
However, there is even more noise.
This means that I'm unable to set the minimum line length threshold for hough transform.
What other functions should I be looking at?
Image:
After edge detection:
Edit: After Rotem's help, my code now identifies images with skew angles between 90 to -90 degrees including 90 but excluding -90.
import numpy as np
import imutils
import math
import pytesseract
img = cv2.imread('omezole.jpg')
resized = imutils.resize(img, width=300)
gray = cv2.cvtColor(resized,cv2.COLOR_BGR2GRAY)
th3 = cv2.threshold(gray, 80, 255, cv2.THRESH_BINARY_INV)[1]
minLineLength = 50
maxLineGap = 3
lines = cv2.HoughLinesP(th3, rho=1, theta=np.pi/180, threshold=100, minLineLength=minLineLength, maxLineGap=maxLineGap)
colLineCopy = cv2.cvtColor(th3,cv2.COLOR_GRAY2BGR)
#Draw but remove all vertical lines, add corresponding angle to ls
ls = []
for line in lines:
if line is None:
angle = 0
else:
x1, y1, x2, y2 = line[0].tolist()
print(line)
#check for vertical lines since you can't find tan90
if (x2-x1==0):
ls.append(-90)
else:
ls.append((math.degrees(math.atan((y2-y1)/(x2-x1)))))
cv2.line(colLineCopy, (x1,y1), (x2,y2), (0,0,250), 2)
#special case of strictly vertical words, if more than 0.2 of the lines are vertical assume, words are vertical
if ls.count(-90)>len(ls)//5:
angle = 90
else:
for angle in ls:
if angle < -80:
ls.remove(angle)
angle = sum(ls)/len(ls)
rotated = imutils.rotate_bound(resized, -angle)
cv2.imshow("HoughLinesP", colLineCopy)
cv2.imshow("rotated", rotated)
gray = cv2.cvtColor(rotated, cv2.COLOR_BGR2GRAY)
threshINV = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY_INV)[1]
cv2.imshow("final", threshINV)
#Run OCR
pytesseract.tesseract_cmd = r'C:\\Program Files\\Tesseract-OCR\\tesseract.exe'
custom_config = r'--psm 11'
print(pytesseract.image_to_string(threshINV, config = custom_config))
cv2.waitKey(0)
cv2.destroyAllWindows
``
A useful way for removing the "noise", before using edge detection is applying a threshold that converts the image from Grayscale to binary image.
Finding the correct threshold (automatically) is not always an easy task.
I manually set the threshold value to 50.
Solution using HoughLinesP code sample:
import numpy as np
import cv2
# Read input image
img = cv2.imread('omezole.jpg')
# Convert from RGB to Grayscale.
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Apply threshold - all values below 50 goes to 0, and values above 50 goes to 1.
ret, thresh_gray = cv2.threshold(gray, 50, 255, cv2.THRESH_BINARY)
# https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_houghlines/py_houghlines.html
edges = cv2.Canny(thresh_gray, 50, 150, apertureSize = 3)
minLineLength = 100
maxLineGap = 5
lines = cv2.HoughLinesP(edges, rho=1, theta=np.pi/180, threshold=100, minLineLength=minLineLength, maxLineGap=maxLineGap)
# Draw lines
for line in lines:
x1, y1, x2, y2 = line[0].tolist()
cv2.line(img, (x1,y1), (x2,y2), (0,255,0), 2)
cv2.imwrite('houghlines.png',img)
Result:
The HoughLines solution is not so robust.
I suggest another solution using findContours:
img = cv2.imread('omezole.jpg')
# Inverse polarity:
thresh_gray = 255 - thresh_gray;
# Use "open" morphological operation to remove some rough edges
thresh_gray = cv2.morphologyEx(thresh_gray, cv2.MORPH_OPEN, np.ones((5, 5)))
# Find contours over thresh_gray
cnts = cv2.findContours(thresh_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
# Iterate contours
for c in cnts:
# Only if contour area is large enough:
if cv2.contourArea(c) > 2000:
rect = cv2.minAreaRect(c)
box = cv2.boxPoints(rect)
# convert all coordinates floating point values to int
box = np.int0(box)
cv2.drawContours(img, [box], 0, (0, 255, 0), thickness=2)
angle = rect[2]
print('angle = ' + str(angle))
cv2.imwrite('findcontours.png', img)
# Show result (for testing).
cv2.imshow('thresh_gray', thresh_gray)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
angle = -21.801406860351562
angle = -21.44773292541504
angle = -21.370620727539062
angle = -21.801406860351562
angle = -22.520565032958984
angle = -22.56700897216797
angle = -23.198591232299805
I am working on a torn document reconstruction project. First I tried to detect the edges of the image which contain torn document pieces and then I tried to crop the image into the pieces through the detected edges using the sample code,
import cv2
import numpy as np
img = cv2.imread("test.png")
img = cv2.imread("d:/test.jpeg")
cv2.imshow('Original Image',img)
new_img = cv2.Canny(img, 0, 505)
cv2.imshow('new image', new_img)
blurred = cv2.blur(new_img, (3,3))
canny = cv2.Canny(blurred, 50, 200)
## find the non-zero min-max coords of canny
pts = np.argwhere(canny>0)
y1,x1 = pts.min(axis=0)
y2,x2 = pts.max(axis=0)
## crop the region
cropped = new_img[y1:y2, x1:x2]
cv2.imwrite("cropped.png", cropped)
tagged = cv2.rectangle(new_img.copy(), (x1,y1), (x2,y2), (0,255,0), 3, cv2.LINE_AA)
cv2.imshow("tagged", tagged)
cv2.waitKey()
my input image was
after running the above code i gets a output like
can someone help me to crop the torn document pieces and assign them into variables
The beginning of my workflow is similar to yours. First step: blur the image..
blurred = cv2.GaussianBlur(gray, (5, 5), 0) # Blur
Second step: get the canny image...
canny = cv2.Canny(blurred, 30, 150) # Canny
Third step: draw the contours on the canny image. This closes the torn pieces.
# Find contours
_, contours, _ = cv2.findContours(canny,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# Draw contours on canny (this connects the contours
cv2.drawContours(canny, contours, -1, 255, 2)
canny = 255 - canny
Fourth step: floodfill (the floodfilled areas are gray)
# Get mask for floodfill
h, w = thresh.shape[:2]
mask = np.zeros((h+2, w+2), np.uint8)
# Floodfill from point (0, 0)
cv2.floodFill(thresh, mask, (0,0), 123);
Fifth step: get rid of the really small and really large contours
# Create a blank image to draw on
res = np.zeros_like(src_img)
# Create a list for unconnected contours
unconnectedContours = []
for contour in contours:
area = cv2.contourArea(contour)
# If the contour is not really small, or really big
if area > 123 and area < 760000:
cv2.drawContours(res, [contour], 0, (255,255,255), cv2.FILLED)
unconnectedContours.append(contour)
Finally, once you have segmented the pieces, they can be nested.