我正在尝试用Python编写基于OpenCV库的计算机视觉代码来检测强度接近背景的水平线。请参阅下图示例。
我尝试了两种方法。第一个基于 Canny 边缘检测和 Hough 变换,但它只检测到几行(参见下面的代码和图像)。
import math
import numpy as np
import cv2
scaleFactor = 1
maskX1 = 57
maskX2 = 263
maskY1 = 30
maskY2 = 164
angleStart = -1
angleEnd = 1
verticalKernel = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]])
sharpenKernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
def applyKernel(image, kernel):
return cv2.filter2D(image, -1, kernel)
# read image
image_c = cv2.imread('images/1.png')
image_c = cv2.resize(image_c, None, fx=scaleFactor, fy=scaleFactor, interpolation=cv2.INTER_CUBIC)
cv2.imshow('Original Image', image_c)
# convert to grayscale
image_g = cv2.cvtColor(image_c, cv2.COLOR_RGB2GRAY)
# image_g = cv2.bilateralFilter(image_g, 15, 15, 15)
image_g = applyKernel(image_g, sharpenKernel)
cv2.imshow('Sharpen Image', image_g)
image_g = applyKernel(image_g, verticalKernel)
cv2.imshow('Vertical Sobel Operator', image_g)
# Gaussian blur and Canny
threshold_low = 250
threshold_high = 300
image_canny = cv2.Canny(image_g, threshold_low, threshold_high)
cv2.imshow('Canny Image', image_canny)
# Visualize region of interest
mask = np.zeros_like(image_g)
vertices = np.array([[(maskX1 * scaleFactor, maskY1 * scaleFactor), (maskX2 * scaleFactor, maskY1 * scaleFactor), (maskX2 * scaleFactor, maskY2 * scaleFactor), (maskX1 * scaleFactor, maskY2 * scaleFactor)]], dtype=np.int32)
cv2.fillPoly(mask, vertices, 255)
masked_image = cv2.bitwise_and(image_canny, mask)
# masked_image = image_canny
cv2.imshow('Region of interest', masked_image)
rho = 1 * scaleFactor # distance resolution in pixels
theta = np.pi / 180 # angular resolution in radians
threshold = 3 # minimum number of votes
min_line_len = 10 * scaleFactor # minimum number of pixels making up a line
max_line_gap = 20 * scaleFactor # maximum gap in pixels between connectable line segments
lines = cv2.HoughLinesP(masked_image, rho, theta, threshold, np.array([]), minLineLength=min_line_len,
maxLineGap=max_line_gap)
line_image = np.zeros((masked_image.shape[0], masked_image.shape[1], 3), dtype=np.uint8)
numLines = 0
totalLineLength = 0
for line in lines:
for x1, y1, x2, y2 in line:
if x2 == x1:
lineAngle = 90
else:
lineAngle = math.degrees(math.atan((y2 - y1) / (x2 - x1)))
if angleStart < lineAngle < angleEnd:
cv2.line(line_image, (x1, y1), (x2, y2), [0, 0, 255], 2)
numLines = numLines + 1
totalLineLength = totalLineLength + math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
α = 1
β = 0.3
γ = 0
# Resultant weighted image is calculated as follows: original_img * α + img * β + γ
image_with_lines = cv2.addWeighted(image_c, α, line_image, β, γ)
cv2.imshow('Image with lines', image_with_lines)
cv2.waitKey()
cv2.destroyAllWindows()
第二种方法基于图像阈值和轮廓分析,但结果也令人失望(参见下面的代码和图像)。
import math
import numpy as np
import cv2
scaleFactor = 1
maskX1 = 57
maskX2 = 263
maskY1 = 30
maskY2 = 164
angleStart = -5
angleEnd = 5
verticalKernel = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]])
sharpenKernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
basePath = 'images/'
fileExtension = '.png'
def applyKernel(image, kernel):
return cv2.filter2D(image, -1, kernel)
def getHoughLines(image, masked_image):
rho = 1 * scaleFactor # distance resolution in pixels
theta = np.pi / 180 # angular resolution in radians
threshold = 3 # minimum number of votes
min_line_len = 10 * scaleFactor # minimum number of pixels making up a line
max_line_gap = 5 * scaleFactor # maximum gap in pixels between connectable line segments
lines = cv2.HoughLinesP(masked_image, rho, theta, threshold, np.array([]), minLineLength=min_line_len,
maxLineGap=max_line_gap)
line_image = np.zeros((masked_image.shape[0], masked_image.shape[1]), dtype=np.uint8)
numLines = 0
totalLineLength = 0
for line in lines:
for x1, y1, x2, y2 in line:
if x2 == x1:
lineAngle = 90
else:
lineAngle = math.degrees(math.atan((y2 - y1) / (x2 - x1)))
if angleStart < lineAngle < angleEnd:
cv2.line(line_image, (x1, y1), (x2, y2), 255, 2)
numLines = numLines + 1
totalLineLength = totalLineLength + math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
α = 1
β = 0.3
γ = 0
# Resultant weighted image is calculated as follows: original_img * α + img * β + γ
image_with_lines = cv2.addWeighted(image, α, line_image, β, γ)
cv2.imshow('Image with lines', image_with_lines)
return image_with_lines
# read image
image_g = cv2.imread('images/1.png', cv2.IMREAD_GRAYSCALE)
# image_g = cv2.resize(image_g, None, fx=scaleFactor, fy=scaleFactor, interpolation=cv2.INTER_CUBIC)
cv2.imshow('Original Image', image_g)
# Apply Gaussian blur to reduce noise
# image_blurred = cv2.GaussianBlur(image_g, (5, 5), 0)
image_blurred = image_g
image_blurred = applyKernel(image_blurred, sharpenKernel)
cv2.imshow('Sharpen Image', image_blurred)
image_blurred = applyKernel(image_blurred, verticalKernel)
cv2.imshow('Vertical Sobel Operator', image_blurred)
# Apply adaptive thresholding to binarize the image
# _, binary_image = cv2.threshold(image_blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
_, binary_image = cv2.threshold(image_blurred, 70, 255, cv2.THRESH_BINARY)
cv2.imshow('Binary image', binary_image)
# Visualize region of interest
mask = np.zeros_like(image_g)
vertices = np.array([[(maskX1 * scaleFactor, maskY1 * scaleFactor), (maskX2 * scaleFactor, maskY1 * scaleFactor), (maskX2 * scaleFactor, maskY2 * scaleFactor), (maskX1 * scaleFactor, maskY2 * scaleFactor)]], dtype=np.int32)
cv2.fillPoly(mask, vertices, 255)
masked_image = cv2.bitwise_and(binary_image, mask)
cv2.imshow('Masked image', masked_image)
# morphological operations
# kernel = np.ones((2,2),np.uint8)
# masked_image = cv2.morphologyEx(masked_image, cv2.MORPH_OPEN, kernel)
cv2.imshow('morphologyEx', masked_image)
# Perform edge detection
edges = cv2.Canny(masked_image, 30, 200)
cv2.imshow('Edges', edges)
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print(contours)
for contour in contours:
# Calculate the length of the contour
length = cv2.arcLength(contour, True)
# Calculate the area of the contour
area = cv2.contourArea(contour)
# Filter out small contours (adjust the area threshold as needed)
if area > 1:
x, y, width, height = cv2.boundingRect(contour)
if width > 5:
# Draw the contour on the original image
cv2.drawContours(image_g, [contour], -1, (0, 255, 0), 2)
# Print or store the length and area
print(f"Length: {length}, Area: {area}")
cv2.imshow('Processed Image', image_g)
cv2.waitKey(0)
cv2.destroyAllWindows()
有没有办法更准确地检测这些线条?
我基于图像阈值和轮廓检测提出了以下解决方案。 2 个附加滤波器的组合提供了更多可见的线条,使用阈值处理和轮廓检测更容易检测到。
def getStreakyStructuresForOneImage(imagePath, showProcessingImages, filterVertical = False):
scaleFactor = 1
maskX1 = 128 # 57
maskX2 = 355 # 263
maskY1 = 50
maskY2 = 205
angleStart = -5
angleEnd = 5
verticalKernel = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]])
sharpenKernel2 = 0.64 * np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
sharpenKernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
if filterVertical:
verticalKernel = np.transpose(verticalKernel)
sharpenKernel2 = np.transpose(sharpenKernel2)
sharpenKernel = np.transpose(sharpenKernel)
# read image
image_g = cv2.imread(imagePath, cv2.IMREAD_GRAYSCALE)
if showProcessingImages:
cv2.imshow('Original Image', image_g)
image_blurred = applyKernel(image_g, sharpenKernel)
if showProcessingImages:
cv2.imshow('Sharpen Image', image_blurred)
image_blurred = applyKernel(image_blurred, verticalKernel)
image_blurred = applyKernel(image_blurred, sharpenKernel2)
if showProcessingImages:
cv2.imshow('Sharpening using different kernels', image_blurred)
_, binary_image = cv2.threshold(image_blurred, 70, 255, cv2.THRESH_BINARY)
if showProcessingImages:
cv2.imshow('Binary image', binary_image)
# Visualize region of interest
mask = np.zeros_like(image_g)
vertices = np.array([[(maskX1 * scaleFactor, maskY1 * scaleFactor), (maskX2 * scaleFactor, maskY1 * scaleFactor),
(maskX2 * scaleFactor, maskY2 * scaleFactor), (maskX1 * scaleFactor, maskY2 * scaleFactor)]],
dtype=np.int32)
cv2.fillPoly(mask, vertices, 255)
masked_image = cv2.bitwise_and(binary_image, mask)
if showProcessingImages:
cv2.imshow('Masked image', masked_image)
contours, _ = cv2.findContours(masked_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
image_g = cv2.cvtColor(image_g, cv2.COLOR_GRAY2RGB)
masked_image = cv2.cvtColor(masked_image, cv2.COLOR_GRAY2RGB)
line_image = np.zeros((masked_image.shape[0], masked_image.shape[1], 3), dtype=np.uint8)
numberOfMeaningfulContours = 0
totalLineLength = 0
for contour in contours:
# Calculate the length of the contour
length = cv2.arcLength(contour, True)
# Calculate the area of the contour
area = cv2.contourArea(contour)
# Filter out small contours (adjust the area threshold as needed)
if area > 0:
x, y, width, height = cv2.boundingRect(contour)
if filterVertical:
if height > 50:
# Draw the contour on the original image
cv2.drawContours(line_image, [contour], -1, (0, 0, 255), 2)
numberOfMeaningfulContours += 1;
totalLineLength += width * mmInPx;
# Print or store the length and area
print(f"Length: {length}, Area: {area}")
else:
if width > 15:
# Draw the contour on the original image
cv2.drawContours(line_image, [contour], -1, (0, 0, 255), 2)
numberOfMeaningfulContours += 1;
totalLineLength += width * mmInPx;
# Print or store the length and area
print(f"Length: {length}, Area: {area}")
α = 1
β = 0.14
γ = 0
# Resultant weighted image is calculated as follows: original_img * α + img * β + γ
image_with_lines = cv2.addWeighted(image_g, α, line_image, β, γ)
if showProcessingImages:
cv2.imshow('Processed Image', image_with_lines)
averageLineLength = 0
if numberOfMeaningfulContours > 0:
averageLineLength = totalLineLength / numberOfMeaningfulContours
return image_with_lines, numberOfMeaningfulContours, totalLineLength, averageLineLength