(有的运行结果没弄上去,但文中代码本人亲测均通过;至于有人因版本问题出现个别错误,我相信对于大家应该没什么问题,文档就是很好的辅助学习资料)
二十二、直线检测
使用了霍夫变换,具体可以查阅其他资料了解
介绍使用的三个函数:
(1)HoughLines()
作用:使用标准霍夫变换在二进制图像中查找线。
void cv::HoughLines ( InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double srn = 0,
double stn = 0,
double min_theta = 0,
double max_theta = CV_PI
)
Python:
lines = cv.HoughLines( image, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta]]]]] )
image 8位单通道二进制源图像。该图像可以通过该功能进行修改。
lines 线的输出向量。每行由2或3个元素向量表示(ρ ,θ ) 要么 (ρ ,θ ,票数) 。 ρ 是距坐标原点的距离 (0 ,0 ) (图片的左上角)。 θ 是线的旋转角度,以弧度( 0 〜垂直线,π/ 2〜水平线 )。 票数 是累加器的值。
rho 累加器的距离分辨率(以像素为单位)。
theta 累加器的角度分辨率(以弧度为单位)。
threshold 累加器阈值参数。仅返回获得足够投票的行(> 门槛 )。大于阈值 threshold 的线段才可以被检测通过并返回到结果中。
srn 对于多尺度霍夫变换,它是距离分辨率rho的除数。粗略的累加器距离分辨率为rho,准确的累加器分辨率为rho / srn。如果srn = 0和stn = 0都使用经典的Hough变换。否则,这两个参数都应为正。
stn 对于多尺度霍夫变换,它是距离分辨率θ的除数。
min_theta 对于标准和多尺度霍夫变换,请使用最小角度检查线条。必须介于0和max_theta之间。
max_theta 对于标准和多尺度霍夫变换,请使用最大角度来检查线条。必须介于min_theta和CV_PI之间。
(2) HoughLinesP()
作用:使用概率霍夫变换在二进制图像中查找线段。
void cv::HoughLinesP ( InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double minLineLength = 0,
double maxLineGap = 0
)
Python:
lines = cv.HoughLinesP( image, rho, theta, threshold[, lines[, minLineLength[, maxLineGap]]] )
image 8位单通道二进制源图像。该图像可以通过该功能进行修改。
lines 线的输出向量。每行由4元素向量表示(X1个,ÿ1个,X2,ÿ2) ,在哪里 (X1个,ÿ1个) 和 (X2,ÿ2) 是每个检测到的线段的终点。
rho 累加器的距离分辨率(以像素为单位)。
theta 累加器的角度分辨率(以弧度为单位)。
threshold 累加器阈值参数。仅返回获得足够投票的行(> 门槛 )。大于阈值 threshold 的线段才可以被检测通过并返回到结果中。
minLineLength 最小线长。短于此的线段将被拒绝。
maxLineGap 连接同一条线上的点之间的最大允许间隙。
(3)line()
void cv::line ( InputOutputArray img,
Point pt1,
Point pt2,
const Scalar & color,
int thickness = 1,
int lineType = LINE_8,
int shift = 0
)
Python:
img = cv.line( img, pt1, pt2, color[, thickness[, lineType[, shift]]] )
img 图片。
pt1 线段的第一点。
pt2 线段的第二点。
color 线条颜色。
thickness 线的粗细。
lineType 线的类型。请参见LineTypes。
shift 点坐标中的小数位数。
代码如下:
import cv2 as cv
import numpy as np
# 霍夫直线检测
def line_image(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
edges = cv.Canny(gray, 50, 150, apertureSize=3)
lines = cv.HoughLines(edges, 1, np.pi/180, 200)
for line in lines:
# print(type(line)) 通过输出可以发现line是一个多维数组,该函数内第一行为rho,theta,所以用rho, theta = line[0]来提取rho和theta
rho, theta = line[0]
a = np.cos(theta)
b = np.sin(theta)
# 由极坐标转换为x,y坐标
x0 = a * rho
y0 = b * rho
# 下方之所以乘1000,是由于opencv源码导致
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
cv.line(image, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv.imshow("image-lines", image)
def line_detect_possible_demo(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
edges = cv.Canny(gray, 50, 150, apertureSize=3)
lines = cv.HoughLinesP(edges, 1, np.pi / 180, 100, minLineLength=50, maxLineGap=5)
for line in lines:
# print(type(line)) 通过输出可以发现line是一个多维数组,该函数内第一行为x1, y1, x2, y2,所以用x1, y1, x2, y2 = line[0]来提取x1, y1, x2, y2
x1, y1, x2, y2 = line[0]
cv.line(image, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv.imshow("line_detect_possible_demo", image)
src = cv.imread("D:/image/lines.jpg")
cv.namedWindow("Hello opencv!", cv.WINDOW_AUTOSIZE)
cv.imshow("Hello opencv!", src)
# line_image(src)
line_detect_possible_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()
print("Hi,Python!")
结果如下:
HoughLinesP,很明显,使用该函数效果更好
二十三、圆检测
(1)HoughCircles()函数
void cv::HoughCircles ( InputArray image,
OutputArray circles,
int method,
double dp,
double minDist,
double param1 = 100,
double param2 = 100,
int minRadius = 0,
int maxRadius = 0
)
Python:
circles = cv.HoughCircles( image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]] )
作用:使用霍夫变换在灰度图像中查找圆。
image 8位单通道灰度输入图像。
circles 找到的圆的输出向量。每个向量都编码为3或4元素浮点向量(x,y,radius) 要么(x,y,radius,votes)
method 检测方法,请参见HoughModes。当前,唯一实现的方法是HOUGH_GRADIENT
dp 累加器分辨率与图像分辨率的反比。例如,如果dp = 1,则累加器具有与输入图像相同的分辨率。如果dp = 2,则累加器的宽度和高度是其一半。
minDist 检测到的圆心之间的最小距离。如果参数太小,则除了真实的圆圈外,还可能会错误地检测到多个邻居圆圈。如果太大,可能会错过一些圆圈。
param1 第一个方法特定的参数。在HOUGH_GRADIENT的情况下,它是传递给Canny边缘检测器的两个阈值中的较高阈值(较低的阈值则小两倍)。
param2 第二种方法特定的参数。在HOUGH_GRADIENT的情况下,它是检测阶段圆心的累加器阈值。它越小,可能会检测到更多的假圆圈。与较大的累加器值相对应的圆将首先返回。
minRadius 最小圆半径。
maxRadius 最大圆半径。如果<= 0,则使用最大图像尺寸。如果<0,则返回中心而未找到半径。
(2)circle()函数
void cv::circle ( InputOutputArray img,
Point center,
int radius,
const Scalar & color,
int thickness = 1,
int lineType = LINE_8,
int shift = 0
)
Python:
img = cv.circle( img, center, radius, color[, thickness[, lineType[, shift]]] )
作用:画一个圆。
img 画圆的图像。
center 圆心。
radius 圆的半径。
color 圆圈颜色。
thickness圆形轮廓的粗细(如果为正)。负值(如FILLED)表示要绘制一个实心圆。
lineType 圆边界的类型。查看线型
shift中心坐标和半径值中的小数位数
代码如下
import cv2 as cv
import numpy as np
def detect_circle_demo(image):
# 边缘保留滤波中的均值迁移,因为霍夫检测对噪声比较敏感,所以需要先均值迁移
dst = cv.pyrMeanShiftFiltering(image, 10, 100)
# 霍夫变换是对灰度图像处理,要转换为灰度图像
cimage = cv.cvtColor(dst, cv.COLOR_RGB2GRAY)
circles = cv.HoughCircles(cimage, cv.HOUGH_GRADIENT, 1, 30, param1=70, param2=30, minRadius=0, maxRadius=0)
# np.around函数返回指定数字的四舍五入值,np.uint16转换为无符号16位整数
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
cv.circle(image, (i[0], i[1]), i[2], (0, 0, 255), 2)
# 对圆心进行设置,第三个参数为圆心处点的半径大小
cv.circle(image, (i[0], i[1]), 2, (255, 0, 0), 2)
cv.imshow("detect_circle_demo", image)
src = cv.imread("D:/image/circular.jpg")
cv.namedWindow("circular!", cv.WINDOW_AUTOSIZE)
cv.imshow("circular!", src)
detect_circle_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()
print("Hi,Python!")
结果如下:
效果不是很好,有待改进。HoughCircles函数中的参数对结果影响比较大,可以自己多试试。
二十四、轮廓发现
使用了边缘检测和二值化图像两种方法来生成初始图片,用来对其增加轮廓
(1)findContours()
void cv::findContours ( InputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset = Point()
)
Python:
contours, hierarchy = cv.findContours( image, mode, method[, contours[, hierarchy[, offset]]] )
作用:查找二进制图像中的轮廓。
image 源,一个8位单通道图像。非零像素被视为1。零像素保持为0,因此图像被视为binary。您可以使用compare,inRange,threshold,adaptiveThreshold,Canny和其他图像来创建灰度或彩色图像之外的二进制图像。如果mode等于RETR_CCOMP或RETR_FLOODFILL,则输入也可以是标签(CV_32SC1)的32位整数图像。
contours 检测到的轮廓。每个轮廓都存储为点的向量(例如std :: vector <std :: vector >)。
hierarchy 可选的输出向量(例如std :: vector ),包含有关图像拓扑的信息。它具有与轮廓数量一样多的元素。对于每个第i个轮廓轮廓[i],将元素等级[i] [0],等级[i] [1],等级[i] [2]和等级[i] [3]设置为0-在相同的层次级别上,基于下一个和上一个轮廓的轮廓的索引,分别是第一个子轮廓和父轮廓。如果对于轮廓i,没有下一个,上一个,父级或嵌套的轮廓,则hierarchy [i]的相应元素将为负。
mode轮廓检索模式
method 轮廓近似方法
offset 每个轮廓点移动的可选偏移量。如果从图像ROI中提取轮廓,然后在整个图像上下文中对其进行分析,这将非常有用。
(2)drawContours()
void cv::drawContours ( InputOutputArray image,
InputArrayOfArrays contours,
int contourIdx,
const Scalar & color,
int thickness = 1,
int lineType = LINE_8,
InputArray hierarchy = noArray(),
int maxLevel = INT_MAX,
Point offset = Point()
)
Python:
image = cv.drawContours( image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]] )
**作用:**绘制轮廓轮廓或填充轮廓。如果厚度>0,填充轮廓,如果厚度<0,填充轮廓包围的区域
image 目标图像。
contours 所有输入轮廓。每个轮廓都存储为点向量。
SilhouetteIdx 指示要绘制轮廓的参数。如果为负,则绘制所有轮廓。
color 轮廓的颜色。
thickness 绘制轮廓的线的粗细。如果为负数(例如,厚度= FILLED),则绘制轮廓内部。
lineType 线路连接。
hierarchy 有关层次结构的可选信息。仅当您只想绘制一些轮廓时才需要(请参见maxLevel)。
maxLevel 绘制轮廓的最大水平。如果为0,则仅绘制指定的轮廓。如果为1,该函数将绘制轮廓和所有嵌套轮廓。如果为2,该函数将绘制轮廓,所有嵌套轮廓,所有嵌套到嵌套的轮廓,等等。仅当存在可用的层次结构时,才考虑此参数。
offset 可选的轮廓偏移参数。将所有绘制的轮廓移动指定的偏移量 =(dX ,dÿ) 。
import cv2 as cv
import numpy as np
# 边缘检测
def edge_demo(image):
# 1.高斯模糊-GaussianBlur,Canny对噪声比较铭感,而且Canny没有自带的高斯模糊处理,所以需要先使用高斯模糊降噪一下
blurred = cv.GaussianBlur(image, (3, 3), 0)
# 2.灰度转换-cvtColor
gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY)
# 3.计算梯度-Sobel/Scharr
xgrad = cv.Sobel(gray, cv.CV_16SC1, 1, 0)
ygrad = cv.Sobel(gray, cv.CV_16SC1, 0, 1)
# 4.非最大信号抑制
# 5.高低阈值输出二值图像
# edge_output = cv.Canny(xgrad, ygrad, 50, 150)
# 直接用灰度图像也可以
edge_output = cv.Canny(gray, 50, 120)
cv.imshow("Canny Edge", edge_output)
return edge_output
def contours_demo(image):
"""
# 1.二值化生成图片
# 高斯降噪
dst = cv.GaussianBlur(image, (3, 3), 0)
gray = cv.cvtColor(dst, cv.COLOR_BGR2GRAY)
# 二值化图像
ret, binary = cv.threshold(gray, 30, 255, cv.THRESH_BINARY)
cv.imshow("binary", binary)
"""
# 2. 边缘检测生成图片
binary = edge_demo(image)
# 两种方式均可
contours, hireachy= cv.findContours(binary, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# contours, hireachy= cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# for i, contour in enumerate(contours):
# cv.drawContours(image, contours, i, (0, 0, 255), -1)
# print(i)
# drawContours中第三个为SilhouetteIdx参数,指示要绘制轮廓的参数。如果为负数,则绘制所有轮廓,和上面循环效果相同
# 第五个参数是厚度thickness,如果厚度 > 0,填充轮廓,如果厚度 < 0, 填充轮廓包围的区域
cv.drawContours(image, contours, -1, (0, 0, 255), 2)
cv.imshow("detect contours", image)
src = cv.imread("D:/image/blob.png")
cv.namedWindow("lena!", cv.WINDOW_AUTOSIZE)
cv.imshow("lena!", src)
contours_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()
print("Hi,Python!")
二十五、对象测量
import cv2 as cv
import numpy as np
def measure_object(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
print("threshold value : %s" % ret)
cv.imshow("binary image", binary)
# 为了方便观察,让结果在dst图像上显示,只是为了增加成三通道,灰度图像无法回复成彩色图像
dst = cv.cvtColor(binary, cv.COLOR_GRAY2BGR)
contours, hireachy = cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
for i, contour in enumerate(contours):
# 计算轮廓区域面积
area = cv.contourArea(contour)
# 计算点集或灰度图像的非零像素的右边界矩形。
# 该函数为指定的点集或灰度图像的非零像素计算并返回最小的垂直边界矩形。
x, y, w, h = cv.boundingRect(contour)
# 宽高比
rate = min(w, h)/max(w, h)
print("rectangle rate: %s" % rate)
# 该函数计算矢量形状或栅格化形状的力矩,直到三阶
mm = cv.moments(contour)
print(type(mm))
# 求出重心(质心)的坐标
cx = mm['m10']/mm['m00']
cy = mm['m01']/mm['m00']
# 在重心(质心)画一个圆
cv.circle(dst, (np.int(cx), np.int(cy)), 3, (0, 255, 255), -1)
# 绘制一个简单的,粗的或实心的直角矩形。
cv.rectangle(dst, (x, y), (x+w, y+h), (0, 0, 255), 2)
print("contour area %s" % area)
# 以指定的精度逼近多边形曲线。
approxCurve = cv.approxPolyDP(contour, 4, True)
print(approxCurve.shape)
# approxCurve.shape[0]表示的是图形边的个数
if approxCurve.shape[0] > 6:
cv.drawContours(dst, contours, i, (0, 255, 0), 2)
if approxCurve.shape[0] == 4:
cv.drawContours(dst, contours, i, (0, 255, 255), 2)
if approxCurve.shape[0] == 3:
cv.drawContours(dst, contours, i, (255, 0, 0), 2)
cv.imshow("measure-contours", dst)
src = cv.imread("D:/image/blob.png")
cv.namedWindow("lena!", cv.WINDOW_AUTOSIZE)
cv.imshow("lena!", src)
measure_object(src)
cv.waitKey(0)
cv.destroyAllWindows()
print("Hi,Python!")
二十六、膨胀和腐蚀
**1.**可以把膨胀看成是逻辑或运算,例如下图33的模板下,有1就将中心元素改成1
可以吧腐蚀看成是逻辑与运算,例如下图33的模板下,有0就将中心元素改成0
2.至于这个33的模板是采用卷积的形式移动。如果是彩色图像,就是膨胀的时候,也是采用逻辑或运算,返回的是较大值,将33模板中较大的值放在中心位置;腐蚀的时候,是采用逻辑与运算,返回的是较小值,将3*3模板中较小的值放在中心位置。具体逻辑运算可参考第八章。模板大小可以随意调整,原理相同。
3.retval = cv.getStructuringElement( shape, ksize[, anchor] )
shape 元素形状可能是MorphShapes之一,下方是3种结果
ksize 结构元素的大小。
anchor 元素内的锚位置。默认值(− 1 ,− 1 )表示锚点位于中心。注意,只有十字形元件的形状取决于锚位置。在其他情况下,锚点仅调节形态运算结果偏移了多少。
4.morphologyEx()
void cv::morphologyEx ( InputArray src,
OutputArray dst,
int op,
InputArray kernel,
Point anchor = Point(-1,-1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar & borderValue = morphologyDefaultBorderValue()
)
Python:
dst = cv.morphologyEx( src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] )
**作用:**执行高级形态转换
src 源图像。通道数可以是任意的。深度应为CV_8U,CV_16U,CV_16S,CV_32F或CV_64F之一。
dst 与源图像大小和类型相同的目标图像。
op 形态学运算的类型,请参见MorphTypes(后续用到七个,第八个未使用)
kernel 结构元素。可以使用(3.getStructuringElement)创建它。
anchor 内核的锚位置。负值表示锚点位于内核中心。
iterations 施加腐蚀和膨胀的次数。
borderType 像素外推方法,请参见BorderTypes(一般默认)
borderValue 边界不变时的边界值。默认值具有特殊含义
import cv2 as cv
import numpy as np
# 腐蚀
def erode_demo(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
# cv.THRESH_BINARY_INV 使用INV反过来是为了让背景变成黑色,要根据实际情况调整代码,如果背景就是黑色就不必取反
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)
cv.imshow("binary_erode", binary)
# 返回指定大小和形状的结构元素,以进行形态学操作。
# 该函数构造并返回可进一步传递给erode,dilate或morphologyEx的结构元素
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
dst = cv.erode(binary, kernel)
cv.imshow("erode_demo", dst)
# 膨胀
def dilate_demo(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
# cv.THRESH_BINARY_INV 使用INV反过来是为了让背景变成黑色,要根据实际情况调整代码,如果背景就是黑色就不必取反
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)
cv.imshow("binary_dilate", binary)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
dst = cv.dilate(binary, kernel)
cv.imshow("dilate_demo", dst)
src = cv.imread("D:/image/lena.jpg")
cv.namedWindow("input image!", 0)
cv.imshow("input image!", src)
# erode_demo(src)
# dilate_demo(src)
# 也可对彩色图像进行膨胀和腐蚀
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
dst = cv.dilate(src, kernel)
cv.imshow("result", dst)
cv.waitKey(0)
cv.destroyAllWindows()
print("Hi,Python!")
二十七、开闭操作
可以看出,开闭的膨胀和腐蚀操作顺序相反
开闭操作作用:
1.开操作可以去除小的干扰块
2.闭操作可以填充闭合区域
3.水平或者垂直线提取
import cv2 as cv
import numpy as np
def open_demo(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
# 下方代码灵活性很大,二值化要根据不同的图做不同的变化
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
cv.imshow("binary_erode", binary)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
# morphologyEx包含许多形态学操作
dst = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel)
cv.imshow("open_demo", dst)
def close_demo(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
# 下方代码灵活性很大,二值化要根据不同的图做不同的变化
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
cv.imshow("binary_erode", binary)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (15, 15))
# morphologyEx包含许多形态学操作
dst = cv.morphologyEx(binary, cv.MORPH_CLOSE, kernel)
cv.imshow("close_demo", dst)
src = cv.imread("D:/image/bin3.jpg")
cv.namedWindow("input image!", cv.WINDOW_AUTOSIZE)
cv.imshow("input image!", src)
# open_demo(src)
close_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()
print("Hi,Python!")
结果如下:
1.可以看出,开操作可以去除小的干扰块,闭操作可以填充闭合区域。
2.ret, binary = cv.threshold(gray, 220, 255, cv.THRESH_BINARY_INV)对下方图像二值化的时候修改该行代码,不然一些线段会被忽略。取反让背景变成黑色,主题变成白色。
kernel = cv.getStructuringElement(cv.MORPH_RECT, (15, 1))如果将模板改成15行1列,那么可以去除竖线,提取横向线段。
kernel = cv.getStructuringElement(cv.MORPH_RECT, (1, 15))如果将模板改成1行15列,那么可以去除横线,提取竖向线段。
3.虽然是线段,但是依然很小。注意
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU),我依然取反,只是为了让主题部分变成白色和背景变成黑色,而大部分时间背景都是白色,所以需要根据实际情况看要不要取反
4.kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (15, 15)) 在上一章介绍到cv.MORPH_ELLIPSE是一个椭圆形结构,可以做到提取圆的效果
原图:
结果如下:
二十八、其他形态学操作
(1)顶帽-tophat
(2)黑帽-blackhat
(3)形态学梯度-Gradient
import cv2 as cv
import numpy as np
def top_hat_gray_demo(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
dst = cv.morphologyEx(gray, cv.MORPH_TOPHAT, kernel)
# 提升亮度
cimage = np.array(gray.shape, np.uint8)
cimage = 120
dst = cv.add(dst, cimage)
cv.imshow("tophat", dst)
def black_hat_gray_demo(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
dst = cv.morphologyEx(gray, cv.MORPH_BLACKHAT, kernel)
# 提升亮度
cimage = np.array(gray.shape, np.uint8)
cimage = 120
dst = cv.add(dst, cimage)
cv.imshow("blackhat", dst)
def top_hat_binary_demo(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (15, 15))
dst = cv.morphologyEx(binary, cv.MORPH_TOPHAT, kernel)
cv.imshow("tophat", dst)
def black_hat_binary_demo(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (15, 15))
dst = cv.morphologyEx(binary, cv.MORPH_BLACKHAT, kernel)
cv.imshow("blackhat", dst)
def gradient_binary_demo(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
dst = cv.morphologyEx(binary, cv.MORPH_GRADIENT, kernel)
cv.imshow("gradient", dst)
def internal_external_gradient_binary_demo(image):
kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
dm = cv.dilate(image, kernel)
em = cv.erode(image, kernel)
dst1 = cv.subtract(image, em)
dst2 = cv.subtract(dm, image)
cv.imshow("internal_gradient", dst1)
cv.imshow("external_gradient", dst2)
src = cv.imread("D:/image/bin2.jpg")
cv.namedWindow("input image!", cv.WINDOW_AUTOSIZE)
cv.imshow("input image!", src)
# top_hat_gray_demo(src) 灰度图像的顶帽
# black_hat_gray_demo(src) 灰度图像的黑帽
# top_hat_binary_demo(src) 二值化图像的顶帽
# black_hat_binary_demo(src) 二值化图像的黑帽
# gradient_binary_demo(src) 二值化图像的基本梯度
# 二值化图像的内部和外部梯度
internal_external_gradient_binary_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()
print("Hi,Python!")
二十九、分水岭算法(基于距离变换)
介绍几个函数:
(1)distanceTransform()
void cv::distanceTransform ( InputArray src,
OutputArray dst,
OutputArray labels,
int distanceType,
int maskSize,
int labelType = DIST_LABEL_CCOMP
)
Python:
dst = cv.distanceTransform( src, distanceType, maskSize[, dst[, dstType]] )
dst, labels = cv.distanceTransformWithLabels( src, distanceType, maskSize[, dst[, labels[, labelType]]] )
本文使用了第一个
作用:函数cv :: distanceTransform计算每个二进制图像像素到最近的零像素之间的近似或精确距离。对于零图像像素,该距离显然将为零。
参数如下:
src 8位单通道(二进制)源图像。
dst 输出具有计算出的距离的图像。它是与src大小相同的8位或32位浮点单通道图像。
labels 输出标签的二维数组(离散Voronoi图)。它的类型为CV_32SC1,大小与src相同。
distanceType 距离类型,请参见DistanceTypes(下表)
maskSize 距离变换蒙版的大小,请参见DistanceTransformMasks(下表)。此变体不支持DIST_MASK_PRECISE。对于DIST_L1或DIST_C距离类型,该参数被强制为3,因为3 × 35 × 5
labelType 要构建的标签数组的类型,请参见DistanceTransformLabelTypes。
(2)normalize()
void cv::normalize ( InputArray src,
InputOutputArray dst,
double alpha = 1,
double beta = 0,
int norm_type = NORM_L2,
int dtype = -1,
InputArray mask = noArray()
)
Python:
dst = cv.normalize( src, dst[, alpha[, beta[, norm_type[, dtype[, mask]]]]] )
作用: 规范数组的范数或值范围。
参数如下
src 输入数组。
dst 与src大小相同的输出数组。
alpha 在范围归一化的情况下用于归一化到下限边界的标准值。
beta 在范围归一化的情况下,范围的上限边界;它不用于规范归一化。
norm_type 规范化类型(请参阅cv :: NormTypes)。非常多!!!
dtype 如果为负,则输出数组与src具有相同的类型;否则,它的通道数与src相同,并且depth = CV_MAT_DEPTH(dtype)。
mask 可选的操作面罩。
(3)connectedComponents()
int cv::connectedComponents ( InputArray image,
OutputArray labels,
int connectivity,
int ltype,
int ccltype
)
Python:
retval, labels = cv.connectedComponents( image[, labels[, connectivity[, ltype]]] )
retval, labels = cv.connectedComponentsWithAlgorithm( image, connectivity, ltype, ccltype[, labels] )
本文使用第一个
作用: 计算布尔图像的连接组件标记图像
iamge 要标记的8位单通道图像
labels 目的地标记图像
connectivity 8路或4路连接分别为8或4
ltype 输出图像标签类型。当前支持CV_32S和CV_16U。
ccltype 连接组件的算法类型(请参阅ConnectedComponentsAlgorithmsTypes)。
(4)watershed()
void cv::watershed ( InputArray image,
InputOutputArray markers
)
Python:
markers = cv.watershed( image, markers )
作用: 使用分水岭算法执行基于标记的图像分割。
image 输入8位3通道图像。
markers 标记的输入/输出32位单通道图像(图)。它的大小应与image相同。
import cv2 as cv
import numpy as np
def watershed_demo(image):
# 1.如果有噪声,使用边缘保留滤波中的均值迁移去噪
blurred = cv.pyrMeanShiftFiltering(src, 10, 100)
# 2.gray/binary image
gray = cv.cvtColor(blurred, cv.COLOR_RGB2GRAY)
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
cv.imshow("binary-image", binary)
# 3.morphology operation
kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
# 去除一些小的干扰块
mb = cv.morphologyEx(binary, cv.MORPH_CLOSE, kernel, iterations=2)
# 膨胀
sure_bg = cv.dilate(mb, kernel, iterations=3)
cv.imshow("mor-opt", sure_bg)
# 4.distance transform
# 函数cv :: distanceTransform计算每个二进制图像像素到最近的零像素之间的近似或精确距离。对于零图像像素,该距离显然将为零。
dist = cv.distanceTransform(mb, cv.DIST_L2, 3)
# 规范数组的范数或值范围。
dist_output = cv.normalize(dist, 0, 1.0, cv.NORM_MINMAX)
cv.imshow("distance-transform", dist_output*50)
# 5.寻找种子
ret, surface = cv.threshold(dist, dist.max()*0.6, 255, cv.THRESH_BINARY)
cv.imshow("surface-bin", surface)
# 6.生成markers
surface_fg = np.uint8(surface)
unkown = cv.subtract(sure_bg, surface_fg)
# 计算布尔图像的连接组件标记图像
ret, markers = cv.connectedComponents(surface_fg)
print(ret)
# 7.watershed transform
# 下方该行代码为何这样写,暂时未知,日后补充
markers = markers + 1
# 未知区域变成白色
markers[unkown == 255] = 0
# 使用分水岭算法执行基于标记的图像分割。
markers = cv.watershed(image, markers=markers)
# 让边框改成红色
image[markers == -1] = [0, 0, 255]
cv.imshow("result", image)
src = cv.imread("D:/image/coins_001.png")
cv.namedWindow("input image!", cv.WINDOW_AUTOSIZE)
cv.imshow("input image!", src)
watershed_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()
print("Hi,Python!")
结果如下:
三十、人脸检测
准备工作:找到分类器:
方法:安装opencv软件包,或者把此文件放到根目录
1.用pip安装的opencv不带分类器,所以要下载完整版的,可去官网下载安装,分类器位置在
opencvuildetchaarcascadeshaarcascade_frontalface_alt_tree.xml
官网地址点这里
2.或者直接下载此文件把它放到根目录就行:下载地址
我这里只是下载了haarcascade_frontalface_alt_tree.xml一个人脸识别的文件,其实还有很多,如果有兴趣,可以到官网下载完整的。
detectMultiScale()
void cv::CascadeClassifier::detectMultiScale ( InputArray image,
std::vector< Rect > & objects,
double scaleFactor = 1.1,
int minNeighbors = 3,
int flags = 0,
Size minSize = Size(),
Size maxSize = Size()
)
Python:
1.objects = cv.CascadeClassifier.detectMultiScale( image[, scaleFactor[, minNeighbors[, flags[, minSize[, maxSize]]]]] )
2.objects, numDetections = cv.CascadeClassifier.detectMultiScale2( image[, scaleFactor[, minNeighbors[, flags[, minSize[, maxSize]]]]] )
3.objects, rejectLevels, levelWeights = cv.CascadeClassifier.detectMultiScale3( image[, scaleFactor[, minNeighbors[, flags[, minSize[, maxSize[, outputRejectLevels]]]]]] )
本文使用第一个
image CV_8U类型的矩阵,其中包含在其中检测到对象的图像。
objects 矩形向量,其中每个矩形都包含检测到的对象,这些矩形可能部分位于原始图像的外部。
scaleFactor 该参数指定每个图像比例缩小多少图像尺寸。
minNeighbors 该参数指定每个候选矩形必须保留多少个邻居。(通俗说也就是会同时选取多个检测到的图像,进行对比,如果差不多才可以认为检测成功)
flags 与旧级联的含义相同的参数,与函数cvHaarDetectObjects中的含义相同。它不用于新的级联。
最小尺寸 最小可能的对象大小。小于此值的对象将被忽略。
maxSize 最大可能的对象大小。大于此值的对象将被忽略。如果maxSize == minSize 模型是按单一比例评估的。
import cv2 as cv
import numpy as np
def face_detection_demo(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
# 级联检测器,路径不要出现中文
face_detector = cv.CascadeClassifier("D:/face_detection/haarcascade_frontalface_alt_tree.xml")
faces = face_detector.detectMultiScale(gray, 1.02, 5)
for x, y, w, h in faces:
cv.rectangle(image, (x, y), (x+w, y+h), (0, 0, 255), 2)
cv.imshow("face_detection_demo", image)
# src = cv.imread("D:/image/topstar.png")
# cv.namedWindow("input image!", cv.WINDOW_AUTOSIZE)
# cv.imshow("input image!", src)
# 打开视频
capture = cv.VideoCapture(0, cv.CAP_DSHOW)
while True:
ret, frame = capture.read()
frame = cv.flip(frame, 1)
face_detection_demo(frame)
c = cv.waitKey(10)
if c == 27:
break
cv.waitKey(0)
cv.destroyAllWindows()
print("Hi,Python!")
人脸检测如下:
视频检测涉及隐私,不进行展示!
三十一、识别验证码
验证码识别前期准备:
1.安装:tesseract-ocr,直接安装就行,点击这里下载
具体安装和环境配置看这里,一定要配置环境
可以在终端下使用tesseract -v查看是否安装并配置成功,如果出现版本那么安装成功。
2.python安装:(这两个都要安装)
pip install pytesseract
pip install PILLOW
3.第三个需要注意的是,如果出现下方第一个图片报错情况,那么可以在pycharm输出中出现第二张图片,我们需要打开这个pytesseract.py文件,在文件中使用ctrl+F下输入tesseract_cmd = 找到该行代码,等号后改成第三张图片格式,前面是自己安装的路径,看个人情况而定。
代码如下:
import cv2 as cv
import numpy as np
from PIL import Image
import pytesseract as tess
def recognize_text(image):
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)
# 下面四行代码用来处理干扰,要视情况而定
kernel = cv.getStructuringElement(cv.MORPH_RECT, (1, 8))
bin1 = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 1))
open_out = cv.morphologyEx(bin1, cv.MORPH_OPEN, kernel)
cv.imshow("binary_image", open_out)
# 背景变为白色
open_out = cv.bitwise_not(open_out)
cv.imshow("binary_white_image", open_out)
# 在PIL中,array转换为image,使用image = Image.fromarray(np.uint8(img))
# image转换成array,使用array = np.asarray(image) 或 array = np.array(image)
text_image = Image.fromarray(open_out)
# 将image解析为string类型进行输出
text = tess.image_to_string(text_image)
print("recognize_answer: %s" % text)
src = cv.imread("D:/image/yzm2.png")
cv.namedWindow("input image!", cv.WINDOW_AUTOSIZE)
cv.imshow("input image!", src)
recognize_text(src)
cv.waitKey(0)
cv.destroyAllWindows()
print("Hi,Python!")
结果如下: