Opencv3.0-python的那些事儿:(一)、Opencv的图像和视频处理基本用法

        写这篇文章的目的:Opencv已经支持3.0了,这个与python3.4结合绝对是天生的搭配。Opencv主要可以用于图像和视频处理,由于在实际的一些项目需求中也可能会涉及到图像和视频的处理,因此即使抛开项目不谈,做为一个程序员,对自己比较感兴趣的地方做一些探索也是可以的。

      具体如何在Win7 或 Ubuntu下安装Opencv请参看我这两篇文章。

Windows7 64位+python3.4环境下安装opencv3.0的方法

Ubuntu14.04 64位+Python3.4环境下安装opencv3.0的方法


     下面主要是对图像和视频基本用法的一些使用。

# coding: utf-8

#学习opencv中基本的图像处理操作
import numpy as np
import cv2

#读取图像
def readImage_my():
    #cv2.imread(filename[,flags]),返回图像,作用:加载图像并返回该图像,flags>0:返回3通道颜色,=0:返回灰度图像,<0:返回的图像带有透明度,alpha是灰度通道,记录透明度信息
    img = cv2.imread("0_snap.png" , 0)
    img = cv2.imread("0_snap.png" , 1)
    img = cv2.imread("0_snap.png" , -1)
    cv2.imshow("image" , img)
    #0表示永久等待键盘输入,waitKey()是键盘绑定函数,时间尺度是毫秒级,特定的几毫秒内,如果有键盘输入,函数会返回按键的ASCII码值
    cv2.waitKey(0)
    #删除建立的窗口,删除特定的窗口用cv2.destroyWindow(),参数是想删除的窗口名
    cv2.destroyAllWindows()

#保存图像
def imwrite_test():
    img = cv2.imread("0_snap.png" , 1)
    '''
    cv2.imwrrite(filename,img[,params])->返回值,参数:filename是文件名称,img是保存的图像,作用:将图像保存成指定格式的文件,注意这里的params是一个数组
    对于JPEG,可以是有质量的保存 CV_IMWRITE_JPEG_QUALITY 从0到100,100表示最高保存质量,默认95
    对于WEBP,                  CV_IMWRITE_WEBP_QUALITY
    对于PNG,可以是压缩级别     CV_IMWRITE_PNG_COMPRESSION:从0到9,越小表示保存的大小越大,压缩时间越少,默认为3
    alpha为0时表示透明,255时表示不透明
    '''
    #注意cv2.IMWRITE_PNG_COMPRESSION类型为Long,必须转换成int
    outimg = cv2.imwrite("0_snap_save_9.png" , img , [ int(cv2.IMWRITE_PNG_COMPRESSION),9 ] )
    outimg = cv2.imwrite("0_snap_save_0.png" , img , [ int(cv2.IMWRITE_PNG_COMPRESSION),0 ] )
    '''
    cv2.imshow("outimg" , outimg)
    cv2.waitKey(0)
    cv2.destroyWindow("outimg")
    '''

    '''
    如果你用的是 64 位系统,你需要将  k = cv2.waitKey(0) 这行改成
    k = cv2.waitKey(0)&0xFF
    '''

#修改像素值
def modifyPixel():
    '''
    可以根据像素的行和列的坐标获取他的像素值。对于BGR图像而言,返回值为B,G,R的值。对灰度图像而言,会返回灰度值
    :return:
    '''
    img = cv2.imread("./roi.jpg")
    #图像可以理解为二维数组,给出行列则得到BGR,注意不是RGB,一个像素是一个三元组
    px = img[100,100]
    print(px)
    #给出img[row , col , index]  ,index=0时,给出蓝色的像素值
    blue = img[100,100,0]
    print(blue)
    green = img[100,100,1]
    print(green)
    red = img[100,100,2]
    red2  = img.item(100,100,2)
    print(red)
    print(red2)
    #给出BGR的三元组来直接修改像素值
    img[100,100] = [255,255,255]
    print(img[100,100])
    #获取每个像素值,可以使用Numpy的array.item()和array.itemset(),返回值是标量(只有大小),矢量(大小与方向),想获得所有BGR,需要使用array.item()来分割
    img.itemset( (10,10,2) , 100 )
    red3 = img.item(10,10,2)
    print(red3)
    #img.shape返回行数,列数,色彩通道数(灰度图像不返回通道数)
    print(img.shape)


#提取出感兴趣的图像
def imageROI():
    img = cv2.imread("./roi.jpg" , 1)
    #行对应了图像的高度,列数对应了图像的宽度,190,300
    '''
    我现在比较疑惑的地方就是shape返回的number of rows, columns and channels (if image is color):
    shape返回的是(190, 302, 3)
    实际上该图片是302*190的,也就是宽度是302对应列数
    那么其实shape返回的值可以理解为:高度(y) , 宽度(x)

    行数->对应图像多少行->图像高度->坐标y->对应于img[, y] ,总结: 行数->y
    列数->对应图像多少列->图像宽度->坐标x->对应于img[x, ]
    '''
    print(img.shape)
    #img.size返回图像的像素数目 = 高度 * 宽度 * 3(色彩通道)
    print(img.size)
    #img.dtype: 返回图像的数据类型
    print("图像的数据类型:")
    print(img.dtype)
    '''
    分析:如果我要移动足球到垂直上方处,那么首先x是固定不动的
    由于shape返回190,302
    因此宽度是302,高度是190,因此x的范围是[0,302],y的范围是[0,190]
    球在右下角处,x大概是[150,225]
    y大概是[160,190]
    因为移动到正上方,所以x不变,y减小为[10,40]
    现在关键就是对img[]
    shape(y,x)
    img(y,x)
    因此真正的位置应该是
    '''
    ball = img[160:190 , 150:225 ]
    img[10:40,150:225] = ball
    '''
    ball = img[150:225 , 160:190]
    img[ 150:225 , 10:40 ] = ball
    '''
    cv2.imwrite("roi_modify.jpg" , img , [int(cv2.IMWRITE_JPEG_QUALITY) , 100 ])
    outimg = cv2.imread("roi_modify.jpg" , 1)
    cv2.imshow("roi_out" , outimg)
    cv2.imshow("roi" , img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

#拆分及合并图像通道,适用:对BGR三个通道分别进行操作,或者把独立通道的图片合并成BGR图像
def mergeAndSplitImage():
    img = cv2.imread("./roi.jpg" )
    b,g,r = cv2.split(img)
    #img = cv2.merge(b, g ,r)
    print("分离的通道的bgr分别是")
    print(cv2.split(img))
    #合并时候最多接受两个参数
    img2 = cv2.merge(b, g )
    b = img[: , : , 0]
    print("b色彩通道是")
    print(b)
    #使得所有像素红色通道值为0,split耗时,能用Numpy索引就尽量使用
    img[: , : , 2] = 0
    cv2.imshow("image" , img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

#图像加法
def imageAdd():
    #cv2.add(src1 , src2 [ ,dst [, mask [,dtype]]]) ,返回dst,作用:对两幅图像进行加法运算
    #opencv中的加法是饱和操作,超过最大值按最大值算,小于最小值按最小值算(例如大于1变成1)
    #numpy中的加法是模操作,超过后,取模
    #这里[250]表示是一个数组,该数组只有一个元素250
    x = np.uint8([250])
    print("x = np.uint8([250])的结果是")
    print(x)
    y = np.uint8([10])
    print("cv2饱和加法结果是")
    print(cv2.add(x,y)) # 250 + 10 = 260 => 255
    print("numpy取模加法结果是")
    print(x+y)
    img1 = cv2.imread("./0_snap.png")
    img2 = cv2.imread("./9_snap.png")
    resImg = cv2.add(img1 , img2)
    cv2.imshow("image add",resImg)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

'''
图像混合,也是加法,只不过给与两幅图像的权重不同,给人混合或者透明的感觉
图像混合的计算公式
g(x) = (1-a)f(x) + ah(x)
通过修改a的值,实现混合
'''
def imageBlending():
    img1 = cv2.imread("./blend1.jpg")
    img2 = cv2.imread("./blend2.jpg")
    '''
    cv2.addWeighted(src1 , alpha , src2 , beta , gamma[ , dst [,dtype]] ),返回dst,作用
    dst( = saturate( src1 * alpha + src2 * beta + gamma )
    saturate:饱和
    '''
    dst = cv2.addWeighted(img1 , 0.7 , img2 , 0.3 , 0)
    cv2.imshow("dst" , dst)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

#按位运算,操作有:AND , OR , NOT , XOR,作用:选择非矩形ROI操作会很有用
def bitOperation():
    img1 = cv2.imread("roi.jpg")
    img2 = cv2.imread("opencv_logo.jpg")
    #希望把logo放在左上角
    rows , cols , channels = img2.shape
    roi = img1[0 : rows , 0 : cols]

    #现在创建对于logo的掩码:将源码与掩码(需要的字段位为1)经过或运算得到符合需求的结果

    '''
    cv2.threshold(src, thresh , maxval , type[,dst] )返回retval,dst
    src输入数组或者图像,dst输出图像,maxval用于二元阈值的最大值,type:阈值类型
    作用:将阈值应用到单通道数组,需要用到灰度图像,主要是过滤掉太大或太小的图像
    THRESH_BINARY
    >threshold,为maxval,否则为0
    \texttt{dst} (x,y) =  \fork{\texttt{maxval}}{if $\texttt{src}(x,y) > \texttt{thresh}$}{0}{otherwise}
    THRESH_BINARY_INV
    \texttt{dst} (x,y) =  \fork{0}{if $\texttt{src}(x,y) > \texttt{thresh}$}{\texttt{maxval}}{otherwise}
    THRESH_TRUNC
    \texttt{dst} (x,y) =  \fork{\texttt{threshold}}{if $\texttt{src}(x,y) > \texttt{thresh}$}{\texttt{src}(x,y)}{otherwise}
    THRESH_TOZERO
    \texttt{dst} (x,y) =  \fork{\texttt{src}(x,y)}{if $\texttt{src}(x,y) > \texttt{thresh}$}{0}{otherwise}
    THRESH_TOZERO_INV
    \texttt{dst} (x,y) =  \fork{0}{if $\texttt{src}(x,y) > \texttt{thresh}$}{\texttt{src}(x,y)}{otherwise}
    '''
    img2gray = cv2.cvtColor(img2 , cv2.COLOR_BGR2GRAY)
    ret , mask = cv2.threshold( img2gray , 175 , 255 , cv2.THRESH_BINARY )
    '''
    cv2.bitwise_not(src[,dst[,mask]]) ,
    src:输入数组,dst:输出数组(与src有同样的大小和类型),mask:可选择的操作掩码
    作用:按位取反
    dst(I) = 取反src(I)
    bitwise表示按位
    '''
    mask_inv = cv2.bitwise_not(mask)

    #下面就是讲ROI区域进行处理,取roi中与mask中不为零的值对应的像素的值,其他值为0
    #注意这里必须有mask=mask 或者mask=mask_inv,其中的mask= 不能忽略
    '''
    cv2.bitwise_and(src1 ,src2[,dst[,mask]])->dst
    src1:第一个输入数组或者标量
    src2:第二个数组
    src:单通道的输入数组
    value:标量值
    dst:输出数组
    mask:掩码
    计算按位与
    dst(I) = src1(I) & src2(I) , if mask(I) != 0
    '''
    #这里的roi是足球照片,用于背景,mask是logo的灰度图像,0是黑,255是白,也就是把白色部分的像素拿出来求与,其实就是把足球偏白色的部分拿出来
    img1_bg = cv2.bitwise_and(roi, roi , mask = mask)
    #取roi中与mask_inv中不为0的值对应的像素的值,其他值为0,把logo中黑色部分提取出来
    img2_fg = cv2.bitwise_and(img2 , img2 , mask = mask_inv)

    #将ROI中的logo和修改主要的图像
    dst = cv2.add(img1_bg , img2_fg)
    #替换原来的图像
    img1[0:rows , 0:cols] = dst
    cv2.imshow("res" , img1)
    cv2.waitKey(0)
    cv2.destroyAllWindows()








'''
Canny边缘检测:
原理:1986年由John F.Canny提出的算法
步骤:
1噪声去除
由于边缘检测容易受到噪声影响,因此需去除噪声,第一步是使用5*5的高斯滤波器
(先看5*5高斯滤波器)

2计算图像梯度
对平滑后的图像使用Sobel算子计算水平方向和竖直方向的一阶导数(图像梯度)
(Gx和Gy)。根据得到的这两幅梯度图(Gx和Gy)找到边界的梯度和方向,公式如下:
Edge_Gradient(G)=sqrt( Gx*Gx + Gy*Gy )
Angle( C塔 ) = tan-1(上标)(Gx/Gy)
梯度的方向一般总是与边界垂直。梯度方向有四类:垂直,水平,和两个对角线
(算子和梯度也需要看)

3非极大值抑制
获得梯度的方向和大小后,对整幅图像做扫描,去除非边界上的点。对每个像素检查,
看这个点的梯度是不是周围具有相同梯度方向中的点中最大的

4滞后阈值
需要设置两个阈值:minVal和maxVal。当图像的灰度梯度高于maxVal时被认为是真的
边界,低于minVal的边界会被抛弃。介于两者之间的话,需要看该点是否与某个被确定
为真正的边界点相连,如果是就认为它是边界点,否则就抛弃。
边界是长的线段。
'''





if __name__ == "__main__":
    #readImage_my()
    #imwrite_test()
    #modifyPixel()
    #imageROI()
    #mergeAndSplitImage()
    #imageAdd()
    #imageBlending()
    bitOperation()















'''
可能有用的一些内容(简要了解,后面需要时再仔细学习):
物体跟踪:应用提取某特定颜色的物体(从BGR转换到HSV后,HSV更容易表示特定颜色)
步骤:
1从视频中获取每一帧图像
2将图像红钻换到HSV空间
3设置HSV阈值到蓝色范围

学习轮廓后,可以找到物体的重心,根据重心来跟踪物体

几何变换:
1扩展缩放,cv2.resize(),缩放使用cv2.INTER_AREA,扩展时使用v2.INTER_CUBIC(慢)和v2.INTER_LINEAR(快)
默认改变图像尺寸大小插值方法是cv2.INTER_LINEAR

2平移:将对戏那个换一个位置,cv2.warpAffine(宽度,高度,输出图像的大小)
宽度对应列数,高度对应行数

3旋转:cv2.getRotationMatrix2

4仿射变换:原图中所有的平行线在结果图像中同样平行,为了创建这个矩阵需要从原图像
中找到三个点以及他们在输出图像中的位置
cv2.getAffineTransform , cv2.warpAffine

5透视变换
视角变换,需要3*3变换矩阵,在变换前后直线还是直线,要构建这个矩阵,需要在输入图像
中找4个点,以及他们在输出图像上对应的位置,4个点中任意三个不能共线
变换矩阵可通过cv2.getPerspectiveTransform()构建
然后矩阵传递给cv2.warpPerspective

'''''



'''
图像阈值:
当像素高于阈值时,给这个像素赋予新值
cv2.threshhold(),第一个参数是原图像,原图像应该是灰度图,第二个参数就是用来对像素值
进行分类的阈值。第三个参数是像素高于(或小于)阈值时应被赋予的新像素值

自适应阈值:
根据图像上的每一个小区域计算与其对应的阈值
Adaptive Method

Otsu's二值化
对一个双峰图像自动根据其直方图计算出其阈值
cv2.threshold(),多传入一个参数cv2.THRESH_OTSU,把阈值设为0

'''

'''
图像平滑(模糊)
使用低通滤波器对图像进行模糊,使用自定义的滤波器对图像进行卷积(2D卷积)

2D卷积:
低通滤波去除噪音,模糊图像,高通滤波找到图像的边缘
cv.filter2D()

图像模糊(图像平滑)
噪音是指高频成分,边界也会被模糊一点
平均:归一化卷积狂完成,用卷积框覆盖区域所有像素平均值来代替中心元素
可以使用函数cv2.blur()和cv2.boxFilter()来完成

高斯模糊:把卷积核换成高斯核,中心值最大,其余会随距离中心元素的距离递减

中值模糊:用卷积框对应像素的中值来代替中心像素的值

双边滤波

'''


'''
图像梯度:
梯度简单说就是求导
梯度滤波器也叫高通滤波器:Sobel,Scharr(求一阶导数),Laplacian算子(二阶导数)

'''



你可能感兴趣的:(Opencv3.0-python的那些事儿:(一)、Opencv的图像和视频处理基本用法)