使用 Canny 在倾斜图像中寻找边缘
finding edge in tilted image with Canny
我试图在一系列图像中找到倾斜角度,这些图像看起来像下面创建的示例数据。应该有肉眼可见的清晰边缘。但是,到目前为止,我一直在努力提取边缘。 Canny 是找到边缘的正确方法还是有更好的方法来找到边缘?
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage.filters import gaussian_filter
# create data
xvals = np.arange(0,2000)
yvals = 10000 * np.exp((xvals - 1600)/200) + 100
yvals[1600:] = 100
blurred = gaussian_filter(yvals, sigma=20)
# create image
img = np.tile(blurred,(2000,1))
img = np.swapaxes(img,0,1)
# rotate image
rows,cols = img.shape
M = cv.getRotationMatrix2D((cols/2,rows/2),3.7,1)
img = cv.warpAffine(img,M,(cols,rows))
# convert to uint8 for Canny
img_8 = cv.convertScaleAbs(img,alpha=(255.0/65535.0))
fig,ax = plt.subplots(3)
ax[0].plot(xvals,blurred)
ax[1].imshow(img)
# find edge
ax[2].imshow(cv.Canny(img_8, 20, 100, apertureSize=5))
部落,
好像可以拍渐变图G = |Gx| + |戈瑞| (将其归一化到某个已知范围),计算其直方图并获取其顶部的垃圾箱。它会给你大约线的掩码。然后你可以做线拟合。它会给你一个很好的初始猜测。
一个非常简单的方法如下...调整我的数字以适合您对数据的了解。
将您的图像标准化为 0-255 的比例。
选择 A 和 B 两点,其中 A 距左侧图像宽度的 10%,B 距右侧图像宽度的 10%。距离 AB 现在是 0.8 x 2000,或 1600 像素。
从 A 点向北对图像进行采样,直到超过某个合理的阈值,这意味着您已经遇到倾斜线。注意此时的Y值,为YA。
做同样的事情,从 B 点向北,直到遇到斜线。注意此时的Y值,为YB.
您寻求的角度是:
tan-1((YB-YA)/1600)
您可以通过将图像转换为二进制 (cv2.threshold(cv2.THRESH_BINARY)
) 然后搜索轮廓来找到角度。
当您找到轮廓(线)时,您可以在轮廓上拟合一条线 cv2.fitLine()
并获得线的两个点。我的数学不是很好,但我认为在线性方程式中,公式为 f(x) = k*x + n
,你可以从这两个点 (k = (y2-y1)/(x2-x1)
) 中得到 k
,最后得到角度 phi = arctan(k)
. (如有错误请指正)
您还可以使用旋转的边界矩形 - cv2.minAreaRect()
- 它已经 returns 矩形的角度 (rect = cv2.minAreaRect()
--> rect[2]
)。希望能帮助到你。干杯!
这是一个示例代码:
import cv2
import numpy as np
import math
img = cv2.imread('angle.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, threshold = cv2.threshold(gray,170,255,cv2.THRESH_BINARY)
im, contours, hierarchy = cv2.findContours(threshold,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
for c in contours:
area = cv2.contourArea(c)
perimeter = cv2.arcLength(c, False)
if area < 10001 and 100 < perimeter < 1000:
# first approach - fitting line and calculate with y=kx+n --> angle=tan^(-1)k
rows,cols = img.shape[:2]
[vx,vy,x,y] = cv2.fitLine(c, cv2.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
(x1, y1) = (cols-1, righty)
(x2, y2) = (0, lefty)
k = (y2-y1)/(x2-x1)
angle = math.atan(k)*180/math.pi
print(angle)
#second approch - cv2.minAreaRect --> returns center (x,y), (width, height), angle of rotation )
rect = cv2.minAreaRect(c)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(img,[box],0,(0,0,255),2)
print(rect[2])
cv2.imshow('img2', img)
原图:
输出:
-3.8493663478518627
-3.7022125720977783
kavko 建议的阈值处理效果不佳,因为强度因图像而异(我当然可以考虑每个图像的直方图来改进这种方法)。我最终在 y 方向上取了最大的梯度:
def rotate_image(image):
blur = ndimage.gaussian_filter(image, sigma=10) # blur image first
grad = np.gradient(blur, axis= 0) # take gradient along y-axis
grad[grad>10000]=0 # filter unreasonable high values
idx_maxline = np.argmax(grad, axis=0) # get y-indices of max slope = indices of edge
mean = np.mean(idx_maxline)
std = np.std(idx_maxline)
idx = np.arange(idx_maxline.shape[0])
idx_filtered = idx[(idx_maxline < mean+std) & (idx_maxline > mean - std)] # filter positions where highest slope is at different position(blobs)
slope, intercept, r_value, p_value, std_err = stats.linregress(idx_filtered, idx_maxline[idx_filtered])
out = ndimage.rotate(image,slope*180/np.pi, reshape = False)
return out
out = rotate_image(img)
plt.imshow(out)
我试图在一系列图像中找到倾斜角度,这些图像看起来像下面创建的示例数据。应该有肉眼可见的清晰边缘。但是,到目前为止,我一直在努力提取边缘。 Canny 是找到边缘的正确方法还是有更好的方法来找到边缘?
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage.filters import gaussian_filter
# create data
xvals = np.arange(0,2000)
yvals = 10000 * np.exp((xvals - 1600)/200) + 100
yvals[1600:] = 100
blurred = gaussian_filter(yvals, sigma=20)
# create image
img = np.tile(blurred,(2000,1))
img = np.swapaxes(img,0,1)
# rotate image
rows,cols = img.shape
M = cv.getRotationMatrix2D((cols/2,rows/2),3.7,1)
img = cv.warpAffine(img,M,(cols,rows))
# convert to uint8 for Canny
img_8 = cv.convertScaleAbs(img,alpha=(255.0/65535.0))
fig,ax = plt.subplots(3)
ax[0].plot(xvals,blurred)
ax[1].imshow(img)
# find edge
ax[2].imshow(cv.Canny(img_8, 20, 100, apertureSize=5))
部落,
好像可以拍渐变图G = |Gx| + |戈瑞| (将其归一化到某个已知范围),计算其直方图并获取其顶部的垃圾箱。它会给你大约线的掩码。然后你可以做线拟合。它会给你一个很好的初始猜测。
一个非常简单的方法如下...调整我的数字以适合您对数据的了解。
将您的图像标准化为 0-255 的比例。
选择 A 和 B 两点,其中 A 距左侧图像宽度的 10%,B 距右侧图像宽度的 10%。距离 AB 现在是 0.8 x 2000,或 1600 像素。
从 A 点向北对图像进行采样,直到超过某个合理的阈值,这意味着您已经遇到倾斜线。注意此时的Y值,为YA。
做同样的事情,从 B 点向北,直到遇到斜线。注意此时的Y值,为YB.
您寻求的角度是:
tan-1((YB-YA)/1600)
您可以通过将图像转换为二进制 (cv2.threshold(cv2.THRESH_BINARY)
) 然后搜索轮廓来找到角度。
当您找到轮廓(线)时,您可以在轮廓上拟合一条线 cv2.fitLine()
并获得线的两个点。我的数学不是很好,但我认为在线性方程式中,公式为 f(x) = k*x + n
,你可以从这两个点 (k = (y2-y1)/(x2-x1)
) 中得到 k
,最后得到角度 phi = arctan(k)
. (如有错误请指正)
您还可以使用旋转的边界矩形 - cv2.minAreaRect()
- 它已经 returns 矩形的角度 (rect = cv2.minAreaRect()
--> rect[2]
)。希望能帮助到你。干杯!
这是一个示例代码:
import cv2
import numpy as np
import math
img = cv2.imread('angle.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, threshold = cv2.threshold(gray,170,255,cv2.THRESH_BINARY)
im, contours, hierarchy = cv2.findContours(threshold,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
for c in contours:
area = cv2.contourArea(c)
perimeter = cv2.arcLength(c, False)
if area < 10001 and 100 < perimeter < 1000:
# first approach - fitting line and calculate with y=kx+n --> angle=tan^(-1)k
rows,cols = img.shape[:2]
[vx,vy,x,y] = cv2.fitLine(c, cv2.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
(x1, y1) = (cols-1, righty)
(x2, y2) = (0, lefty)
k = (y2-y1)/(x2-x1)
angle = math.atan(k)*180/math.pi
print(angle)
#second approch - cv2.minAreaRect --> returns center (x,y), (width, height), angle of rotation )
rect = cv2.minAreaRect(c)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(img,[box],0,(0,0,255),2)
print(rect[2])
cv2.imshow('img2', img)
原图:
输出:
-3.8493663478518627
-3.7022125720977783
kavko 建议的阈值处理效果不佳,因为强度因图像而异(我当然可以考虑每个图像的直方图来改进这种方法)。我最终在 y 方向上取了最大的梯度:
def rotate_image(image):
blur = ndimage.gaussian_filter(image, sigma=10) # blur image first
grad = np.gradient(blur, axis= 0) # take gradient along y-axis
grad[grad>10000]=0 # filter unreasonable high values
idx_maxline = np.argmax(grad, axis=0) # get y-indices of max slope = indices of edge
mean = np.mean(idx_maxline)
std = np.std(idx_maxline)
idx = np.arange(idx_maxline.shape[0])
idx_filtered = idx[(idx_maxline < mean+std) & (idx_maxline > mean - std)] # filter positions where highest slope is at different position(blobs)
slope, intercept, r_value, p_value, std_err = stats.linregress(idx_filtered, idx_maxline[idx_filtered])
out = ndimage.rotate(image,slope*180/np.pi, reshape = False)
return out
out = rotate_image(img)
plt.imshow(out)