小白的OpenCV学习

小白的OpenCV学习

一.学习环境
  • Python 3.6 + OpenCV-python 3.4.1.15 + OpenCV-contrib-python 3.4.1.15 + numpy + matplotlib + Jupyter Notebook

  • 这里要多说一句,在OpenCV 3.4.2以后的版本有很多模型由于受到专利限制,无法使用,切记!

二.图像的基本操作
图像读取及显示
  • 使用cv2.imread(path)读取图像

  • 注意:path参数必须是全英文的,不能包含中文字符

  • image=cv2.imread(path),返回值image是一个ndarray类型的数组,并且是以BGR的通道顺序读进来的图像(如果不好记,麻烦想一下BGM…)

    • 读进来的图像是通道放在末尾的形式(e.g: image.shape=(xxx,xxx,3),其中3表示BGR通道)
    • image=cv2.imread(path,cv2.IMREAD_GRAYSCALE),后面的参数表示以灰度图的方式读取图像,此时image.shape=(xxx,xxx)
  • cv2.imshow(‘name’,image)用来显示图像,两个参数分别是图像的名称,和图像本身

    需要用cv2.waitKey(0) + cv2.destroyAllWindows()来将显示的图像hold住,不然图像闪一下就过了,通常情况下,会定义一个小函数来帮助显示图像

    import cv2
    
    def cv_show(name,image):
        cv2.imshow(name,image)
        cv2.waitKey()
        cv2.destroyAllWindows()
    
    cv_show(name,image)    
    
  • cv2.imwrite(path,image) 用来保存图像

读取视频
  • cv2.VideoCapture可以捕捉摄像头,用不同的数字来控制不同的设备,如果是视频文件,直接指定文件路径即可
  • vc=cv2.VideoCapture(path)
  • 读取视频的原理就是一帧一帧的读取每张图片
vc=cv2.VideoCapture(path)

# 这里需要检查是否正常打开视频
if vc.isOpen():
    open,frame=vc.read()
else:
    open=False
    
while open:
    ret,frame=vc.read()
    # 表示视频读完了
    if frame is None:
        break
    if ret==True:
        # 将图像转换为灰度图
        gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
        cv2.imshow('result',gray)
        # 这里waitKey的参数表示视频的播放时间,注意!不是视频本身的时间,而是你指定多久,就播放多久,以秒为单位,oxff==27就是键盘上的退出键(esc)
        if cv2.waitKey(10) & 0xff==27:
            break
vc.release()
cv2.destroyAllWindows()
截取部分图像数据(ROI region of interest)

image=cv2.imread(path)

  • 图像大小提取

    cat=image[0:300,0:200]

    cv_show(‘cat’,cat)

    由于图像的本质是三维矩阵(彩色图像),分别是[h,w,c](高,宽,通道),那么就可以像数组一样,采用切片的方式获取任意想得到的地方

  • 颜色通道提取

    • b,g,r=cv2.split(image)

      提取到的b,g,r都是一个二维矩阵,大小和图像的shape一样

    • image=cv2.merge((b,g,r))

      重新将b,g,r合并,需要注意的是,merge参数是一个元组,合并后的图像就又是三维数组了

    • cur_image=image.copy()

      cur_image[:,:,0]=0 根据b,g,r的排序,将b通道设置为0

      cur_image[:,:,1]=0 同理,将g通道设置为0,这样就只留下了r通道

边界填充
# 指定上,下,左,右分别填充的大小
top_size,bottom_size,left_size,right_size=(50,50,50,50)

# 复制法:直接复制最边缘像素
replicate=cv2.copyMakeBorder(img,top_size,bottom_size,left_size,right_size,borderType=cv2.BORDER_REPLICATE)

# 反射法:对感兴趣的图像中的像素在两边进行复制
reflect=cv2.copyMakeBorder(img,top_size,bottom_size,left_size,right_size,cv2.BORDER_REFLECT)

# 反射法:也就是以最边缘像素为轴,对称
reflect101=cv2.copyMakeBorder(img,top_size,bottom_size,left_size,right_size,cv2.BORDER_REFLECT_101)

# 外包装法:
wrap=cv2.copyMakeBorder(img,top_size,bottom_size,left_size,right_size,cv2.BORDER_WRAP)

# 常量法:用常数值进行填充
constant=cv2.copyMakeBorder(img,top_size,bottom_size,left_size,right_size,cv2.BORDER_CONSTANT,value=0)
数值计算
image_cat=cv2.imread(path1)

image_dog=cv2.imread(path2)

image_cat[:5,:,0]
output:
array([[142, 146, 151, ..., 156, 155, 154],
       [107, 112, 117, ..., 155, 154, 153],
       [108, 112, 118, ..., 154, 153, 152],
       [139, 143, 148, ..., 156, 155, 154],
       [153, 158, 163, ..., 160, 159, 158]], dtype=uint8)  

image_cat2=image_cat+10
image_cat2[:5,:,0]
output:
array([[152, 156, 161, ..., 166, 165, 164],
       [117, 122, 127, ..., 165, 164, 163],
       [118, 122, 128, ..., 164, 163, 162],
       [149, 153, 158, ..., 166, 165, 164],
       [163, 168, 173, ..., 170, 169, 168]], dtype=uint8)

(image_cat+image_cat2)[:5,:,0]
output:
array([[ 38,  46,  56, ...,  66,  64,  62],
       [224, 234, 244, ...,  64,  62,  60],
       [226, 234, 246, ...,  62,  60,  58],
       [ 32,  40,  50, ...,  66,  64,  62],
       [ 60,  70,  80, ...,  74,  72,  70]], dtype=uint8)  
# 由于142+152=294(第一行第一列的数字),但是这个数字超过了256,所以用294%256=38
-------------------------------------------------------------------------
cv2.add(image_cat,image_cat2)[:5,:,0]
output:
array([[255, 255, 255, ..., 255, 255, 255],
       [224, 234, 244, ..., 255, 255, 255],
       [226, 234, 246, ..., 255, 255, 255],
       [255, 255, 255, ..., 255, 255, 255],
       [255, 255, 255, ..., 255, 255, 255]], dtype=uint8)
# 在越界的情况下,就直接取最大值255,没越界就取自身
图像融合
  • 图像融合的前提是两张图片大小相同
image_dog=cv2.resize(image_dog,填入猫图像的shape)

# 猫占40%,狗占60%
res=cv2.addWeighted(image_cat,0.4,image_dog,0.6,0)
cv_show('res',res)
  • R = α x 1 + β x 2 + b R=\alpha{x_1}+\beta{x_2}+b R=αx1+βx2+b α , β , b \alpha,\beta,b α,β,b 分别代表两张图像的占比和提亮系数
    在这里插入图片描述
三.图像阈值 & 图像平滑
图像阈值

ret,dst=cv2.threshold(src,thresh,maxval,type)

  • src:输入图,只能输入单通道图像,通常是灰度图

  • dst:输出图

  • thresh:阈值

  • maxval:当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值

  • type:二值化操作的类型,包含以下5种类型:cv2.THRESH_BINARY;cv2.THRESH_BINARY_INV,cv2.THRESH_TRUNC,cv2.THRESH_TOZERO,

    cv2.THRESH_TOZERO_INV

    • cv2.THRESH_BINARY:超过阈值部分取maxval(最大值),否则取0
    • cv2.THRESH_BINARY_INV:THRESH_BINARY的反转
    • cv2.THRESH_TRUNC:大于阈值部分设为阈值,否则不变
    • cv2.THRESH_TOZERO:大于阈值部分设为阈值,否则为0
    • cv2.THRESH_TOZERO_INV:THRESH_TOZERO的反转
img_original=cv2.imread(path)
img=cv2.imread(path,cv2.IMREAD_GRAYSCALE)

ret,thresh1=cv2.threshold(img,127,255,cv2.THRESH_BINARY)
ret,thresh2=cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3=cv2.threshold(img,127,255,cv2.THRESH_TRUNC)
ret,thresh4=cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
ret,thresh5=cv2.threshold(img,127,255,cv2.THRESH_TOZERO_INV)

titles=['original','binary','binary_inv','trunc','to_zero','to_zero_inv']
images=[img_original,thresh1,thresh2,thresh3,thresh4,thresh5]

for i in range(6):
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])

在这里插入图片描述

图像平滑
  • 分别展示均值滤波,方框滤波,高斯滤波,中间值滤波
    • 均值滤波:平均卷积操作
    • 方框滤波:和均值滤波基本相同,但是可以选择归一化
    • 高斯滤波:高斯模糊的卷积核里的数值满足高斯分布,相当于更重视中间值
    • 中间值滤波:用中间值替代(注意:使用中间值(位置在中间)而不是均值)
  • 图像顺序(原图,均值,方框1,方框2,高斯,中间值)在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四.形态学操作(腐蚀操作,膨胀操作,开/闭运算,梯度运算,礼帽/黑帽)
腐蚀操作
  • 腐蚀操作一般都在二值图像上进行
image=cv2.imread(path)

kernel=np.ones((3,3),np.uint8)
# erode参数:
# image:需要操作的图像
# kernel:卷积核大小
# iterations:迭代次数,次数越多,腐蚀越狠
erosion=cv2.erode(image,kernel,iterations=1)

在这里插入图片描述
在这里插入图片描述

膨胀操作
  • 膨胀操作和腐蚀操作互为逆操作
image=cv2.imread(path)

kernel=np.ones((3,3),uint8)
# 先腐蚀
erosion=cv2.erode(image,kernel,iterations=1)

# 再膨胀
dilation=cv2.dilate(erosion,kernel,iterations=1)

在这里插入图片描述
在这里插入图片描述

  • 可以看到,图片变“胖了”
开运算与闭运算
  • 将腐蚀与膨胀操作结合起来
  • 开(下图左):先腐蚀,后膨胀
  • 闭(下图右):先膨胀,后腐蚀
image=cv2.imread(path)
kernel=np.ones((3,3),uint8)

opening=cv2.morphologyEx(image,cv2.MORPH_OPEN,kernel)
--------------------------------------------------------------
image=cv2.imread(path)

closing=cv2.morphologyEx(image,cv2.MORPH_CLOSE,kernel)

在这里插入图片描述
在这里插入图片描述

  • 可以看到,经过闭运算后,“毛刺”仍然存在!
梯度运算
  • 梯度=膨胀-腐蚀
  • 可以猜到,图片经过膨胀再减去腐蚀后留下的应该是图片的轮廓,下图左是图片原图,右是经过梯度运算的结果
image=cv2.imread(path)
kernel=np.ones((3,3),uint8)

gradient=cv2.morphologyEx(image,cv2.MORPH_GRADIENT,kernel)

在这里插入图片描述
在这里插入图片描述

礼帽与黑帽
  • 礼帽:原始输入-开运算

  • 黑帽:闭运算-原始输入

  • 可以猜到,如果原始图像中有一些“毛刺”,那么礼帽运算会将其留下来,

    而经过黑帽以后,会留下图像的大致轮廓

  • 下图中,左是礼帽运算,右是黑帽运算

image=cv2.imread(path)
kernel=np.ones((3,3),uint8)

tophat=cv2.morphologyEx(image,cv2.MORPH_TOPHAT,kernel)
--------------------------------------------------------------------
balckhat=cv2.morphologyEx(image,cv2.MORPH_BLACKHAT,kernel)

礼帽运算
在这里插入图片描述

五.图像梯度处理
Sobel算子
  • G x = [ − 1 0 + 1 − 2 0 + 2 − 1 0 + 1 ] ∗ A G_x=\left[ \begin{matrix} -1 & 0 & +1\\ -2 & 0 & +2\\ -1 & 0 & +1 \end{matrix} \right] *A Gx=121000+1+2+1A G y = [ − 1 − 2 − 1 0 0 0 + 1 + 2 + 1 ] ∗ A G_y=\left[ \begin{matrix} -1 & -2 & -1\\ 0 & 0 & 0\\ +1 & +2 & +1 \end{matrix} \right] *A Gy=10+120+210+1A

  • dst=cv2.Sobel(src,ddepth,dx,dy,ksize)

    • ddepth:图像的深度,通常情况下为-1
    • dx,dy:分别表示水平和竖直方向
    • ksize是Sobel算子的大小
  • 原图像

在这里插入图片描述

  • 对x方向而言,整个Sobel计算是从左到右的,这是一个二值图像,从左向右,从白到黑就是正数,从黑到白就是负数,所有的负数都会被截断成0
image=cv2.imread(path)

# cv2.CV_64F (一般是默认参数)
# 1,0:表示x方向
sobelx=cv2.Sobel(image,cv2.CV_64F,1,0,ksize=3)

在这里插入图片描述

  • 可以看到,图像只有左半边的轮廓,因为右半边为负数,被自动截断成了0,所以需要给结果取绝对值!!!
sobelx=cv2.Sobel(image,cv2.CV_64F,1,0,ksize=3)
sobelx=cv2.convertScaleAbs(sobelx)

在这里插入图片描述

  • 一般求图片的轮廓是分别计算x,y方向的轮廓,再将他们组合起来,而不是一起求,因为一起求会有重影!!!
sobely=cv2.Sobel(image,cv2.CV_64F,0,1,ksize=3)
sobely=cv2.convertScaleAbs(sobely)

sobelxy=cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
  • 下图左是sobely的图像,右是sobelxy的图像
    在这里插入图片描述

在这里插入图片描述

  • 再来看一下美丽的lena小姐姐经过梯度处理后的样子(左边是原图,右边是梯度处理后的图),可以看到,图像的轮廓被清晰的提取出来了
    在这里插入图片描述
    在这里插入图片描述
Scharr算子和laplacian算子
  • Scharr算子

G x = [ − 3 0 3 − 10 0 10 − 3 0 + 3 ] ∗ A G_x=\left[ \begin{matrix} -3 & 0 & 3\\ -10 & 0 & 10\\ -3 & 0 & +3 \end{matrix} \right] *A Gx=3103000310+3A G y = [ − 3 − 10 − 3 0 0 0 − 3 − 10 − 3 ] ∗ A G_y=\left[ \begin{matrix} -3 & -10 & -3\\ 0 & 0 & 0\\ -3 & -10 & -3 \end{matrix} \right] *A Gy=30310010303A

  • laplacian算子

G = [ 0 1 0 1 − 4 1 0 1 0 ] G=\left[ \begin{matrix} 0 & 1 & 0\\ 1 & -4 & 1\\ 0 & 1 & 0 \end{matrix} \right] G=010141010

  • 来比较一下不同算子的差异(从左到右依次是sobel,scharr,laplacian)
img=cv2.imread(path,cv2.IMREAD_GRAYSCALE)
sobelx=cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobelx=cv2.convertScaleAbs(sobelx)
sobely=cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobely=cv2.convertScaleAbs(sobely)
sobelxy=cv2.addWeighted(sobelx,0.5,sobely,0.5,0)

scharrx=cv2.Scharr(img,cv2.CV_64F,1,0)
scharrx=cv2.convertScaleAbs(scharrx)
scharry=cv2.Scharr(img,cv2.CV_64F,0,1)
scharry=cv2.convertScaleAbs(scharry)
scharrxy=cv2.addWeighted(scharrx,0.5,scharry,0.5,0)

laplacian=cv2.Laplacian(img,cv2.CV_64F)
laplacian=cv2.convertScaleAbs(laplacian)

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

六.边缘检测算法
canny边缘检测
  • 1)使用高斯滤波器,以平滑图像,消除噪声
  • 2)计算图像中每个像素点的梯度和方向
  • 3)应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应
  • 4)应用双阈值(Double-Threshold)检测来确定真实和潜在的边缘
  • 5)通过抑制孤立的弱边缘最终完成边缘检测
高斯滤波器

H = [ 0.0924 0.1192 0.0924 0.1192 0.1538 0.1192 0.0924 0.1192 0.0924 ] H=\left[ \begin{matrix} 0.0924 & 0.1192 & 0.0924\\ 0.1192 & 0.1538 & 0.1192\\ 0.0924 & 0.1192 & 0.0924 \end{matrix} \right] H=0.09240.11920.09240.11920.15380.11920.09240.11920.0924 <–这里进行了归一化处理

e = H ∗ A = [ h 1 1 h 1 2 h 1 2 h 2 1 h 2 2 h 2 3 h 3 1 h 3 2 h 3 3 ] ∗ [ a b c d e f g h i ] = s u m { [ a ∗ h 1 1 b ∗ h 1 2 c ∗ h 1 3 d ∗ h 2 1 e ∗ h 2 2 f ∗ h 2 3 g ∗ h 3 1 h ∗ h 3 2 i ∗ h 3 3 ] } e=H*A=\left[ \begin{matrix} h_11 & h_12 & h_12\\ h_21 & h_22 & h_23\\ h_31 & h_32 & h_33 \end{matrix} \right]*\left[ \begin{matrix} a & b & c\\ d & e & f\\ g & h & i \end{matrix} \right]= sum\left\{\left[ \begin{matrix} a*h_11 & b*h_12 & c*h_13\\ d*h_21 & e*h_22 & f*h_23\\ g*h_31 & h*h_32 & i*h_33 \end{matrix} \right]\right\} e=HA=h11h21h31h12h22h32h12h23h33adgbehcfi=sumah11dh21gh31bh12eh22hh32ch13fh23ih33

梯度和方向

G = G x 2 + G y 2 G=\sqrt{G_x^2+G_y^2} G=Gx2+Gy2 θ = a r c t a n ( G y / G x ) \theta=arctan({G_y}/{G_x}) θ=arctan(Gy/Gx)

S x = [ − 1 0 1 − 2 0 2 − 1 0 1 ] S_x=\left[\begin{matrix} -1 & 0 & 1\\ -2 & 0 & 2\\ -1 & 0 & 1 \end{matrix}\right] Sx=121000121 S y = [ 1 2 1 0 0 0 − 1 − 2 − 1 ] S_y=\left[\begin{matrix} 1 & 2 & 1\\ 0 & 0 & 0\\ -1 & -2 & -1 \end{matrix}\right] Sy=101202101

G x = S x ∗ A = [ − 1 0 1 − 2 0 2 − 1 0 1 ] ∗ [ a b c d e f g h i ] = s u m { [ − a 0 c − 2 d 0 2 f − g 0 i ] } G_x=S_x*A=\left[\begin{matrix} -1 & 0 & 1\\ -2 & 0 & 2\\ -1 & 0 & 1 \end{matrix}\right] *\left[\begin{matrix} a & b & c\\ d & e & f\\ g & h & i \end{matrix}\right]=sum\left\{\left[\begin{matrix} -a & 0 & c\\ -2d & 0 & 2f\\ -g & 0 & i \end{matrix}\right]\right\} Gx=SxA=121000121adgbehcfi=suma2dg000c2fi

双阈值检测
  • 梯度值>maxVal:则处理为外便捷
  • minVal<梯度值<maxVal:连有边界则保留,否则舍弃
  • 梯度值<minVal:舍弃
image=cv2.imread(path)

v1=cv2.Canny(image,120,250)
v2=cv2.Canny(image,50,100)
res=np.hstack((v1,v2))

在这里插入图片描述

七.图像金字塔与轮廓检测
高斯金字塔

1 16 [ 1 4 6 4 1 4 16 24 16 4 6 24 36 24 6 4 16 24 16 4 1 4 6 4 1 ] \frac{1}{16}\left[\begin{matrix} 1 & 4 & 6 & 4 & 1\\ 4 & 16 & 24 & 16 & 4\\ 6 & 24 & 36 & 24 & 6\\ 4 & 16 & 24 & 16 & 4\\ 1 & 4 & 6 & 4 & 1 \end{matrix}\right] 1611464141624164624362464162416414641

  • G i G_i Gi与高斯内核卷积
  • 将所有偶数行和列去除
向上采样(放大)

[ 10 30 56 96 ] − − − > [ 10 0 30 0 0 0 0 0 56 0 96 0 0 0 0 0 ] \left[\begin{matrix} 10 & 30\\ 56 & 96 \end{matrix}\right] --->\left[\begin{matrix} 10 & 0 & 30 & 0\\ 0 & 0 & 0 & 0\\ 56 & 0 & 96 & 0\\ 0 & 0 & 0 & 0 \end{matrix}\right] [10563096]>10056000003009600000

  • 1.将图像在每个方向扩大为原来的两倍,新增的行和列用0填充
  • 2.使用先前同样的内核(乘以4)与放大后的图像卷积,获得近似值
  • 先将图像进行上采样,再将图像进行下采样,虽然图像的大小不改变,但是图像糊变得模糊,因为在上采样过程中是用0对图像进行填充,相当于损失了一些信息,而下采样过程中用卷积核卷积,也损失了一些信息(左边是原图,右边是先上采样后下采样的图像)
image=cv2.imread(path)

up=cv2.pyrUp(image)
up_down=cv2.pyrDown(up)

在这里插入图片描述

拉普拉斯金字塔
  • L i = G i − P y r U p ( P y r D o w n ( G i ) ) L_i=G_i-PyrUp(PyrDown(G_i)) Li=GiPyrUp(PyrDown(Gi))

  • 其中: G i G_i Gi是输入图片

  • 1.低通滤波

  • 2.缩小尺寸

  • 3.放大尺寸

  • 4.图像相减

down=cv2.pyrDown(image)
down_up=cv2.pyrUp(down)

l_l=image-down_up

在这里插入图片描述

图像轮廓
  • cv2.findContours(img,mode,method)
    • mode:轮廓检索模式
      • RETR_EXTERNAL:只检索最外面的轮廓
      • RETR_LIST:检索所有的轮廓,并将其保存到一条链表中
      • RETR_CCOMP:检索所有的轮廓,并将他们组织为两层,等曾是各部分的外部边界,第二层是空洞的边界
      • RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次**(用的最多的)**
    • method:轮廓逼近方法
      • CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)
      • CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分
    • return:binary,contours,hierarchy,分别是(二值化图像,轮廓信息,层级)
  • 为了更高的准确率,使用二值图像
image=cv2.imread(path)
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)

ret,thresh=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)

binary,contours,hierarchy=cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
  • 绘制轮廓
    • 传入绘制图像,轮廓,轮廓索引,颜色模式,线条厚度
    • 注意:需要copy原图,不然原图会发生改变
draw_image=image.copy()

# drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]]) -> image
   # @brief Draws contours outlines or filled contours.
# draw_image:需要绘制的图像
# contours:轮廓,是findContours函数的第二个返回值
# -1:表示默认画出所有轮廓线
#(0,0,255):表示用红色的线画出轮廓
# 2:线宽
res=cv2.drawContours(draw_image,contours,-1,(0,0,255),2)
  • 下图左是图像原图,右是轮廓图,注意:轮廓图中,每个图像的内外都被画上了轮廓,因为给定的参数是-1,至于为什么边框是蓝色的…因为为了方便直接保存图像,我用的plt.imshow()函数显示的图像,他的色彩通道是r,g,b…
    在这里插入图片描述
    在这里插入图片描述
  • 再来看一下如果参数-1换成0会如何,可以看到,只有一个图像的外部被添加上了轮廓
    在这里插入图片描述
轮廓特征
image=cv2.imread(path)
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)

ret,thresh=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)

binary,contours,hierarchy=cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)

# 拿到第0个轮廓
cnt=contours[0]

# 计算面积
ares=cv2.contoursArea(cnt)

# 计算周长 True表示闭合的
girth=cv2.arcLength(cnt,True)
轮廓近似
img=cv2.imread(path)

gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
binary,contours,hierarchy=cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)

cnt=contours[0]

draw_img=img.copy()
res=cv2.drawContours(draw_img,[cnt],-1,(0,0,255),2)

epsilon=0.1*cv2.arcLength(cnt,True)  # 0.1那个值,越小,就越贴合轮廓
approx=cv2.approxPolyDP(cnt,epsilon,True)

draw_img=img.copy()
res=cv2.drawContours(draw_img,[approx],-1,(0,0,255),2)
  • 左边是原轮廓图,右边是轮廓近似图

在这里插入图片描述 在这里插入图片描述

边界矩形(重要!!)
image=cv2.imread('./data/Image manipulation/contours.png')

gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
ret,thresh=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
binary,contours,hierarchy=cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt=contours[0]

# 传入轮廓,然后计算边界矩形的x,y,w,h分别对应(x,y)坐标点,以及矩形的宽,高
x,y,w,h=cv2.boundingRect(cnt)

# The function cv::rectangle draws a rectangle outline or a filled rectangle whose two opposite corners,通过两个对角点来绘制一个边界矩形
# 绘制出这个矩形,需要参数:
# image:原图像
# (x,y):对应的起始坐标点
# (x+w,y+h):对角坐标点
# (0,255,0),2:绿线,线宽为2
image=cv2.rectangle(image,(x,y),(x+w,y+h),(0,255,0),2)

在这里插入图片描述

  • 计算轮廓面积与边界矩形之比
area=cv2.contourArea(cnt)
x,y,w,h=cv2.boundingRect(cnt)
rect_area=w*h
extent=float(area)/rect_area
print('轮廓面积与边界矩形比:',extent)
  • 外接圆(和外接矩形同理)
# 拿到圆心,拿到半径就行了
(x,y),radius=cv2.minEnclosingCircle(cnt)
center=(int(x),int(y)) 
radius=int(radius)
img=cv2.circle(img,center,radius,(0,255,0),2)
八.直方图与傅里叶变换
直方图

cv2.calcHis(images,channels,mask,histSize,ranges)

  • images:原图像格式为uint8或float32,当传入函数时应使用中括号[image]
  • channels:同样用中括号,它会告诉函数我们整幅图像的直方图,如果图像是灰度图,他的值就是[0],如果是彩色图,他传入的参数可以是[0][1][2],他们分别是BGR
  • mask:掩码图像,统计整幅图像的直方图就将它置为None,但是如果想统计图像某一部分的直方图,就制作一个掩码图像,并使用
  • histSize:BIN的数目,也需要用中括号
  • ranges:像素值范围常为[0-256]
image=cv2.imread(path)

hist=cv2.calHist([image],[0],None,[256],[0,256])
hist.shape
output:
(256, 1) 

plt.hist(img.ravel(),256)
plt.show()

在这里插入图片描述

image=cv2.imread(path)

color=('b','g','r')
for i,col in enumerate(color):
    histr=cv2.calcHist([img],[i],None,[256],[0,256])
    plt.plot(histr,color=col)
    plt.xlim([0,256])

在这里插入图片描述

掩码操作
# 创建mask
mask=np.zeros(image.shape[:2],np.uint8)
mask[100:300,100:400]=255
# 上面两步操作,第一步是创建一个全零二维数组,大小和图像大小一样(其实就是一块和图像大小相同的黑色图像),然后将中间一块地方变成255(也就是变成白色)

masked_image=cv2.bitwise_and(image,image,mask=mask)  # 与操作(&)

在这里插入图片描述

hist_full=cv2.calcHist([image],[0],None,[256],[0,256])
hist_mask=cv2.calcHist([image],[0],mask,[256],[0,256])

plt.subplot(221),plt.imshow(image,'gray')
plt.subplot(222),plt.imshow(mask,'gray')
plt.subplot(223),plt.imshow(masked_image,'gray')
plt.subplot(224),plt.plot(hist_full),plt.plot(hist_mask)
plt.xlim([0,255])
plt.show()

在这里插入图片描述

直方图均值化
  • 可以观察到,在上面的直方图中,存在“陡峭”的现象,直方图均值化可以使直方图变得平均
img=cv2.imread(path,0)  # 这里是以灰度图的方式读进图像
plt.hist(img.ravel(),256)
plt.show()

equ=cv2.equalizeHist(img)
plt.hist(equ.ravel(),256)
plt.show()

res=np.hstack((img,equ))
cv_show('res',res)

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

  • 可以看到,经过直方图均值化后,图像的整体亮度变高,但是,会丢失掉一些图像的细节部分!
自适应直方图均值化
  • 自适应直方图均值化是将图片分成几个部分,每个部分别均值化,然后在整合到一起,这样就避免了图像细节部分的丢失
clahe=cv2.createCLAHE(clipLimit=2.0,tileGridSize=(8,8))
res_clahe=clahe.apply(img)
res=np.hstack((img,equ,res_clahe))

在这里插入图片描述

傅里叶变换(我也没搞明白)
  • 高频:变化剧烈的灰度分量,例如边界
  • 低频:变化缓慢的灰度分量,例如大海
img=cv2.imread(path,0)
img_float32=np.float32(img)
dft=cv2.dft(img_float32,flags=cv2.DFT_COMPLEX_OUTPUT)
dft_shift=np.fft.fftshift(dft)
# 得到灰度图像能表示的形式
magnitude_spectrum=20*np.log(cv2.magnitude(dft_shift[:,:,0],dft_shift[:,:,1]))

plt.subplot(121),plt.imshow(img,cmap='gray')
plt.title('input image'),plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(magnitude_spectrum,cmap='gray')
plt.title('Magitude Spectrum'),plt.xticks([]),plt.yticks([])
plt.show()
滤波
  • 低通滤波器:只保留低频,会使得图像模糊
  • 高通滤波器:只保留高频,会使得图像细节增强
    • OpenCV中主要就是cv2.dft()和cv2.idft(),输入图像需要需要先转换成np.float32格式
    • 得到的结果中频率为0的部分会在左上角,通常需要转换到中心位置,可以通过shift变换来实现
    • cv2.dft()返回的结果是双通道的(实部,虚部),通常还需要转换成图像格式才能展示(0,255)
九.模板匹配
匹配单个对象
  • 模板匹配和卷积原理相像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在openCV里有6中,然后将每次计算的结果放入一个矩阵里,作为结果输出,假如原图形是AxB大小,模板大小是axb,则输出结果的矩阵是(A-a+1)x(B-b+1)
  • TM_SQDIFF:计算平方不同,计算出来的值越小,越相关
  • TM_CCORR:计算相关性,计算出来的值越大,越相关
  • TM_CCOEFF:计算相关系数,计算出的值越大,越相关
  • TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关
  • TM_CCORR_NORMED:就散归一化相关性,计算出来的值越接近1,越相关
  • TM_CCOEFF_NORMED:计算归一化相关系数,计算出的值越接近1,越相关
img=cv2.imread('./data/Image manipulation/lena.jpg',0)
template=cv2.imread('./data/Image manipulation/face.jpg',0)
img.shape,template.shape
output:
((263, 263), (110, 85))    

methods=['cv2.TM_SQDIFF','cv2.TM_CCOEFF_NORMED','cv2.TM_CCORR','cv2.TM_CCORR_NORMED','cv2.TM_SQDIFF','cv2.TM_SQDIFF_NORMED']

res=cv2.matchTemplate(img,template,1)
min_val,max_val,min_loc,max_loc=cv2.minMaxLoc(res)
h,w=template.shape[:2]

for meth in methods:
    img2=img.copy()
    
    # 匹配方法的真值
    method=eval(meth)
    print(method)
    res=cv2.matchTemplate(img,template,method)
    min_val,max_val,min_loc,max_loc=cv2.minMaxLoc(res)
    
    # 如果是平方差匹配TM_SQDIFF或归一化平方差匹配TM_SQDIFF_NORMED,取最小值
    if method in [cv2.TM_SQDIFF,cv2.TM_SQDIFF_NORMED]:
        top_left=min_loc
    else:
        top_left=max_loc
    bottom_right=(top_left[0]+w,top_left[1]+h)
    
    # 画矩形
    cv2.rectangle(img2,top_left,bottom_right,255,2)
    
    plt.subplot(121),plt.imshow(res,cmap='gray')
    plt.xticks([]),plt.yticks([])
    plt.subplot(122),plt.imshow(img2,cmap='gray')
    plt.xticks([]),plt.yticks([])  # 隐藏坐标
    plt.suptitle(meth)  
    plt.show()
匹配多个对象
# 读入图像
img_rgb=cv2.imread(path')
# 灰度处理                   
img_gray=cv2.cvtColor(img_rgb,cv2.COLOR_BGR2GRAY)
# 读模板                   
template=cv2.imread(path,0)
# 拿到模板的高,宽                   
h,w=template.shape[:2]

# 匹配                   
res=cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
threshold=0.8
# 取匹配程度大于%80的坐标
loc=np.where(res>=threshold)
for pt in zip(*loc[::-1]):
    bottom_right=(pt[0]+w,pt[1]+h)
    cv2.rectangle(img_rgb,pt,bottom_right,(0,0,255),2)
    
cv_show('img_rgb',img_rgb)

在这里插入图片描述

十.图像特征
Harris角点检测

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
近似可得:

c ( x , y ; Δ x , Δ y ) ≈ ∑ ω ( I z ( u , v ) Δ x + I y ( u , v ) Δ y ) 2 = [ Δ x , Δ y ] M ( x , y ) [ Δ x Δ y ] c(x,y;\Delta{x},\Delta{y})\approx{\sum_\omega(I_z(u,v)\Delta{x}+I_y(u,v)\Delta{y})^2}=[\Delta{x},\Delta{y}]M(x,y) \left[\begin{matrix} \Delta{x}\\ \Delta{y} \end{matrix}\right] c(x,y;Δx,Δy)ω(Iz(u,v)Δx+Iy(u,v)Δy)2=[Δx,Δy]M(x,y)[ΔxΔy]
其中M: M ( x , y ) = ∑ ω [ I z ( x , y ) 2 I z ( x , y ) I y ( x , y ) I z ( x , y ) I y ( x , y ) I y ( x , y ) 2 ] M(x,y)=\sum_\omega{\left[\begin{matrix} I_z(x,y)^2 & I_z(x,y)I_y(x,y)\\ I_z(x,y)I_y(x,y) & I_y(x,y)^2 \end{matrix}\right] } M(x,y)=ω[Iz(x,y)2Iz(x,y)Iy(x,y)Iz(x,y)Iy(x,y)Iy(x,y)2] =

[ ∑ ω I z ( x , y ) 2 ∑ ω I z ( x , y ) I y ( x , y ) ∑ ω I z ( x , y ) I y ( x , y ) ∑ ω I y ( x , y ) 2 ] = [ A c c b ] \left[\begin{matrix} \sum_\omega{I_z(x,y)^2} & \sum_\omega{I_z(x,y)I_y(x,y)}\\ \sum_\omega{I_z(x,y)I_y(x,y)} & \sum_\omega{I_y(x,y)^2} \end{matrix}\right]=\left[\begin{matrix} A & c\\ c & b \end{matrix}\right] [ωIz(x,y)2ωIz(x,y)Iy(x,y)ωIz(x,y)Iy(x,y)ωIy(x,y)2]=[Accb]

简化可得: c ( x , y ; Δ x , Δ y ) ≈ A Δ x 2 + 2 C Δ x Δ y + B Δ y 2 c(x,y;\Delta{x},\Delta{y})\approx{A\Delta{x^2}+2C\Delta{x}\Delta{y}+B\Delta{y^2}} c(x,y;Δx,Δy)AΔx2+2CΔxΔy+BΔy2

A = ∑ ω I z 2 , B = ∑ ω I y 2 , C = ∑ ω I x I y A=\sum_\omega{I_z^2},B=\sum_\omega{I_y^2},C=\sum_\omega{I_xI_y} A=ωIz2,B=ωIy2,C=ωIxIy

二次项函数本质上是一个椭圆函数,椭圆方程为:
[ Δ x , Δ y ] M ( x , y ) [ Δ x Δ y ] = 1 [\Delta{x},\Delta{y}]M(x,y)\left[\begin{matrix}\Delta{x}\\\Delta{y}\end{matrix}\right]=1 [Δx,Δy]M(x,y)[ΔxΔy]=1

在这里插入图片描述

  • cv2.cornerHarris(img,blockSize,ksize,k)
    • img:数据类型为float32的输入图像
    • blockSize:角点检测中指定区域的大小
    • ksize:Sobel求导中使用的窗口大小
    • k:取值参数为[0.04,0.06]
SIFT(Scale Invariant Feature Transform)算法
  • 图像尺度空间

    在一定范围内,无论物体是大还是小,人眼都可以分辨,然而计算机要有相同的能力却很难,所以要让机器能够对物体在不同尺度下有一个同一的认知,就需要考虑图像在不同的尺度下都存在的特点

    尺度空间的获取通常需要使用高斯模糊来实现

image=cv2.imread(path)
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)

sift=cv2.xfeatures2d.SFIT_creat()
# kp就是关键点
kp=sift.detect(gray,None)

image=cv2.drawKeypoints(gray,kp,image)
cv_show('drawKeypoints',image)

# 计算特征
kp,des=sift.compute(gray,kp)
十一.背景建模
帧差法
  • 由于场景中的目标在移动,目标的影像在不同图像帧中的位置不同,该类算法对时间上连续的两帧图像进行差分运算,不同帧对应的像素点相减,判断灰度差的绝对值,当绝对值超过一定阈值时,即可判定为运动目标,从而实现目标的检测功能

    帧差法会引入噪音和空洞问题

混合高斯模型
  • 在进行前景检测前,先对背景进行训练,对图像中的每个背景采用一个混合高斯模型进行检测,每个背景的混合高斯的个数可以自适应,然后在测试阶段,对新来的像素进行GMM匹配,如果该像素值能够匹配其中一个高斯值,则认为是背景,由于整个过程GMM模型在不断更新学习中,所以对动态背景有一定的鲁棒性,最后通过对一个有树枝摇摆的动态背景进行前景检测,取得了较好效果
  • 在视频中对于像素点的辩护情况应当是符合高斯分布
  • 混合高斯模型学习方法
    • 1.首先初始化每个高斯模型矩阵参数
    • 2.取视频中T帧数据图像用来训练高斯混合模型,来了第一个像素后用它来做第一个高斯分布
    • 3.当后面来的像素时,与前面已有的高斯的均值比较,如果该像素点的值与其模型均值差在3倍的方差内,则属于该分布,并对其进行参数更新
    • 4.如果下一次来的像素不满足当前高斯分布,用它来创建一个新的高斯分布
  • 混合高斯模型测试方法
    • 在测试阶段,对新来的像素点的值与混合高斯模型中的每个均值进行比较,如果其差值在2倍的方差之间的话,则认为是背景,否则认为是前景,将前景赋值为255,背景赋值为0,这样就形成了一副前景二值图
cap=cv2.VideoCapture('path')
kernel=cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
fgbg=cv2.createBackgroundSubtractorMOG2()

while True:
    ret,frame=cap.read()
    fgmask=fgbg.apply(frame)
    # 开运算除噪点
    fgmask=cv2.morphologyEx(fgmask,cv2.MORPH_OPEN,kernel)
    # 找到轮廓
    im,contours,hierarchy=cv2.findContours(fgmask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    for c in contours:
        # 计算周长
        perimeter=cv2.arclength(c,True)
        if perimeter>188:
            # 找到一个直矩形
            x,y,w,h=cv2.boundingRect(c)
            cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)
        cv2.imshow('frame',frame)
        cv2.imshow('fgmask',fgmask)
        k=cv2.waitKey(100) & 0xff
        if k==27:
            break
    cap.release()
    cv2.destroyAllWindows()

该像素值能够匹配其中一个高斯值,则认为是背景,由于整个过程GMM模型在不断更新学习中,所以对动态背景有一定的鲁棒性,最后通过对一个有树枝摇摆的动态背景进行前景检测,取得了较好效果

  • 在视频中对于像素点的辩护情况应当是符合高斯分布
  • 混合高斯模型学习方法
    • 1.首先初始化每个高斯模型矩阵参数
    • 2.取视频中T帧数据图像用来训练高斯混合模型,来了第一个像素后用它来做第一个高斯分布
    • 3.当后面来的像素时,与前面已有的高斯的均值比较,如果该像素点的值与其模型均值差在3倍的方差内,则属于该分布,并对其进行参数更新
    • 4.如果下一次来的像素不满足当前高斯分布,用它来创建一个新的高斯分布
  • 混合高斯模型测试方法
    • 在测试阶段,对新来的像素点的值与混合高斯模型中的每个均值进行比较,如果其差值在2倍的方差之间的话,则认为是背景,否则认为是前景,将前景赋值为255,背景赋值为0,这样就形成了一副前景二值图
cap=cv2.VideoCapture('path')
kernel=cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
fgbg=cv2.createBackgroundSubtractorMOG2()

while True:
    ret,frame=cap.read()
    fgmask=fgbg.apply(frame)
    # 开运算除噪点
    fgmask=cv2.morphologyEx(fgmask,cv2.MORPH_OPEN,kernel)
    # 找到轮廓
    im,contours,hierarchy=cv2.findContours(fgmask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    for c in contours:
        # 计算周长
        perimeter=cv2.arclength(c,True)
        if perimeter>188:
            # 找到一个直矩形
            x,y,w,h=cv2.boundingRect(c)
            cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)
        cv2.imshow('frame',frame)
        cv2.imshow('fgmask',fgmask)
        k=cv2.waitKey(100) & 0xff
        if k==27:
            break
    cap.release()
    cv2.destroyAllWindows()
  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值