OpenCV

一,数据读取-图像

1,读取图像

import cv2
import matplotlib.pyplot as plt
import numpy as np
img=cv2.imread('cat.jpg') #读取图像。直接使用cv2.imread指定图形路径来读取
                          #读取结果为一个三维数组,如下图
img=cv2.imread('cat.jpg',cv2.IMREAD_GRAYSCALE)#读取灰度图像

 

 第一维表示整张图像,第二维表示图像中的一个像素点,其中每个像素点又分为若干像素点,第三维表示最小像素点的RGB数值

 

 

像素点范围为0~255,所以选用dtyper为uint8就可以了 

如下所示,二维数组,为灰度图的读取结果

2,展示图像

将图像读进来之后,我们可能要对图像进行处理,在处理时想看一看这个图像实际变成什么样了。处理这个问题,我们可以用matplotlib工具包,也可以直接使用opencv(需要注意,opencv默认的读取格式是BGR,与matplotlib有一些冲突,在用其他工具包时也要看看默认读取格式是否是BGR,所以最好用opencv自带函数进行展示)

#图像显示,也可以创建多个窗口
cv2.imshow('image',img)#第一个参数是给窗口命名为image,第二个参数是去显示img这张图像
#等待时间,毫秒级,0表示按键盘任意键关闭窗口,如果改为1000,表示停留1000ms后自动关闭
cv.waitKey(0)
cv2.destroyAllWindows()#当触发一些关闭时,就把所有窗口关闭掉

 运行结果:

如果每次都写这几行来显示图像会比较麻烦,所以将他们打包在一个函数中

def cv_show(name,img):
    cv2.imshow(name,img)
    cv2.waitKey(0)
    cv2.destoryAllWindows()

 如下为灰度图展示

3,获得图像信息

图像大小

img.shape   

 

输出结果为图像高414,宽500,为RGB彩色图(在opencv中为BGR) 

输出结果为高414.宽500,为灰度图

图像像素点个数

img.size

图像数据类型

img.dtyp

4,图像保存

cv2.imwrite('cat.jpg',img) #将img保存为cat.jpg在路径中

二,数据读取-视频

1,读取视频

cv2.VideoCapture可以捕获摄像头,用数字来控制不同的设备,例如0,1.如果是视频文件,直接指定好路径即可。

vc=cv2.VideoCapture('test.mp4')
#检查是否打开
if vc.isOpened():
    open,frame = vc.read()#如果打开则vc.read()会将视频的第一帧给到open,open为1
                    #如果再vc.read()就会将视频第二帧给open。。。。。
                    #vc.read()会返回两个值,第一个值是bool类型,如果有图像就返回True
                    #第二个值是返回视频中图像的每一帧,也就是三维数组
else:
     open = Flase
while open:
    ret,frame = vc.read()
    if frame is None:#如果视频结束就退出此循环
        break
    if ret== True:#如果有图像帧,则进行处理
        gray = cv.cvtColor(frame, cv2.COLOR_BGR2GRAY)#将图像转换为黑白图像
        cv2.imshow('result',gray)
        if cv2.waitKey(10) & 0xFF == 27:#27指的是退出键所对应的值,也可以设置其他键位作为退                                                                                    
                                        #出键来切换下一帧,cv2.waitKey(10)是指10ms播放一帧
            break
vc.release()
cv2.destoryAllWindows()

        

2,截取部分图像数据

即截取图像部分

img = cv2.imread('cat.jpg')
cat = img[0:200,0:200]#切片操作,只选择200*200的区域(以左上角为原点)
cv_show('cat',cat)

 3,颜色通道提取

b,g,r = cv2.split(img)#分离颜色通道
b.shape#输出(414,500)

img = cv2.merge((b,g,r))#合并颜色通道

#只保留R
cur_img = img.copy()#将img图像数据赋值给cur_img
cur_img[:,:,0]=0#按照B G R这个顺序,B的索引为0,G索引为1,R索引为2
cur_img[:,:,1]=0
cv_show('R',cur_img)

 

只保留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)#img是要填充的图像,borderType指的是要按照什么样的方式填充

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)

 

四,数值计算

#读取两张图片
img_cat=cv2.imread('cat.jpg')
img_dog=cv2.imread('dog.jpg')
img_cat2 = img_cat + 10 #数组中所有数据都加10
img_cat[:5,:,0]
img_cat2[:5,:,0]
#两矩阵相加(维度必须相同),即每个数据都相加,由于dtype=unit8,所以超过256会从重新回到0再加,相当于%256
(img_cat + img_cat2)[:5,:,0]
#add函数相对于+来说,如果大于256,他就会取255,而不会从0开始重新加
cv2.add(img_cat,img_cat2)[:5,:,0]
#图像融合(图像叠加)
img_cat + img_dog#如果两个图片大小(即shape值)不同会报错
#如果shape值不同,可以如下修改,img_dog是指要修改的图片,(500,414)是指要修改成的图片大小
img_dog = cv2.resize(img_dog,(500,414))
res = cv2.addWeighted(img_cat , 0.4,img_dog, 0.6 ,0)#图像融合,0.4和0.6表示要融合图像的像素值比例,0表示偏置项(可以改变整体亮度)
res = cv2.resize(img,(0,0),fx=3.fy=1)#(0,0)表示不指定图片要修改的大小,而用fx和fy来指定图像大小的倍数,fy=1表示y方向上不变,fx=3表示在x方向上放大三倍

 

 

五,图像阈值(二值化)

 

 

六,图像平滑处理(去噪点)

对图像的数据进行滤波操作

#先打开原始图像看一看
img = cv2.imread('1.png')
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

看以看到原始图像上是由很多点的 

 

1,均值滤波

均值滤波就是简单的平均卷积操作,原理如下:

比如我们拿到了下图所示的图像矩阵,我们可以取3*3(一般为奇数)一个矩形数据。要对中间的204像素点做变换,均值滤波就要取3*3这个矩阵内的9个数据然后取平均值,这个平均值就可以赋给204.如果要修改78,就要求以他为中心的3*3矩阵数据的平均值

 

只需要调用opencv库中的blur函数就可以实现均值滤波 

blur = cv2.blur(img,(3,3))

cv2.imshow('blur',blur)
cv2.waitKey(0)
cv2.destroyAllWindows()

可以看到噪音点小了很多 

 

2,方框滤波

 方框滤波和均值一样,只不过可以多选一个参数

#第二个参数-1:Python中指定-1表示让它自动进行计算,他表示我们得到的结果和原始输入的颜色通道数在颜色通道上是一致的,通常情况下也就用-1
#第四个参数normalize=TRUE则和均值滤波一摸一样,如果等于False,则将3*3内的9个数据相加而不取均值,如果大于255.则这个像素点就取255.效果如下图
box = cv2.boxFilter(img,-1,(3,3),normalize=TRUE)

 

3,高斯滤波

下图为高斯函数,指的是越接近中间均值时可能性越大,放在图像矩阵中就是离要修改数据越近的数据在求均值时比重就应该越大。

高斯滤波是一种线性滤波器,能够有效抑制噪声,平滑图像。其作用原理和均值滤波类似,都是取滤波器窗口内的像素均值作为输出,只是窗口模板的系数不同,均值滤波模块系数全部为1,而高斯滤波器的系数随着距离模板中心的增大而减小。所以高斯滤波器相比于均值滤波器对图像的模糊程序较小。

 

 高斯模糊的卷积核里的数值是满足高斯分布,相当于更重视中间的

aussian = cv2.GaussianBlur(img,(5,5),1)

以下为执行结果,相对于均值滤波好了很多 

 

4,中值滤波

要修改的数据是用所选矩阵数据中的中值数据 

median = cv2.medianBlur(img,5)#5表示5*5

以下为滤波效果 

 

 可以利用numpy库函数,将上述所展示的图像连接在一起

res = np.hstack((blur,aussian,median))#hstack是将矩阵水平排列,vstack是将矩阵竖直排列
cv2.imshow('median vs average',res)

下图为hstack展示图像 

七,腐蚀操作与膨胀操作(形态学)

1,腐蚀操作

腐蚀操作需要使用二值化后的图像进行

img = cv2.imread('dige.png')
cv2.imshow('img',img)
cv2.waitKey()
cv2.destroyAllWindows()

 

这张图字有毛刺,腐蚀操作就可以处理,处理后的图像,线条变细了,而且可以处理毛刺

下图为腐蚀一次的结果

 

下图为腐蚀两次的结果

 

原理:

在下图黑色区域选择一个3*3卷积和,它都是黑色,这个点就不会修改。然后再在圆形白色区域内选择一个白色像素点,它周围的九宫格均为白色,所以不用修改。如果在黑白交界处找一个九宫格,他就会有白色和黑色,这样就会把这个点腐蚀掉

 

代码:iterations用来修改迭代次数,np.ones里的(5,5)用来修改一次迭代的大小

 

 2,膨胀操作(腐蚀操作逆)

以下为腐蚀操作

img = cv2.imread('dige.png')
kernel = np.ones((3,3),np.uint8)
dige_erosion = cv2.erode(img,kernel,iterations = 1)
cv2.imshow('erosion',erosion)
cv2.waitKey(0)
cv2.destroyAllWindows()

得到图片如下 ,相比于之前虽然去除了毛刺,但是字也变细了,膨胀操作就可以让字变粗

以下经过膨胀操作 

kernel = np.ones((3,3),np.uint8)
dige_dilate = cv2.dilate(dige_erosion,kernel,iterations = 1)
cv2.imshow('dilate',dige_dilate)
cv2.waitKey(0)
cv2.destroyAllWindows()

可以看到线条变粗了,这就是膨胀操作 

 

3,开运算和闭运算

开运算:先腐蚀后膨胀

img = cv2.imread('dige.png')
kernel = np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
cv2.imshow('opening',opening)
cv2.waitKey(0)
cv2.destroyAllWindows()

闭运算: 先膨胀后腐蚀

img = cv2.imread('dige.png')
kernel = np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
cv2.imshow('closing',closing)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

4,梯度计算 

梯度=膨胀-腐蚀

对原始图像分别进行膨胀和腐蚀,如下图

如果用膨胀后的减去腐蚀后的,那么这两张图中间都为白色的区域为255-255=0,就变成了黑色。这就是梯度运算

gradient = cv2.morphologyEx(pie, cv2.MORPH_GRADIENT, kernel)
cv2.imshow('gradient',gradient)
cv2.waitKey(0)
cv2.destroyAllWindows(0)

 

5,礼帽和黑帽(礼貌突出更明亮区域)

  • 礼帽:原始输入-开运算结果(剩下毛刺)
  • 黑帽:闭运算-原始输入(原始的小轮廓,无毛刺)

 以下为礼帽代码及结果

img = cv2.imread('dige.png')
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
cv2.imshow('tophat',tophat)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

 以下为黑帽代码及结果

img = cv2.imread('dige.png')
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
cv2.imshow('blackhat',blackhat)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

八,图像梯度计算

在白色圆中间画一条线,这条线上的像素点都为白色,不会产生梯度。如果在黑白边缘处的一点,它的左边和右边分别为黑色和白色,差值很大,这个点就有很大梯度

1,Sobel算子

 计算一个点的梯度,需要看它的左右像素值和上下像素值。左右计算就用Gx,上下计算就用Gy

上述Gx,Gy是取3*3的一个区域,我们把这个区域对应的像素值和Gx对应处的值相乘然后相加,得到的和就表示这个点的水平梯度,同理,Gy得到的就是竖直梯度(如果得到的小于0就等于0)

代码实现:dst = cv2.Sobel(src,ddepth,dx,dy,ksize)

  • src指的是当前图像

通常情况下ddepth指定成-1就可以 ,表示输入深度和输出深度一样。

ksize就是选取?*?大小的区域计算,通常选取为3*3

img = cv2.imread('pie.png',cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
#cv2.CV_64F:算出的结果有正有负,opencv会默认把负值截断成0,
#而负值按道理应该取为绝对值,在未取绝对值之前,应该保留负数
#cv2.CV_64F可以保留负数
#dx指定为1,dy指定为0表示只算水平的

 

按理说一个圆应该沿着圆弧都有梯度,而上图输出结果只有左半弧有结果 ,这是因为根据Gx水平为右减左,也就是白-黑是一个大于0的,而右边是黑-白是一个负数,opencv会给他阶段成0(也就是黑色),所以对上述代码进行修改,取一下绝对值

img = cv2.imread('pie.png',cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobelx = cv2.convertScaleAbs(soblex)#取绝对值

这样就可以输出正常图像

 

如果dx=0,dy=1得到以下图像

 

分别计算x和y再求和:

soblexy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)

 

如果直接计算:

img = cv2.imread('pie.png',cv2.IMREAD_GRAYSCALE)
sobelxy = cv2.Sobel(img,cv2.CV_64F,1,1,ksize=3)
sobelxy = cv2.convertScaleAbs(soblexy)#取绝对值

可以看到,效果并没有分别计算Gx和Gy好,所以不建议直接计算 

 

2,Scharr算子

 

3,laplacian算子

缺点:对噪音点比较敏感

通常与其他方法融合使用

 

 第一张为Sobel算子,第二张为Scharr算子,第三张为laplacian算子

九,Canny边缘检测

 Canny边缘检测五步:

1,高斯滤波器

与高斯滤波原理相同,以下给定归一化数值:

 

2, 梯度强度和方向

 

3,非极大值抑制

 方法一如下:

假如说算出C点的梯度值,且蓝线为梯度方向。目的是为了比较C点附近两个点的梯度是否比C大,比C大就取消C点作为边缘,比C小则C点保留下来作为边缘。

蓝线与红框交点dTmp1和dTmp2即为要与C点比较的梯度点,正常情况下dTmp1和dTmp2一般不会正好处于某个像素点中心,而是在g1,g2两块像素点的中间。所以g1,g2梯度可以计算而,dTmp1梯度不能直接计算,这里就借助权重值,以g1,g2到dTmp1的距离为权重加和即可表示出dTmp1出的梯度值。

 

方法二:

 这种方法是方法一的简便运算

4,双阈值检测

 

5,最终效果 

img = cv2.imread('lena.jpg',cv2.IMREAD_GRAYSCALE)
v1 = cv2.Canny(img,80,150)#80为minVal,150为maxVal
v2 = cv2.Canny(img,50,100)
res = np.hstack((v1,v2))

 

十,图像金字塔

图像金字塔就是从底部开始越往上图像越小。当要做图像特征提取的时候,不仅要对原始图像进行提取,还要对每一层都进行特征提取,可能每一层提取出来的结果信息是不一样的,将提取的结果总结在一起。

 

1,高斯金字塔

(1)向下采样法

 先进行高斯滤波

第一步将图像像素数据与对应位置Gi相乘,再加和,再做归一化处理(即除总数据数25,上式16改为25) 

第二步将上述5*5数据的偶数行和列去除,即图像长和宽缩小为原来的一半

(2)向上采样法

 先新增行和列进行扩大,再进行卷积

(3)代码实现

原始图像如下

 

#向上采样法
img = cv2.imread("AM.png")
up = cv2.pyrUp(img)#原始图像为(442,340),向上采样后变为(884,680)

 

#向下采样法
img = cv2.imread("AM.png")
up = cv2.pyrDown(img)#原始图像为(442,340),向下采样后变为(221,170)

 

并且可以执行多次 

2,拉普拉斯金字塔

每一层都通过以下公式计算,即用原始图像-执行完PryUp和PryDown后的图像

down = cv2.pyrDown(img)
down_up = cv2.pryUp(down)
l_1 = img-down_up

结果如下 

 

十一,轮廓检测

1,轮廓提取

 之前所说过的边缘只是一些零零散散的片段,而轮廓是闭合的,整体的。

利用findContours提取轮廓,mode通常选择RETR_TREE

 下图左图是利用了第一个轮廓逼近方法得到的,右图是利用第二个轮廓逼近方法得到的。

轮廓检测时,为了更高的准确率,需要使用二值化图像。所以第一步需要将图像转换为二值化图像。

img = cv2.imread('contours.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#转换为灰度图像
ret,thresh = cv2.threshold(grat, 127, 255, cv2.THRESH_BINARY)#转换为二值化图像

得到以下结果 

 

绘制轮廓:

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

此代码返回2个值,第一个值contours就是轮廓点,第二个值hierarchy就是 层级,把结果保存在层级之中,

利用返回的contours轮廓点画出轮廓

draw_img = img.copy()#注意要使用copy,因为cv2.drawContours画出的轮廓会将原始图像覆盖
res = cv2.drawContours(draw_img, contours, -1, (0,0,255), 2)#draw_img必须为三颜色通道图片
#第三个参数表示要画几个轮廓,-1表示都画进来,第四个参数(0,0,255)分别表示BGR,此处表示用红色的线去画,2表示线条宽度

如下,可以看到用红线画出了轮廓 

 

2,轮廓特征

 注意,在计算面积和周长的时候一定要将第几个轮廓指定出来,比如不能直接用contours,而应该用contours[0](指第一个轮廓)来操作

cnt = contours[0]
#计算面积
cv2.contourArea(cnt)
#计算周长,True表示闭合
cv2.arcLength(cnt,True)

3,轮廓近似

将轮廓近似成一些比较规整的形状,如下图,将轮廓近似为中间的矩形

原理是一条曲线,连接两个端点A,B形成直线l,在这条曲线上到直线l的最大距离的点C,如果C到直线l距离小于所设阈值,就将这条曲线近似为AC和CB,如果大于,就以AC和CB为两条新曲线重复上述过程

epsilon = 0.1*cv2.arcLength(cnt,True)
#第一个参数指要近似的轮廓,第二个参数是要指定的阈值一般情况下为上述公式
approx = cv2.approxPolyDP(cnt,epsilon,True)
draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], -1, (0,0,255), 2)

4,轮廓外接矩形和外接圆

外接矩形:

 cv_show为自己编写的展示图象函数,不是库函数

 

甚至可以算外接比 

 

外接圆:

 

 

十二,模板匹配

 模板匹配:比如说截取了图片的一部分,在原图像中将图像按照截取的大小将原图像分为多个部分,模板匹配要做的就是找出截取图像的那个部分。opencv会将截取图片的那部分(模板窗口)以从左到右,从上到下的顺序与原图像进行一步步匹配,按照匹配程度来选出图片区域

模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里有6种,然后计算的结果放在一个矩阵里,作为结果输出。假如原图像为AxB大小,而模板时axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1)

最好使用带归一化的.

 

 

 原始图像大小为(263,263)

模板窗口图像大小为(110,85)

#img表示原始图像,template表示截取图像
res = cv2.matchTemplate(img,template,cv2.TM_SQDIFF)

进行匹配算法之后返回结果图像大小为(154,179),可以看到这个矩阵中的数是很多的,我们可以用以下函数得出这堆数据的最值

min_val,max_val,min_loc,max_loc=cv2.minMaxLoc(res)

注意:左上角为原点 

最后将结果画出来

#w表示模板的宽,h表示模板的高
cv2.rectangle(img,(107,89),(107+w,89+h),255,2)

 

  •  匹配多个对象:

不需要找到最值,而是选择一个合适的范围值

十三,直方图(统计)

1,基本用法

直方图就是统计一张图像中像素点数据的数量,制成直方图。如下

以下为直方图统计函数,第一个参数是要输入的图像,通常情况下转换为灰度图。第二个参数是输入图像不是灰度图时,可输入0 1 2分别表示BGR三个通道的数值统计。第三个参数mask掩膜是指输入一张图像时,正常情况下是对图像整体进行统计,而这个参数是告诉函数统计图像的哪个部分。第四个参数是指统计结果的直方图中横坐标一个矩形格代表的范围 ,比如0~10,11~20以bin=10为间距的,也可以是0~20,21~40以bin=20为间距的。最后一个参数是像素的取值范围

hist返回值:hist = cv2.calcHist([img],[0],None,[256],[0,256])中,hist是一个256*1的矩阵,每一个值代表了每个灰度值对应的像素点数目 

​
import cv2
import numpy as np
import matplotlib.pyplot as plt
def cv_show(img,name):
    cv2.imshow(name,img)
    cv2.waitKey()
    cv2.destroyAllWindows()

​img = cv2.imread('cat.jpg',0)#0表示灰度图
hist = cv2.calcHist([img],[0],None,[256],[0,256])#参数外必须加中括号
                                                 #已经为灰度图了,只有一个通道,[0]表示选择第一个通道
##img.ravel() 将图像转成一维数组,这里没有中括号
plt.hist(img.ravel(),256)
plt.show()#展示图像,用opencv也可以

 

  •  每个颜色通道均展示,如下
  • 掩膜:
mask = np.zeros(img.shape[:2],np.uint8)
mask[100:300,100:400] = 255#将不掩盖区域变为白色
cv_show(mask,'mask')

 

#读入猫图像
img = cv2.imread('cat.jpg',0)
cv_show(img,'img')

 

masked_img = cv2.bitwise_and(img, img, mask=mask)#与操作,有一个地方为0就为0,这样黑色区域都为0,两张图像与操作就有了黑色区域掩膜
cv_show(masked_img,'masked_img')

 

hist_mask = cv2.calcHist([img],[0],mask,[256],[0,256])

2,直方图均衡化(增强图像对比度)

上图中有些位置值很少,有些位置值很多,这个直方图不够均衡

 接下来就要让他变得均衡一点,变化过程结果如下图

算法过程: 

以下表为例,首先,将统计到的像素值如下图50,128,200,255从小到大排序好,根据像素值出现的次数计算它们的概率,然后计算累计概率(本身的概率+上面所有值出现的概率,比如0.4375=0.1875+0.25. 0.75=0.3125+0.4375),然后根据累计概率通过计算公式改变像素值。计算公式:映射后的灰度值=累积概率X255即可

  • 效果展示

均衡化前图像

均衡化后图像 

 

3,自适应直方图均衡化(增强图像对比度)

 可以看一下下图,经过直方图均衡化之后,人物脸上的细节几乎消失了。这种情况下我们可以采用自适应均衡化,将图像分块分别均衡化,然后再融合在一起。但是如果每块都均衡化再融合在一起可能会出现每块的图像边界更明显,一张图像中会出现多条边界,不过在opencv中函数已经通过拟合的方式解决了这个问题

import cv2
 
# 读取灰度图像
image = cv2.imread('input.jpg', cv2.IMREAD_GRAYSCALE)
 
# 创建 CLAHE 对象
clipLimit = 2.0
tileGridSize = (8, 8)
clahe = cv2.createCLAHE(clipLimit=clipLimit, tileGridSize=tileGridSize)
 
# 对图像应用 CLAHE
enhanced_image = clahe.apply(image)
 
# 显示结果
cv2.imshow("Original Image", image)
cv2.imshow("Enhanced Image", enhanced_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

下方第三张图是自适应均衡化之后的,有明显改善 

十四,傅里叶变换

1,基本变换 

cv2.dft()就是执行傅里叶变换,得到的结果是一个频域,没办法显示出来。为了显示还要使用cv2.idft()进行逆变换一下

import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('lena.jpg',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('Magnitude Spectrum'),plt.xticks([]),plt.yticks([])
plt.show()

右图可以看到低频信号到了中间(越低频越亮) 

 

2,低通滤波 

利用掩码mask实现,选中中心区域令为1,其他区域是0,这样在fshift = dft_shift*mask这一步中为1的位置与原始图像相乘仍为原始数据,为0的位置与原始位置相乘就变成了0,实现掩膜的目的

左图为原图,右图为低通滤波后的图像 

 

3,高通滤波

 

 

十五,角点检测

1,基本原理

如下图,如果图像中某物体的内部移动,那么沿xy轴方向像素灰度值变化并不剧烈;如果在图像中物体与物体之间的边界上移动,沿y轴方向灰度值变化并不剧烈,而沿x轴方向变化剧烈;如果某角点移动则沿xy轴方向变化均剧烈

用I(x,y)来表示(x,y)点的灰度度数值。假如说框选了3*3区域,往上移动一段距离再框选3*3区域,比较两个区域内9个像素点灰度值的变化情况,变化情况计算公式如下

 

 

 

 

 

 

栏目大1和栏目大2就是 M(x,y)矩阵对角化后得到的两个特征值 

 

用以下公式判断:α通常取0.0?。如果R接近于0,代表平坦区域;如果R小于0代表边界;如果R大于0且不约等于0代表角点 

最后,再加上一个极大值抑制即可

2,opencv角点检测效果

使用函数如下:ksize一般取3。k值用来判定的系数大小

可以看到角点标红了。改变img[dst>0.01*dst.max()]中的0.01可以改善角点检测时误将边界识别为角点 

 

十六,尺度空间(SIFT算法)

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

需要做两件事:1,相同大小情况下做同等数量的高斯模糊(如下图做了6张)。2,做不同大小的图片(金字塔)

1,高斯模糊

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

以下是选择5*5的高斯模糊 。类似于高斯滤波。通过来控制模糊程度越大

2,多分辨率金字塔 

每一层金字塔都要有相同数量的模糊图片

3,高斯差分金字塔

以下图为例,下图为两层金字塔,最下面一层图片大小更大,并且每一层都有5种不同模糊。将每一层临近的两种模糊相减,每层共得到4个差分结果。差距比较大的(也就是特征)在差分后的结果中更明显

 

通过上式之后,就要找出一些差别比较大的点,也就是极值点

比如说,下图经过高斯模糊后得到以下3张不同模糊程度的图像。以叉号点为例,计算它是否为极值点,之前计算它是否为极值点,只需要计算它与周围8个点的差值而SIFT算法还要计算它与临近两幅图像,也就是上下两幅图像对应位置的差值,以3*3区域为例,既要计算它与26个点的差值。但是两边的模糊图像只有一个临近图像,所以只能用被夹在中间的模糊图像计算极值点。计算完之后会得到一系列极值点

 

经过计算得到的极值点均为离散的点,需要进一步精确定位,对尺度空间中各点组成的函数进行曲线拟合,再计算其极值点,从而实现关键点的精确定位。泰勒展开式展开之后,再对f(x)进行求导,一阶导等于0的地方就是真正的极点。

以上举例的是一维曲线,而D(x,y,) 是三维,公式如下

当得到极值点位置之后,还要进行一些过滤和判断 (使用高斯计算的时候可能会增加一些边界响应),做法和角点检测几乎一样

消除完之后得到关键点

接下来利用计算出来的尺度和方向。统计方向时只统计上下左右以及倾斜45度的4个方向,直方图横坐标是0~45,45~90,90~135。。。。等,通常情况下统计完后得到一个数量最多的为主方向,但是如果有一个和主方向特别相近的方向,我们可保留这两个方向,分别进行操作

 

 

 4,opencv sift函数

找出关键点并构建特征向量

注意:opencv3.4.3版本以后此函数已经做了专利保护。要使用需要降版本到3.4.3以下

import cv2
import numpy as np
img = cv2.imread('test_1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#得到特征点
sift = cv2.xfeatures2d.SIFT_create()#实例化对象
kp = sift.detect(gray,None)
img = cv2.drawKeypoints(gray, kp, img)#可以把关键点绘制出来,结果如下图1
#计算特征
kp, des = sift.compute(gray, kp)#返回两个结果,第一个结果kp还是关键点,第二个参数是每个关键#点对应的特征向量,des.shape输出结果为(关键点数量,128)即每个关键点特征向量为128维
print(np.array(kp).shape)#可以查看关键点数量


 

十七,特征匹配

上一节得到的关键点特征向量,这一节就利用这些特征向量来比较两张图中比较类似的关键点

import cv2
import numpy as np
import matplotlib.pyplot as plt
img1 = cv2.imread('box.png',0)
img2 = cv2.imread('box_in_scene.png',0)
def cv_show(name,img)
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
cv_show('img1',img1)
cv_show('img2',img2)

根据特征向量找出书 

1,Brute-Force蛮力匹配

得到两张图特征向量后对比特征向量之间的大小关系和距离

sift = cv2.xfeatures2d.SIFT_create()
kp1, des1 = sift.detectAndComputer(img1, None)
kp2, des2 = sift.detectAndComputer(img2, None)#找关键点并且计算特征向量的函数,des为特征向量
#BFMatcher来比较这两张图片之间的欧式距离
bf = cv2.BFMatcher(crossCheck = True)#crossCheck表示两个特征点要互相匹配。比如说A图中从i到j #是最近的,B图中从j到i可能不是最近的,等于True就是让从j到i也是最近的

1对1匹配

matches = bf.match(des1, des2)
#matches会有很多结果,进行排序:从最接近的到最远的
matches = sorted(matches,key=lambda x:x.distance)
img3 = cv2.drawMatches(img1, kp1, img2, kp, matches[:10],None, flags=2)
cv_show('img3', img)

 

k对最佳匹配(可以匹配多对)

bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)#一个点对应两个离他最近的
#过滤操作
good = []
for m,n in matches:
    if m.distance < 0.75*n.distance:
        good.append([m])
img3 = cv2.drawMatchesKnn(img1,kp1,img2,kp2,good,None,flags=2)

 

可以看到有些点匹配出现了错误,需要想办法把配对错的给过滤掉,利用RANSAC算法

2,RANSAC算法(随机抽样一致算法)

可以看到左图红线是最小二乘法拟合得到的直线,在该直线拟合的过程中,有一些点太靠下而对直线整体造成了影响,而RANSAC算法拟合的图像如右图绿线,它可以规避误差值的影响

随机选择两个点进行连线,选择一个容忍范围,进行不断迭代。直到在容忍范围内数据点最多

 

3,图像拼接

 对图像进行变换操作

变换所需的图像矩阵为H ,一般为3*3。如下图,原始数据一个像素点[x',y',1](最后一个数据转换为1好求),乘上H即可得到变化后的像素点[x,y,1].求解H矩阵需要求解8个值,需要8个方程,而一个[x',y']到[x,y]就是两个方程,所以需要4个[x',y']到[x,y]即可,也就是至少需要4个特征点。在取特征点时要保证取到的特征点没有错误,这就需要利用RANSAC算法,取到之后算出H矩阵,然后建立一个损失函数,将点带入,看看是否能真的对应到变换后的图像上,根据损失函数找出一个最好的H值

实例:将以下两张图片拼接

 1,提取两张图像特征,找出配对特征点

2,求出H矩阵

拼接结果

 

函数detectAndDescibe 

 

 

 函数matchKeypoints

 

 

十八,背景建模

1,帧差法

帧差法:由于场景中的目标在运动,目标的影像在不同图像帧中的位置不同。这类算法对时间上连续两帧图像进行差分运算,不同帧对应的像素点相减,判断灰度差的绝对值,当绝对值超过一定阈值时,即可判断为运动目标,从而实现目标的检测功能。

 

2,混合高斯模型

对图像中每个像素点通过多帧数据进行高斯模型训练,训练出3~5套高斯模型参数,再对新来的像素值与之前训练的高斯模型进行比较得出是运动的物体还是静止的。

 

 

 

3,opencv实现

最终效果如下

import numpy as np
import cv2
cap = cv2.VideoCapture('text.avi')
#形态学操作需要使用
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
#创建混合高斯模型用于背景建模。实例化对象
fgbg = cv2.createBackgroundSubtractorMOG2()#cv2.createBackgroundSubtractorMOG()改进版
while(1):
    ret, frame = cap.read()#每一次读一帧
    fgmask = fgbg.apply(frame)#背景提取应用到当前每一帧图像当中,得到掩码。掩码就是上述所说 #的静止物体赋值0,运动物体赋值255
    #形态学开运算去噪点
    fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
    #寻找视频中轮廓
im, contours, hierarchy = cv2.findConturs(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(150) & 0xff
    if k == 27:
        break
cap.release()
cv2.destroyAllWindows()

 十九,光流估计

光流是空间运动物体在观测成像平面上的像素运动的“瞬时速度,根据各个像素点的速度矢量特征,可以对图像进行动态分析,例如目标跟踪。光流估计需要满足以下条件:

 箭头大小表示像素运动瞬时速度

下图地上的线就是在跟着人走,也就是追踪功能 

Lucas-Kanade算法

 I表示图像像素点,t表示某一帧图像,dt表示后一帧图像。I对x,y求导在梯度时讲过是可以算出来的,而I对t求导就是后一帧数据减前一帧数据。

 移项后两边分别除dt可以得到下式。而Ix,Iy,It都已知,所以就可以得到一个关于u,v的方程

根据空间一致性,临近两点变化相同(u,v相同)。上述所求Ix,Iy为一个点的变化情况,再找一个A点的临近点B,他们的u和v是相同的,求出B的Ix,Iy就得到了两个未知数两个方程就可以求u和v。在实际中我们不是用两个点,通常是用一个3*3或5*5窗口,如下图。这样我们就有了9或者25个方程来求解2个未知数,显然方程过多,u,v没办法满足所有方程,这时就需要用到最小二乘法

25个方程相当于坐标系上的25个点,u,v分别表示直线的斜率和截距,找出一个最佳斜率和截距来尽可能满足这25个方程 。一般情况下只有角点附近的矩阵才可逆,所以在进行光流估计的时候一定要先检测角点

prevPts待跟踪的特征点向量是指要跟踪哪些特征点。winSize搜索窗口就是上面所说的3*3或者5*5的窗口大小。maxLevel最大金字塔就是尺度空间。会返回三个值,但我们只需要两个值就够了,第一个值nextPts是根据前一帧的特征点,看一下这些特征点在这一帧到哪了。

 

import numpy as np
import cv2
cap = cv2.VideoCapture('test.avi')
#角点检测所需参数
feature_params = dict(maxCorners = 100
                      qualityLevel = 0.3
                      minDistance = 7)
#Lucas Kanade参数
lk_params = dict(winSize = (15,15)
                maxLevel = 2)
#随机颜色条
color = np.random.randint(0, 255, (100,3))
#拿到第一帧图像
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
#返回所有检测特征点,需要输入图像,角点最大数量(效率),品质因子(特征值越大越好,来筛选)
#距离相当于这个区间有比这个角点强的,就不要这个弱的了
p0 = cv2.goodFeaturesToTrack(old_gray, mask = None, **feature_params)
#创建一个mask
mask = np.zeros_like(old_frame)
while(True):
    ret, frame = cap.read()
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    #需要传入前一帧和当前图像以及前一帧检测到的角点
    pi, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
    #st=1表示
    good_new = p1[st==1]#表示此帧找到的特征点传到good_new当中,用于更新
    good_old = p0[st==1]
    #绘制轨迹
    for i,(new,old) in enumerate(zip(good_new,good_old)):
        a,b = new.ravel()
        c,d = old.ravel()
        mask = cv2.line(mask, (a,b), (c,d), color[i].tolist(), 2)
        frame = cv2.circle(frame, (a,b), 5, color[i].tolist(), -1)
    img = cv2.add(frame, mask)
    cv2.imshow('frame', img)
    k = cv2.waitKey(150) & 0xff
    if k == 27:
        break
    #更新,给下一帧使用
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1,1,2)#reshape保证没有问题,-1表示自动判断有多少点,每个点两个值
cv2.destroyAllWindows()
cap.release()
    

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值