图像的获取
-
.VideoCapture() 获取视频流 cap
-
.isOpened() 返回是否打开
-
.read() 读取一张照片,一帧一帧读,而不是抽帧读
-
.get(propId) 获取视频特征, .set(propId, value) 修改值
import numpy as np import cv2 as cv cap = cv.VideoCapture(0) while(True): # 一帧一帧捕捉 ret, frame = cap.read() # 我们对帧的操作在这里 gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY) # 显示返回的每帧 cv.imshow('frame',gray) if cv.waitKey(1) & 0xFF == ord('q'): break # 当所有事完成,释放 VideoCapture 对象 cap.release() cv.destroyAllWindows()
-
-
.imread() 读取图片
- .line()
- .circle()
- .rectangle()
- .ellipse() 画椭圆
- .putText() 写中文的话要改调用 PIL包
- 上面的这些函数,你能看到一些相同的参数:
- img:你想画的图片
- color:形状的颜色,如 BGR,它是一个元组,例如:蓝色(255,0,0)。对于灰度图,只需传一个标量值。
- thickness: 线或圆等的厚度。如果传 -1 就是像圆的闭合图形,它将填充形状。_ 默认 thickness = 1_
- lineType:线条类型,如 8 连接,抗锯齿线等。默认情况下,它是 8 连接。cv.LINE_AA 画出抗锯齿线,非常好看的曲线。
-
import numpy as np import cv2 as cv # 鼠标回调函数 def draw_circle(event,x,y,flags,param): if event == cv.EVENT_LBUTTONDBLCLK: cv.circle(img,(x,y),100,(255,0,0),-1) # 创建一个黑色图像,一个窗口,然后和回调绑定 img = np.zeros((512,512,3), np.uint8) cv.namedWindow('image') cv.setMouseCallback('image',draw_circle) while(1): cv.imshow('image',img) if cv.waitKey(20) & 0xFF == 27: break cv.destroyAllWindows() -
其中 cv2.EVENT_LBUTTONDOWN 等属性可以判断一直按下等操作
-
Numpy 是一个用于快速阵列计算的优化库。因此,简单地访问每个像素值并对其进行修改将非常缓慢,并不鼓励这样做。
-
img.dtype 获取图像数据类型
-
合并与拆分
-
>>>b,g,r = cv.spilt(img) #拆分,速度慢 >>>img = cv.merge((b,g,r))#合并,一般BGR
-
-
制作边界
-
.copyMakeBorder()
-
src-输入的图像
-
top,bottom,left,right-上下左右四个方向上的边界拓宽的值
-
borderType-定义要添加的边框类型的标志。它可以是以下类型:
- cv.BORDER_CONSTANT- 添加一个恒定的彩色边框。该值应作为下一个参数value给出。
- cv.BORDER_REFLECT-边框将是边框元素的镜像反射,如下所示:fedcba|abcdefgh|hgfedcb
- cv.BORDER_REFLECT_101或者 cv.BORDER_DEFAULT-与上面相同,但略有改动,如下所示: gfedcb | abcdefgh | gfedcba
- cv.BORDER_REPLICATE -最后一个元素被复制,如下所示: aaaaaa | abcdefgh | hhhhhhh
- cv.BORDER_WRAP-不好解释,它看起来像这样: cdefgh | abcdefgh | abcdefg
-
value- 如果边框类型为cv.BORDER_CONSTANT,则这个值即为要设置的边框颜色
-
-
-
图像加法
-
找不同:
>>> x = np.uint8([250]) >>> y = np.uint8([10]) >>> print(cv.add(x,y)) #250 + 10 =260 => 255 [[255]] >>> print(x + y) [4]
-
addWeighted(img1,0.7,img2,0.3,0) 权重相加
-
-
img1 = cv.imread('messi5.jpg') e1 = cv.getTickCount()#返回执行程序此时时钟数 for i in xrange(5,49,2): img1 = cv.medianBlur(img1,i) e2 = cv.getTickCount()#返回执行程序此时时钟数 t = (e2 - e1)/cv.getTickFrequency()#除每秒时钟数 print( t ) # 得到的结果是 0.521107655 秒 -
OpenCV自带默认优化,可以使用 cv.useOptimized() 来检查它是否已启用/禁用,并使用 cv.setUseOptimized() 来启用/禁用它
-
.cvtColor(input_image, flag) 更改颜色空间
- flag 为 .COLOR_ 开头
-
一般更改完颜色空间后使用 .inRange() 返回指定范围内颜色
- 图像大小变换
-
.getAffineTransform() 仿射变换(Affine Transformation)是指在向量空间中进行一次线性变换(乘以一个矩阵)和一次平移(加上一个向量),变换到另一个向量空间的过程。变换矩阵存为 np.float32 类型的 numpy 数组
-
.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) → dst 执行仿射变换
- src - 输入图像。
M - 变换矩阵。
dsize - 输出图像的大小。
flags - 插值方法的组合(int 类型!)
borderMode - 边界像素模式(int 类型!)
borderValue - (重点!)边界填充值; 默认情况下,它为0
- src - 输入图像。
-
.GetPerspectiveTransform(src, dst) 透视变换(Perspective Transformation)是将成像投影到一个新的视平面(Viewing Plane),也称作投影映射(Projective Mapping)。
- src:源图像中待测矩形的四点坐标
- sdt:目标图像中矩形的四点坐标
-
cv2.warpPerspective(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) → dst#获取变换矩阵
参数为:
- src:输入图像
- M:变换矩阵
- dsize:目标图像shape
- flags:插值方式,interpolation方法INTER_LINEAR或INTER_NEAREST
- borderMode:边界补偿方式,BORDER_ConSTANT or BORDER_REPLICATE
- borderValue:边界补偿大小,常值,默认为0
-
-
图像阈值
-
retVal, thresh = cv2.threshold(需要处理的图像,阈值,分配的值,阈值处理模式选择)
-
参数1,需要处理的图像:注意,需要处理的图像需要被转成灰度图像(后面小试一下部分会讲解)
参数2,阈值:设定阈值,分界值
参数3,分配的值:如果一个像素的灰度值,大于或者小于(取决于参数4的选择)阈值,会被赋予分配的值
参数4,
- cv2.THRESH_BINARY
- cv2.THRESH_BINARY_INV
- cv2.THRESH_TRUNC
- cv2.THRESH_TOZERO
- cv2.THRESH_TOZERO_INV
-
def adaptiveThreshold(src,maxValue,adaptiveMethod,thresholdType,blockSize,C,dst=None)
-
maxval:Double类型的,阈值的最大值
adaptiveMethod:Int类型的,这里有两种选择
1 —— ADAPTIVE_THRESH_MEAN_C(通过平均的方法取得平均值)
2 —— ADAPTIVE_THRESH_GAUSSIAN_C(通过高斯取得高斯值)
不过这两种方法最后得到的结果要减掉参数里面的C值 -
thresholdType:Int类型的,方法如下:
THRESH_BINARY 二进制阈值化 -> 大于阈值为1 小于阈值为0
THRESH_BINARY_INV 反二进制阈值化 -> 大于阈值为0 小于阈值为1
THRESH_TRUNC 截断阈值化 -> 大于阈值为阈值,小于阈值不变
THRESH_TOZERO 阈值化为0 -> 大于阈值的不变,小于阈值的全为0
THRESH_TOZERO_INV 反阈值化为0 -> 大于阈值为0,小于阈值不变
blockSize:Int类型的,这个值来决定像素的邻域块有多大。
注意:这里的blockSize的值要为奇数,否则会给出这样的提示:
Assertion failed (blockSize % 2 == 1 && blockSize > 1) in cv::adaptiveThreshold
C:偏移值调整量,计算adaptiveMethod用到的参数。
实验中,我阈值的type选择第一种,来演示这两种方式得出的结果
当blockSize的值比较小的时候,两种方法得到的结果的差异不是很大当blockSize的值比较大的时候,就会发现,平均的这种会将整体的轮廓加深的程度大于高斯
直接看图:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K6gQJd6p-1633572209561)(https://opencv.apachecn.org/docs/4.0.0/img/Image_Thresholding_2.png)]我图片上虽然显示的blockSize为10,不过我在程序里面做了判断,当为偶数的情况,就让它+1变成奇数。
-
-
-
Otus二值化
-
import cv2 as cv import numpy as np from matplotlib import pyplot as plt img = cv.imread('noisy2.png',0) # 全局阈值 ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY) # Otsu 阈值 ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU) # 经过高斯滤波的 Otsu 阈值 blur = cv.GaussianBlur(img,(5,5),0) ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU) # 画出所有的图像和他们的直方图 images = [img, 0, th1, img, 0, th2, blur, 0, th3] titles = ['Original Noisy Image','Histogram','Global Thresholding (v=127)', 'Original Noisy Image','Histogram',"Otsu's Thresholding", 'Gaussian filtered Image','Histogram',"Otsu's Thresholding"] for i in xrange(3): plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],'gray') plt.title(titles[i*3]), plt.xticks([]), plt.yticks([]) plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256) plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([]) plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],'gray') plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([]) plt.show()
-
-
-
图像模糊
- 均值
- .blur() 默认3*3均值滤波
- .boxFliter() 不使用3*3的滤波
- 高斯
- .GaussianBlur(img,(5,5),0) 正态分布的卷积核,消除高斯噪声
- 中值滤波
- .medianBlur(img,5) 像素点所在5*5范围内中间值替换该像素,可有效降低噪声
- 双边滤波
- .bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]])
- src:输入图像
- d:过滤时周围每个像素领域的直径
- sigmaColor:在color space中过滤sigma。参数越大,临近像素将会在越远的地方mix。
- sigmaSpace:在coordinate space中过滤sigma。参数越大,那些颜色足够相近的的颜色的影响越大。
- 保留边缘的消噪,绝绝子,但是速度慢
- .bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]])
- 均值
-
形态转换了佳人们
- .erode(img,kernel,iterations = 1) 腐蚀操作
- 内核在图像内移动,只有内核所在位置均为1时才会为1,否则均为0
- 有助于消除小的白色噪音(如我们在“颜色空间”一章中所看到的),分离两个连接的对象等
- .dilate(img,kernel,iterations = 1) 膨胀操作
- 如果内核下至少有一个像素为“1”,则像素元素为“1”,它还可用于连接对象的断开部分,往往先腐蚀后膨胀
- .morphologyEx(img, cv.MORPH_OPEN, kernel) 先腐蚀后膨胀
- .morphologyEx(img, cv.MORPH_CLOSE, kernel) 先膨胀后腐蚀
- .morphologyEx(img, cv.MORPH_GRADIENT, kernel) 膨胀和腐蚀间的差值
- .morphologyEx(img, cv.MORPH_TOPHAT, kernel) 原图像和开运算结果的差值
- .morphologyEx(img, cv.MORPH_BLACKHAT, kernel) 原图像和闭运算结果差值
- 使用 .getStructuringElement(cv2.MORPH_CROSS,(5,5)) 获取各个形状的原图的 kernel ,
- 矩形:MORPH_RECT;
- 交叉形:MORPH_CROSS;
- 椭圆形:MORPH_ELLIPSE;
- .erode(img,kernel,iterations = 1) 腐蚀操作
-
隔着整梯度算法是吧
-
Sobel
-
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
- 第一个参数是需要处理的图像;
- 第二个参数是图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度,一般为.CV_**
- dx和dy表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2
-
原理:图像中的边缘区域,像素值会发生“跳跃”,对这些像素求导,在其一阶导数在边缘位置为极值,这就是Sobel算子使用的原理——极值处就是边缘。
-
输出数据类型是 cv.CV_8U或 np.uint8。但这有一个小问题。黑白过渡为正斜率(有正值),而白黑过渡为负斜率(有负值)。所以当你把数据转换成 np.uint8 时,所有的负斜率都变成零。简单来说,你失去了边缘。
如果要检测两条边,更好的选择是将输出数据类型保留为更高的格式,如 cv.CV_16S、cv.CV_64F 等,取其绝对值,然后转换回 cv.CV_8U。下面的代码演示了水平 Sobel滤波器的过程以及结果差异。
-
-
Scharr
-
.Scharr(src, ddepth, dx, dy)
- 参数同上
-
Sobel 算子虽然可以有效的提取图像边缘,但是对图像中较弱的边缘提取效果较差。
这是由于 Sobel 算子在计算相对较小的核的时候,其近似计算导数的精度比较低,例如一个 3 * 3 的 Sobel 算子,在梯度角度接近水平或垂直方向时,其不精确性就非常明显。
因此引入 Scharr 算子。 Scharr 算子是对 Sobel 算子差异性的增强,两者之间的在检测图像边缘的原理和使用方式上相同。
-
-
Laplacian
- dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
- ksize是算子的大小,必须为1、3、5、7。默认为1。
scale是缩放导数的比例常数,默认情况下没有伸缩系数;
delta是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中;
borderType是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。
- ksize是算子的大小,必须为1、3、5、7。默认为1。
- 原理:如果对像素值求二阶导数,会发现边缘处的导数值为0,Laplace函数实现的方法是先用Sobel 算子计算二阶x和y导数,再求和
- dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
-
Canny
-
非常的好用 .Canny() 第一个参数为原图像,第二个和第三个参数分别是我们的 minVal 和 maxVal。第四个参数是 aperture_size。它是用于查找图像渐变的 Sobel 内核的大小。默认情况下,它是 3.最后一个参数是 L2gradient,它指定用于查找梯度幅度的等式。
-
步骤
-
1.高斯平滑去噪
-
2.计算梯度强度及方向(非极大抑制)
- 在每一点上,首先将梯度近似为四个方向,然后将领域中心 x 与沿着其对应的梯度方向的两个像素相比,若中心像素为最大值,则保留,否则中心置0,这样可以抑制非极大值,保留局部梯度最大的点,以得到细化的边缘。
-
3.经过非极大抑制后图像中仍然有很多噪声点。Canny算法中应用了一种叫双阈值的技术。即设定一个阈值上界和阈值下界(opencv中通常由人为指定的),图像中的像素点如果大于阈值上界则认为必然是边界(称为强边界,strong edge),小于阈值下界则认为必然不是边界,两者之间的则认为是候选项(称为弱边界,weak edge),需进行进一步处理。
-
4.和强边界相连的弱边界认为是边界,其他的弱边界则被抑制。
-
-
-
轮廓
- 为了找轮廓一般用二值图像,因此需要先阈值或者Canny
- contours, hierarchy = cv.findContours( image, mode, method[, contours[, hierarchy[, offset]]] )
- 参数1:源图像
- 参数2:轮廓的检索方式一般用 .RETR_TREE ,详细见此处)
- 参数3:一般用 cv.CHAIN_APPROX_SIMPLE,就表示用尽可能少的像素点表示轮廓
- contours:图像轮廓坐标,是一个链表
- hierarchy:[Next, Previous, First Child, Parent],文中有详细解释
-
轮廓操作
-
绘制轮廓 .drawContours(img, contours, -1, (0,255,0), 3)
-
图像的矩
-
图像矩可用来计算某些特征,例如物体的重心,物体的面积等,可以用 .moments(cnt) 得到所有矩
-
可以用图像的矩计算出重心:
Cannot read property 'type' of undefined
-
-
-
轮廓面积 .contourArea()
-
轮廓周长 .arcLength()
-
轮廓近似
-
获取轮廓外围
-
设置精度(从轮廓到近似轮廓的最大距离)
-
获取近似轮廓
-
绘制轮廓
#参照值:决定是否将一段曲线近为直线 epsilon=0.1*cv2.arcLength(cnt,True) #近似轮廓 approx=cv2.approxPolyDP(cnt,epsilon,True) #将近似出来的轮廓画在复制出来的图片上 res=cv2.drawContours(draw_img,[approx],-1,(0,0,255),1)
-
-
凸包
-
hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]])
- points: 就是我们传入的轮廓。
- hull: 是输出,通常我们避免它。
- clockwise:方向标记。如果为True,则输出凸包为顺时针方向。否则,其方向为逆时针方向。
- returnPoints:默认情况下为True。然后返回船体点的坐标。如果为False,则返回与船体点相对应的轮廓点的索引。
-
凸包的定义
- 换句话,对于空间中的对象.如果对象中的任意两个点构成的线段,也包含在对象内,则称对象是凸的。
- 换句话,对于空间中的对象.如果对象中的任意两个点构成的线段,也包含在对象内,则称对象是凸的。
-
凸包外观看起来与轮廓逼近相似,但并非如此(在某些情况下两者可能提供相同的结果)
-
.isContourConvex(cnt) 返回曲线是否是凸多边形
-
-
矩形
-
直角矩形
x,y,w,h = cv.boundingRect(cnt) cv.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
-
旋转矩形
rect = cv.minAreaRect(cnt)#返回一个Box2D结构,其中包含以下细节-(中心(x,y),(宽度,高度),旋转角度) box = cv.boxPoints(rect)#返回矩形的4个角 box = np.int0(box) cv.drawContours(img,[box],0,(0,0,255),2)
-
-
圆
-
(x,y),radius = cv.minEnclosingCircle(cnt) center = (int(x),int(y)) radius = int(radius) cv.circle(img,center,radius,(0,255,0),2)
-
-
拟合线
-
rows,cols = img.shape[:2] [vx,vy,x,y] = cv.fitLine(cnt, cv.DIST_L2,0,0.01,0.01) lefty = int((-x*vy/vx) + y) righty = int(((cols-x)*vy/vx)+y) cv.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
-
-
轮廓属性
-
长宽比:对象边界矩形的宽度与高度的比率
x,y,w,h = cv.boundingRect(cnt) aspect_ratio = float(w)/h
-
范围:轮廓区域与边界矩形区域的比率
area = cv.contourArea(cnt) x,y,w,h = cv.boundingRect(cnt) rect_area = w*h extent = float(area)/rect_area
-
固实性:轮廓面积与其凸包面积的比率
area = cv.contourArea(cnt) hull = cv.convexHull(cnt) hull_area = cv.contourArea(hull) solidity = float(area)/hull_area
-
等效直径是面积与轮廓面积相同的圆的直径
area = cv.contourArea(cnt) equi_diameter = np.sqrt(4*area/np.pi)
-
方向是物体指向的角度。以下方法还给出了主轴和副轴的长度
(x,y),(MA,ma),angle = cv.fitEllipse(cnt)
-
在某些情况下,我们可能需要构成该对象的所有点。可以按照以下步骤完成:
-
mask = np.zeros(imgray.shape,np.uint8) cv.drawContours(mask,[cnt],0,255,-1) pixelpoints = np.transpose(np.nonzero(mask)) # pixelpoints = cv.findNonZero(mask)
-
给出了两种方法,一种使用Numpy函数,另一种使用OpenCV函数(最后注释的行)执行相同的操作。结果也相同,但略有不同。Numpy以(行,列)格式给出坐标,而OpenCV以(x,y)格式给出坐标。因此,基本上答案是可以互换的。注意,row = x,column = y
-
-
最大值、最小值及其位置
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(imgray,mask = mask)
-
平均颜色
- mean_val = cv.mean(im,mask = mask)
-
极点是指对象的最顶部,最底部,最右侧和最左侧的点
-
leftmost = tuple(cnt[cnt[:,:,0].argmin()][0]) rightmost = tuple(cnt[cnt[:,:,0].argmax()][0]) topmost = tuple(cnt[cnt[:,:,1].argmin()][0]) bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])
-
-
凸包缺陷
-
凸包缺陷就是凸包与轮廓间的部分
img = cv2.imread("图片名") gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) ret,binary= cv2.threshold(gray,127,255,cv2.THRESH_BINARY) contours,h= cv2.findContours(binary,cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) #获取凸包 hull= cv2.convexHull(contours[0],returnPoints= False)#一定要写returnPoints= False df= cv2.convexityDefects(contours[0],hull)
-
-
点多边形测试(点与轮廓位置)
-
查找图像中的点与轮廓之间的最短距离。它返回的距离为:当点在轮廓外时为负;当点在轮廓内时为正;如果点在轮廓上,则返回零。
dist = cv.pointPolygonTest(cnt,(50,50),True)
-
-
匹配形状(轮廓)
- .matchShapes() **结果越低,匹配越好。**可以匹配旋转后的轮廓
- 涉及到图像的矩,看不太懂
-
-



