哈哈 准备开始挪一些 cv相的合集~
一、CVの文档自动扫描仪
介绍如何使用 OpenCV + GrabCut实现一个文档自动扫描仪。
背景介绍
文档扫描是将物理文档转换为数字形式的过程。可以通过扫描仪或手机摄像头拍摄图像来完成。我们将在本文中讨论如何使用计算机视觉和图像处理技术有效地实现这一目标。
有许多软件解决方案和应用程序可以做到这一点。借助计算机视觉的力量,从物理文档到扫描文档的过程与将相机对准文档并单击图片没有太大区别。速度和易用性是此类解决方案的主要优势,它们可用于计算机和移动设备。
让我们看看如何使用经典的计算机视觉技术创建一个简单的 OpenCV 文档扫描仪,其中输入将是我们要扫描的文档的图像,而预期的输出将是正确对齐的文档扫描图像。
实现目标
如下图所示,给定一张包含文档的图片,通过代码自动将文档提取并矫正。
编辑
实现步骤
测试原图如下:
编辑
实现步骤:
【1】通过形态学处理,得到一个空白页。这里直接用闭运算即可,闭运算是膨胀,然后是腐蚀。不断重复这些关闭操作,直到你得到一个空白页。
编辑
kernel = np.ones((5,5),np.uint8)
img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations= 3)
编辑
为什么我们想要一个空白文档呢?因为后面会进行边缘检测,并且我们不希望被页面的文字内容干扰该。
【2】用GrabCut去掉背景。
- 它只需要在前景中的对象周围设置一个边界框,边界框之外的所有内容都被视为背景。
- GrabCut 会自动消除所有背景,即使在边界框内也是如此。现在剩下的就是前景对象。
我们将角落 20 像素作为背景,GrabCut 会自动确定前景和背景,只留下文档。
mask = np.zeros(img.shape[:2],np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (20,20,img.shape[1]-20,img.shape[0]-20)
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]
编辑
【3】Canny边缘检测 + 轮廓提取。
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (11, 11), 0)
# Edge Detection.
canny = cv2.Canny(gray, 0, 200)
canny = cv2.dilate(canny, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)))
- 首先将空白页的图像转换为灰度,因为canny只对灰度图像起作用。
- 然后执行高斯模糊以去除图像中的噪声。
- 最后,对图像进行精确边缘检测。
- 此外,放大图像以获得文档的细轮廓。
# Blank canvas.
con = np.zeros_like(img)
# Finding contours for the detected edges.
contours, hierarchy = cv2.findContours(canny, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
# Keeping only the largest detected contour.
page = sorted(contours, key=cv2.contourArea, reverse=True)[:5]
con = cv2.drawContours(con, page, -1, (0, 255, 255), 3)
- 根据大小对检测到的轮廓进行排序
- 只保留检测到的最大轮廓
- 然后在空白画布上绘制这个检测到的最大轮廓
编辑
【4】角点检测 + 排序。
# Blank canvas.
con = np.zeros_like(img)
# Loop over the contours.
for c in page:
# Approximate the contour.
epsilon = 0.02 * cv2.arcLength(c, True)
corners = cv2.approxPolyDP(c, epsilon, True)
# If our approximated contour has four points
if len(corners) == 4:
break
cv2.drawContours(con, c, -1, (0, 255, 255), 3)
cv2.drawContours(con, corners, -1, (0, 255, 0), 10)
# Sorting the corners and converting them to desired shape.
corners = sorted(np.concatenate(corners).tolist())
# Displaying the corners.
for index, c in enumerate(corners):
character = chr(65 + index)
cv2.putText(con, character, tuple(c), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 1, cv2.LINE_AA)
编辑
角点排序:
def order_points(pts):
'''Rearrange coordinates to order:
top-left, top-right, bottom-right, bottom-left'''
rect = np.zeros((4, 2), dtype='float32')
pts = np.array(pts)
s = pts.sum(axis=1)
# Top-left point will have the smallest sum.
rect[0] = pts[np.argmin(s)]
# Bottom-right point will have the largest sum.
rect[2] = pts[np.argmax(s)]
diff = np.diff(pts, axis=1)
# Top-right point will have the smallest difference.
rect[1] = pts[np.argmin(diff)]
# Bottom-left will have the largest difference.
rect[3] = pts[np.argmax(diff)]
# Return the ordered coordinates.
return rect.astype('int').tolist()
确定目标坐标:一旦获得文档的角点,接下来只需要目标坐标来执行透视变换和对齐文档。
【5】透视变换对齐文档。
# Getting the homography.
M = cv2.getPerspectiveTransform(np.float32(corners), np.float32(destination_corners))
# Perspective transform using homography.
final = cv2.warpPerspective(orig_img, M, (destination_corners[2][0], destination_corners[2][1]), flags=cv2.INTER_LINEAR)
编辑
【6】扩展测试。
我们在 23 种不同的背景和不同的方向上进行了测试,自动文档扫描仪几乎在所有情况下都运行良好。
编辑
失败情况:
编辑
当文档的一部分在图像之外时,可能会丢失一个角落,GrabCut 无法扫描。这是使用 GrabCut 的唯一限制。在大多数其他情况下,我们的文档扫描仪运行良好。
这种方法的另一个限制是边缘和轮廓检测。如果背景中存在大量噪声,则会检测到许多不需要的边缘,并且在某些情况下,轮廓检测步骤可能会将这些边缘误认为是文档。此外,如果文档边缘与背景无法区分,则轮廓检测可能无法完全正常工作。
但 GrabCut 和轮廓检测并不是唯一经过验证的文档扫描方法。对于消费级文档扫描解决方案,首选角点检测和分割等深度学习技术,因为它们更强大。
参考链接:
https://learnopencv.com/automatic-document-scanner-using-opencv/
二、CVの复杂背景下的直线提取
介绍一个基于OpenCV实现的复杂背景下直线提取实例
案例来源于网络,如下图所示,要求提取图中的直线轮廓并标出:
编辑
实现步骤与代码
【1】中值滤波滤除噪点(中值滤波对椒盐噪声效果较好)。
img = cv2.imread('test.png')
cv2.imshow('src', img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray, 3)
cv2.imshow('blur',blur)
编辑
【2】滤波图像取反,然后使用HoughLinesP(累计概率霍夫变换)检测直线。图像取反原因是HoughLinesP是在黑色背景中找白色直线,这样效果更好。
blur = cv2.bitwise_not(blur)
cv2.imshow('bitwise_not',blur)
cv2.waitKey(0)
temp = np.zeros(blur.shape, np.uint8)
# Detect and draw lines
lines = cv2.HoughLinesP(blur, 1, np.pi/180, 10, minLineLength=50, maxLineGap=30)
for line in lines:
for x1, y1, x2, y2 in line:
cv2.line(temp, (x1, y1), (x2, y2), (255, 255, 255), 2)
编辑
编辑
【3】将检测到的直线的二值图细化。
cv2.imshow('temp', temp)
temp[temp==255] = 1
skeleton = morphology.skeletonize(temp)
thin = skeleton.astype(np.uint8)*255
cv2.imshow("thin",thin)
编辑
【4】使用细化图像再做一次HoughLinesP(累计概率霍夫变换)检测直线。
# Detect and draw lines again
lines = cv2.HoughLinesP(thin, 1, np.pi/180, 20, minLineLength=50, maxLineGap=30)
print(len(lines))
for line in lines:
for x1, y1, x2, y2 in line:
cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
编辑
三、CVの测量圆弧长度
介绍基于OpenCV实现两种方法测量圆弧长度
编辑
要求:如上所示,分别用OpenCV计算出图1和图2中圆弧的长度。因为OpenCV中没有提供现成计算圆弧的方法,所以需要自己编写,本文将提供2种不同的方法来实现,仅供参考。
实现步骤
编辑
首先以图1为例,如上图所示,方法一具体实现步骤如下:
【1】二值化 + 查找轮廓
img = cv2.imread('11.png')
cv2.imshow('src',img)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray, 70, 255, cv2.THRESH_BINARY)
contours,hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
【2】查找轮廓凸包缺陷,确定圆弧起点和终点坐标
hull = cv2.convexHull(contours[0],returnPoints=False)#默认returnPoints=True
defects = cv2.convexityDefects(contours[0],hull)
start = end = (0,0)
for i in range(0,defects.shape[0]):
s,e,f,d = defects[i,0]
start = tuple(contours[0][s][0])
end = tuple(contours[0][e][0])
far = tuple(contours[0][f][0])
if d > 5000:
cv2.line(img,start,end,[0,255,0],2)#凸包
#cv2.circle(img,far,5,[0,0,255],-1)#凸包缺陷点
cv2.circle(img,end,5,[0,0,255],-1)#凸包缺陷点
cv2.circle(img,start,5,[0,0,255],-1)#凸包缺陷点
break
cv2.imshow('defects', img)
cv2.waitKey(0)
编辑
【3】获取轮廓最小外接圆,获取圆心,计算圆弧角度
center,radius = cv2.minEnclosingCircle(contours[0])
cv2.circle(img,(int(center[0]),int(center[1])),8,(255,0,255),-1)
cv2.circle(img,end,8,[0,255,255],-1)#凸包缺陷点
cv2.circle(img,start,8,[0,255,255],-1)#凸包缺陷点
cv2.line(img,start,(int(center[0]),int(center[1])),[0,255,0],2)#凸包
cv2.line(img,end,(int(center[0]),int(center[1])),[0,255,0],2)#凸包
angle = cal_ang(start,center,end)
print('angle = %0.2f' % angle)
编辑
编辑
【4】通过外接圆周长角度比例来计算弧长
length = (1 - angle / 360.0) * math.pi * radius * 2
print((angle / 360.0))
print('radius = %0.2f' % radius)
strL = 'length=%0.2f' % length
cv2.putText(img,strL,(int(center[0]-40),int(center[1]+40)),0,0.8,(255,0,0),2)
cv2.imshow('result', img)
angle_1 = cal_ang(start, center, ((center[0]+100),(center[1])))
angle_2 = cal_ang(end, center, ((center[0]+100),(center[1])))
cv2.ellipse(img,(int(center[0]),int(center[1])),(int(radius),int(radius)),0,-angle_1,0,(255,0,255),2, cv2.LINE_AA)
cv2.ellipse(img,(int(center[0]),int(center[1])),(int(radius),int(radius)),0,0,angle_2,(255,0,255),2, cv2.LINE_AA)
cv2.imshow('result', img)
cv2.imwrite('result.bmp',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
编辑
以图2做测试,验证结果:
编辑
方法二将步骤【2】替换为通过角点检测来获取弧线端点坐标:
feature_params = dict(maxCorners = 2,
qualityLevel = 0.3,
minDistance = 50,
blockSize = 5 )
corners = cv2.goodFeaturesToTrack(thresh,mask=None,**feature_params)
print ('cornerNum = ' + str(len(corners)))
for i in range(0,len(corners)):
cv2.circle(img,(int(corners[i][0][0]),int(corners[i][0][1])),8,(0,255,0),-1, cv2.LINE_AA)
cv2.imshow('cornerNum', img)
编辑
最终结果:
编辑
测试图2结果:
编辑
四、CVの魔方颜色识别与色块排序
为了做自动魔方识别与复原项目,需要用图像处理的方法识别魔方每个色块的位置与颜色。相机拍摄的魔方单面图像如下:
编辑
实现步骤
本文主要使用OpenCV来实现魔方颜色识别与色块位置排序。
【1】颜色识别。设定每个色块在HSV颜色空间的范围来判断和提取颜色。
image = cv2.imread('1.png')
original = image.copy()
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
mask = np.zeros(image.shape, dtype=np.uint8)
colors = {
'gray': ([76, 0, 41], [179, 255, 70]), # Gray
'blue': ([69, 120, 100], [179, 255, 255]), # Blue
'yellow': ([21, 110, 117], [45, 255, 255]), # Yellow
'orange': ([0, 110, 125], [17, 255, 255]) # Orange
}
【2】提取色块掩码。在步骤【1】的基础上添加形态学处理,然后通过或操作
(cv2.bitwise_or)
将所有色块区域提取出来。
# Color threshold to find the squares
open_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7,7))
close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
for color, (lower, upper) in colors.items():
lower = np.array(lower, dtype=np.uint8)
upper = np.array(upper, dtype=np.uint8)
color_mask = cv2.inRange(image, lower, upper)
color_mask = cv2.morphologyEx(color_mask, cv2.MORPH_OPEN, open_kernel, iterations=1)
color_mask = cv2.morphologyEx(color_mask, cv2.MORPH_CLOSE, close_kernel, iterations=5)
color_mask = cv2.merge([color_mask, color_mask, color_mask])
mask = cv2.bitwise_or(mask, color_mask)
编辑
【3】色块排序。排序使用imutils包的contours模块实现。
from imutils import contours
先做一次从上到下排序,再做一次从左到右排序即可:
gray = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
cnts = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
# Sort all contours from top-to-bottom or bottom-to-top
(cnts, _) = contours.sort_contours(cnts, method="top-to-bottom")
# Take each row of 3 and sort from left-to-right or right-to-left
cube_rows = []
row = []
for (i, c) in enumerate(cnts, 1):
row.append(c)
if i % 3 == 0:
(cnts, _) = contours.sort_contours(row, method="left-to-right")
cube_rows.append(cnts)
row = []
# Draw text
number = 0
for row in cube_rows:
for c in row:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(original, (x, y), (x + w, y + h), (36,255,12), 2)
cv2.putText(original, "#{}".format(number + 1), (x,y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,255), 2)
number += 1
编辑
编辑
当然,还可以从下到上,从右向左排序。只需要设置下面的method即可:
(cnts, _) = contours.sort_contours(cnts, method="top-to-bottom")
(cnts, _) = contours.sort_contours(row, method="left-to-right")
编辑
编辑
编辑
【4】如何获取HSV范围。可以使用下面代码,通过滑动条动态调整的方法获取:
import cv2
import numpy as np
def nothing(x):
pass
# Load image
image = cv2.imread('1.jpg')
# Create a window
cv2.namedWindow('image')
# Create trackbars for color change
# Hue is from 0-179 for Opencv
cv2.createTrackbar('HMin', 'image', 0, 179, nothing)
cv2.createTrackbar('SMin', 'image', 0, 255, nothing)
cv2.createTrackbar('VMin', 'image', 0, 255, nothing)
cv2.createTrackbar('HMax', 'image', 0, 179, nothing)
cv2.createTrackbar('SMax', 'image', 0, 255, nothing)
cv2.createTrackbar('VMax', 'image', 0, 255, nothing)
# Set default value for Max HSV trackbars
cv2.setTrackbarPos('HMax', 'image', 179)
cv2.setTrackbarPos('SMax', 'image', 255)
cv2.setTrackbarPos('VMax', 'image', 255)
# Initialize HSV min/max values
hMin = sMin = vMin = hMax = sMax = vMax = 0
phMin = psMin = pvMin = phMax = psMax = pvMax = 0
while(1):
# Get current positions of all trackbars
hMin = cv2.getTrackbarPos('HMin', 'image')
sMin = cv2.getTrackbarPos('SMin', 'image')
vMin = cv2.getTrackbarPos('VMin', 'image')
hMax = cv2.getTrackbarPos('HMax', 'image')
sMax = cv2.getTrackbarPos('SMax', 'image')
vMax = cv2.getTrackbarPos('VMax', 'image')
# Set minimum and maximum HSV values to display
lower = np.array([hMin, sMin, vMin])
upper = np.array([hMax, sMax, vMax])
# Convert to HSV format and color threshold
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, lower, upper)
result = cv2.bitwise_and(image, image, mask=mask)
# Print if there is a change in HSV value
if((phMin != hMin) | (psMin != sMin) | (pvMin != vMin) | (phMax != hMax) | (psMax != sMax) | (pvMax != vMax) ):
print("(hMin = %d , sMin = %d, vMin = %d), (hMax = %d , sMax = %d, vMax = %d)" % (hMin , sMin , vMin, hMax, sMax , vMax))
phMin = hMin
psMin = sMin
pvMin = vMin
phMax = hMax
psMax = sMax
pvMax = vMax
# Display result image
cv2.imshow('image', result)
if cv2.waitKey(10) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
编辑
编辑
编辑
编辑
五、CVの检测并计算直线角度
检测线可用于各种类型的应用,例如机器人导航、无人机导航、运动分析和交通管理。本文我们将使用HoughLinesP函数来检测线条并从线条中提取角度。
编辑
检测线
想象一下,您想要创建一架无人机或一辆可以沿着路线行驶的简单汽车。事实上,创造和使用它似乎很有趣。或者您可能想跟踪道路上的线路并根据车道对汽车进行分类。对于这些类型的程序,您需要检测线条,并且可能需要根据您的目的提取角度。OpenCV 提供了多种检测直线和提取角度的函数:
- HoughLines()
- HoughLinesP()
选择合适的功能
HoughLinesP可以检测碎片或不连续线(例如图像中的虚线或线段)时更有效。它提供有关检测到的线路的更多详细信息。
HoughLines更适合检测完整、连续的线条。当检测图像中长的、不间断的线条时,它非常有用。
我们需要一种可以检测各种形状的线条的算法,而不仅仅是直线。例如,如果道路向右成 45 度角,则 HoughLines() 函数在这种情况下可能无效。因此,我将使用HoughLinesP()函数
HoughLinesP() 函数如何工作?
我不会深入研究该HoughLinesP()函数的数学细节,因为 Google 或 Youtube 等平台上有很多可用的资源。下面简单解释一下该HoughLinesP()函数的工作原理:
OpenCV 中的函数HoughLinesP使用概率霍夫变换来检测图像中的线段。
- 从边缘检测开始提取潜在的线点,然后进行投票过程来识别经过这些点的线,在参数空间中累积投票。
- 参数空间是霍夫变换累积投票以检测图像中的线条的地方,每个维度代表被检测模型的一个参数,例如线条的斜率和截距。
- 在阈值化选择突出线之后,它提取由端点表示的线段,提供有关其位置和长度的详细信息。
这种方法可有效检测碎片或不连续的线,例如虚线或线段,与传统方法(如HoughLines.
HoughLinesP() 函数的参数
HoughLinesP(image, rho, theta, threshold,minLineLength,maxLineGap)
- image:8 位、单通道输入图像。
- rho:累加器的距离分辨率(以像素为单位)。(1在大多数情况下是有效的)
- theta:累加器的角度分辨率(以弧度为单位)。(np.pi/180)
- 阈值:简单的阈值。较高的阈值会导致线条更少但更准确,而较低的阈值会产生相反的效果。设置此参数的最佳方法是尝试不同的值。这完全取决于您的目的。
- minLineLength:最小行长度。
- maxLineGap:同一直线上的点之间允许的最大间隙以链接它们。
选择这些参数(Threshold、minLineLength 和 maxLineGap)的最佳方法是尝试不同的值组合。这些参数没有通用的最佳值;它们取决于图像的具体特征和期望的结果。
检测直线时如何使用提取的角度?
想象一下算法从直线中提取出 67 度角。等式为:
direction_x = cos(extracted_angle=67) → velocity_x=1*direction_x
direction_y = sin(extracted_angle=67) → velocity_y=1*direction_y
编辑
步骤/代码
1. 读取图像并将其转换为灰度
2. 使用 Canny 边缘检测器提取边缘。
3. 对Canny边缘检测后得到的图像应用HoughLinesP函数。
4. 获取 HoughLinesP 函数的输出,对其进行迭代,绘制线条,并使用简单的公式提取角度。
Canny 边缘检测
Canny 边缘检测器有助于识别图像中的边缘
编辑
检测直线并计算角度代码:
# read image
image = cv2.imread("resources/blog1.png")
# conert image to gray
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# extract edges with canny edge detector
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
# apply HoughLinesP
# cv2.HoughLinesP() returns an array of lines detected in the image.
lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=75, minLineLength=100, maxLineGap=20)
if lines is not None:
for i, line in enumerate(lines):
x1, y1, x2, y2 = line[0]
dy = (y2 - y1)
dx = (x2 - x1)
# convert radian to degree and extract angle
angle = np.rad2deg(np.arctan2(dy, dx))
# Since the Y-axis increases downwards(in opencv), invert the angle.
angle = 180 - angle if angle > 0 else -angle
# different color for every line
color = tuple(np.random.randint(0, 255, 3).tolist())
# detected line
cv2.line(rgb_image, (x1, y1), (x2, y2), color, 3)
# draw shape to starting and finishing points of lines
cv2.putText(rgb_image, '>', (x1, y1), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255), 3)
cv2.putText(rgb_image, '<', (x2, y2), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255), 3)
# angle
cv2.putText(rgb_image, str(round(angle, 1)),(x1 , int((y1+y2)/2)), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 3)
plt.imshow(rgb_image)
编辑
编辑