Reputation: 343
I am trying to detect the long middle separator vertical line in my image so that I can crop the image based on that line into two separate images, trying to use HoughLinesP but how do I make sure it only picks the middle vertical line, I can clearly see that that line is pretty distinguishable.
I applied opencv HoughLinesP to it and got the following result
Using the following code:
img = cv2.imread('./image.jpeg', 1)
img_gray = gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cannied = cv2.Canny(img_gray, threshold1=50, threshold2=200, apertureSize=3)
lines = cv2.HoughLinesP(cannied, rho=1, theta=np.pi / 180, threshold=80, minLineLength=30, maxLineGap=10)
for line in get_lines(lines):
leftx, boty, rightx, topy = line
cv2.line(img, (leftx, boty), (rightx,topy), (255, 255, 0), 2)
cv2.imwrite('./lines.png', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
More resources I applied
and several others
Upvotes: 2
Views: 1125
Reputation: 5815
Here's a possible solution. It involves selecting a sample from a couple of rows on the input image, then reducing the sample to a row image using the cv2.reduce
function. Loop through the reduced row and look for the pixel intensity jumps from 0
to 255
, since that transitions denotes the areas of the page you are using to guide your crop. Finally, store the locations of the jumps in a list to further crop the pages from the image. These are the steps:
1 x width
, using the cv2.reduce
function in MAX
mode to get the maximum intensity values of each column0
to 255
- this point is where a vertical line is locatedlist
Let's see the code:
# Imports:
import numpy as np
import cv2
# Set the image path
path = "D://opencvImages//"
fileName = "mx8lW.jpg"
# Read the image in default mode:
inputImage = cv2.imread(path + fileName)
# Convert RGB to grayscale:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
# Set the sample ROI and crop it:
(imageHeight, imageWidth) = grayscaleImage.shape[:2]
roiX = 0
roiY = int(0.05 * imageHeight)
roiWidth = imageWidth
roiHeight = int(0.05 * imageHeight)
# Crop the image:
imageRoi = grayscaleImage[roiY:roiY+roiHeight, roiX:roiWidth]
The first bit reads the image, converts it from BGR
to grayscale
and defines the sample ROI. This ROI is used to locate the vertical lines. Since the vertical lines are constant through the image, selecting and cropping just a sample will do just fine instead of processing the whole image. The ROI I defined selects an area just below the image top, because there are some dark portions that could affect the reduction of the ROI to a row.
This image shows the selected ROI, drawn with a green rectangle:
Note that the two (leftmost and rightmost) vertical lines are shown clearly inside the ROI. If we crop that, we will get this sample image:
All the info we need to crop the pages is right there. Let's threshold
the image to get a binary mask:
# Thresholding:
_, binaryImage = cv2.threshold(imageRoi, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
This is the binary mask:
Note the jumps from 0
to 255
and what they imply. Each transition denotes the starting and ending points that we can use to crop the two pages. Ok, let's reduce
the binary mask to just one row. We will use the MAX
mode, where each pixel value in the row is defined as the maximum intensity value corresponding to that image column:
# Reduce the ROI to a 1 x imageWidth row:
reducedImg = cv2.reduce(binaryImage, 0, cv2.REDUCE_MAX)
This is the image produced, which is just a row:
Let's loop through this image (actually just a plain numpy
array) and look for those transitions. Additionally, let's prepare a list and store the jump the locations as we find them:
# Store the transition positions here:
linePositions = []
# Find transitions from 0 to 255:
pastPixel = 255
for x in range(reducedImg.shape[1]):
# Get current pixel:
currentPixel = reducedImg[0,x]
# Check for the "jumps":
if currentPixel == 255 and pastPixel == 0:
# Store the jump locations in list:
print("Got Jump at:"+str(x))
linePositions.append(x)
# Set current pixel to past pixel:
pastPixel = currentPixel
Cool, we have the jump locations ready in linePositions
. Let's finally crop the image. I've achieved this looping through the locations list and setting the parameters for cropping:
# Crop pages:
for i in range(len(linePositions)):
# Get top left:
cropX = linePositions[i]
# Get top left:
if i != len(linePositions)-1:
# Get point from the list:
cropWidth = linePositions[i+1]
else:
# Set point from the image's original width:
cropWidth = reducedImg.shape[1]
# Crop page:
cropY = 0
cropHeight = imageHeight
currentCrop = inputImage[cropY:cropHeight,cropX:cropWidth]
# Show current crop:
cv2.imshow("CurrentCrop", currentCrop)
cv2.waitKey(0)
Which produces two crops:
Upvotes: 1
Reputation: 395
Based on this input image, you want to find the longest vertical line that sits at the middle of the image. Therefore you have several options to try:
minLineLength
in your cv2.HoughLinesP
, to eliminate shorter lines.Upvotes: 0