2 opencv-python核心库模块core(上)
core模块定义了opencv中的基础数据结构和基础运算,是整个库的核心模块。而mat数据结构是opencv中最重要的数据结构,是opencv中图像最常用的存储格式。本章节主要记录opencv的基本数据结构,图像的裁剪和缩放,矩阵的简单运算,图像通道分解合并以及几个有趣的实验。
1 基本数据结构
opencv的基本数据结构有mat数据结构,point数据结构,rect数据结构,size数据结构。
1)在python中,mat类型的对象构造操作可以通过numpy来实现(创建图像矩阵)
如下代码构建全0矩阵m1,然后将所有值初始化为128,还可以用copy进行矩阵拷贝,也可以对图像数据的部分原始值进行修改。
import cv2 import numpy as np #python中,mat类型的对象通过numpy来实现 m1 = np.zeros([512,512],np.uint8) #创建全0二维矩阵 m1[:] = 128 #初始化赋值 m2 = m1.copy() #把m1图像拷贝到m2 m2[128:384,128:384] = 0 #将行,列为128到384的区域像素值赋值为0 cv2.imshow('m2',m2) cv2.imshow('m1',m1) cv2.waitKey(0) cv2.destroyAllWindows()
2 图像的裁剪和缩放
可以对图像矩阵进行裁剪和缩放,裁剪直接用python的切片来完成,缩放可以用opencv的 resize() 函数来操作。resize函数介绍如下:
cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) -> dst
参数说明:
src:原图像;dst:改变大小的目标图像;dsize:输出图像的大小。
fx:width方向的缩放比例;fy:height方向的缩放比例;
interpolation:指定插值的方式,图像resize之后,像素要重新计算,靠这个参数来指定重新计算像素的方式,有以下几种:
INTER_NEAREST - 最邻近插值
INTER_LINEAR - 双线性插值,如果最后一个参数你不指定,默认使用这种方法
INTER_AREA -区域插值,使用像素区域关系进行重采样(效果最好)
INTER_CUBIC - 4x4像素邻域内的双立方插值
INTER_LANCZOS4 - 8x8像素邻域内的Lanczos插值
有两点需要注意:1 dsize和fx/fy不能同时为0。指定dsize的值,让fx和fy直接省略或置0 或者 dsize=None或置 (0,0),指定fx和fy的值,(dsize不可省略,会报错)。2 图像的size是(h,w),而
resize的操作是先w,再h。
import cv2 #图像裁剪和缩放 import numpy as np img = cv2.imread('cat.jpg') cv2.imshow('cat',img) rows = img.shape[0] #图像的行(高) [h,w,c] cols = img.shape[1] #图像的列(宽) img2 = img.copy() #把img1图像拷贝到img2 img2 = cv2.resize(img2,(int(cols/2),int(rows/2))) #图像尺寸减小为原来的一半,不是裁剪 [w,h] cv2.imshow('img2',img2) img3 = img[0:int(rows/2),0:int(cols/2)] #图像裁剪为原来的1/4,用切片来裁剪,cropped_image = img[80:280, 150:330] 切片img[高,宽] cv2.imshow('img3',img3) cv2.waitKey(0) cv2.destroyAllWindows()
2)在opencv中,点的定义由point类实现。python语言中,point类型可以由tuple元组表示,然后利用numpy进行点乘和叉乘计算。
import numpy as np #tuple元组点乘叉乘(向量内积/外积) point1 = (10,20) point2 = (2,4) print(type(point1)) #tuple(元组,有序且不可更改的集合)类型, # 元组与列表相似,不同之处在于元组的元素不能修改,而列表的元素可以修改。元组使用小括号(),列表使用中括号[]。 res1 = np.dot(point1,point2) #点乘 print('res1',res1) point3 = (1,2,3) point4 = (4,5,6) res2 = np.cross(point3,point4) #叉乘,输出一个向量,方向垂直于输入两向量形成的平面 print('res2',res2)
3)opencv中定义了矩形的表示类rect,模板类rect定义了顶点坐标,宽高,面积等信息的获取操作,以及矩形是否包含点的判断操作。在python中的rect也是通过tuple类型的数据表示,即左上角坐标点(x,y)和矩形宽高4个值表示的rect为(x,y,w,h)的形式,在需要传入rect数据类型的地方,可以按照tuple类型的数据传入。
4)opencv的很多函数中都需要传入size类型的参数,比如mat类的后置函数为mat(Size size,int type),需要通过size类型对象传入构造矩阵的尺寸。size类型对象包含两个成员变量width和height。python中的size对象也是通过tuple类型的数据表示,即 (w,h) 的形式传入宽高数据的,在需要传入size数据类型的地方,可以按照tuple类型的数据传入。
3 矩阵运算
core模块中提供了图像矩阵的基本运算,opencv对这些基本运算都做了封装,比如四则运算,位运算,比较运算等。
1)四则运算
加法:dst = add(src1, src2, mask=None) 减法:dst = subtract(src1, src2, mask=None) 乘法:dst = multiply(src1, src2) 除法:dst = divide(src1, src2)
import cv2 #矩阵加减乘除,相当于对矩阵中的每个元素进行对应操作 import numpy as np m1 = np.array([[1,2,3],[4,5,6],[7,8,9]],dtype=float) #创建ndarray二维数组(矩阵),数据类型为浮点数 m2 = np.array([[11,12,13],[14,15,16],[17,18,19]],dtype=float) m_add = cv2.add(m1,m2) #等同于m_add = m1 + m2 print('add:',m_add) m_sub = cv2.subtract(m1,m2) #等同于m_sub = m1 - m2 print('subtract:',m_sub) m_mul = cv2.multiply(m1,m2) #等同于m_mul = m1 * m2 print('multiply:',m_mul) m_div = cv2.divide(m1,m2) #等同于m_div = m1 / m2 print('divide:',m_div) m_num = m1+5 #直接加减数字相当于矩阵中的元素都加减该数字 m_num2 = cv2.add(m1,5) print('m_num:\n',m_num) print('m_num2:\n',m_num2)
2) 位运算
opencv中定义了位运算函数:
按位与:dst = bitwise_and(src1, src2, mask=None) 按位或:dst = bitwise_or(src1,src2, mask=None) 按位异或:dst = bitwise_xor(src1, src2, mask=None) 非运算:dst = bitwise_not(src, mask=None)
import cv2 #矩阵/图像位运算 import numpy as np m1 = np.array([[1,2],[3,1]]) m2 = np.array([[5,3],[4,6]]) m_and = cv2.bitwise_and(m1,m2) #按位与(转换为二进制,对应位均为1时,结果为1,其余为0) print('m_and:',m_and) m_or = cv2.bitwise_or(m1,m2) #按位或(如果两个相应的二进制位有一个为1,则该结果为1,否则为0) print('m_or:',m_or) m_xor = cv2.bitwise_xor(m1,m2) #异或运算(如果两个相应的二进制位值不同则为1,否则为0) print('m_xor:',m_xor) m_not = cv2.bitwise_not(m1) #非运算(转换为二进制,对应位取反) print('m_not:',m_not)
3)代数运算
计算矩阵均值的函数:retval = mean(src,mask=None)
对矩阵进行归一化:dst = normalize(src, dst, alpha=None, beta=None, norm_type=None, dtype=None, mask=None) alpha表示归一化的上界,beta表示归一化的下界。norm_type表示归一化类型(归一化是机器学习和深度学习很重要的一个技巧,可以减小数据之间的差异对模型训练结果的影响,还可以加速模型收敛)
平方根:dst = sqrt(src) 幂运算:dst = pow(src, power) 也可以直接 src**power 指数运算:dst = exp(src) 对数运算:dst = log(src)
import cv2 import numpy as np #矩阵代数运算 m1 = np.array([[1,2,3],[3,4,5]],dtype=float) mean_res = cv2.mean(m1) #矩阵均值返回值类型是tuple,有四个元素,第一个是均值 print('the mean is:',mean_res) print(type(mean_res)) dst = np.array([]) #均值归一化参数的返回值需要一个对象 norm_res = cv2.normalize(m1,dst,norm_type=cv2.NORM_MINMAX) #线性归一化 print('norm_res:',norm_res) sqrt_res = cv2.sqrt(m1) print('sqrt_res:',sqrt_res) #平方根 pow_res = cv2.pow(m1,3) print('pow:',(m1)**3) #幂运算 print('pow_res:',pow_res) #幂运算 exp_res = cv2.exp(m1) #指数运算 print('exp_res:',exp_res) log_res = cv2.log(m1) print('log_res:',log_res) #对数运算
4) 比较运算
两个矩阵的比较:dst = compare(src1, src2, cmpop) cmpop表示比较方式。(CMP_EQ=0,相等;CMP_GT=1, 大于等于;CMP_GE=2,大于;CMP_LT=3, 小于等于;CMP_LE=4, 小于;CMP_NE=5, 不等于) 求最大值:dst = max(src1, src2) 求最小值:dst = min(src1, src2) 还有一个比较有用的函数:minval, maxval, minloc,maxloc = minMaxLoc(src, mask=None) 可以输出矩阵最大最小值和其对应的位置。
矩阵排序:dst = sort(src, flags) flags表示排序方式,由SortFlags定义。
import cv2 import numpy as np #矩阵比较运算 m1 = np.array([[1,6,3],[15,12,5]],dtype=float) m2 = np.array([[9,5,3],[14,12,16]],dtype=float) compare_res1 = cv2.compare(m1,m2,cv2.CMP_EQ) #比较两个矩阵对应的元素是否相等,等返回255,不等返回0 compare_res2 = cv2.compare(m1,m2,cv2.CMP_GT) #判断m1的元素是否大于等于m2,是返回255,非返回0 max_res =cv2.max(m1,m2) #求两个矩阵中的最大值,元素比较 min_res =cv2.min(m1,m2) #求两个矩阵中的最小值,元素比较 minmax_loc = cv2.minMaxLoc(m1) #求矩阵中的最小值,最大值和对应的坐标,不是下标(cv是列优先,(0,2)代表第一列第三行) #OpenCV读取的图像矩阵坐标与图像坐标系下的坐标横纵正好相反 sort_res = cv2.sort(m1,cv2.SORT_ASCENDING) #升序排序 print('res1:',compare_res1) print('res2:',compare_res2) print('max_res:',max_res) print('min_res:',min_res) print('minmax_loc:',minmax_loc) print('sort_res:',sort_res) print('m1[1][0]:',m1[1][0])
5)特征值和特征向量
特征值和特征向量在机器学习中很常见,opencv提供了函数eigen来计算对称矩阵的特征值和特征向量,非对称矩阵用eigenNonSymmetric。
retval, eigenvalues, eigenvectors = eigen(src) retval是返回状态,eigenvalues是返回的特征值,eigenvectors 是返回的特征向量。
opencv提供了生成随机数矩阵的函数randn和randu,randn生成的矩阵服从正态分布,randu生成的矩阵服从均匀分布。dst = randn(dst, mean, stddev) mean表示均值,stddev表示随机数的标准差。dst = randu(dst, low, high) low表示随机数的生成下界,high表示随机数生成的上界。
opencv还提供了将矩阵的行/列视为一组一维向量,并对向量执行指定的操作(REDUCE_AVG=1, 计算均值;REDUCE_MAX=2, 计算最大值;REDUCE_MIN=3, 计算最小值;REDUCE_SUM=0, 计算行/列的和),直到获得单个行/列,从而将矩阵缩减为一个向量。 dst = reduce(src, dim, rtype) dim为0表示矩阵降维为单行,1表示矩阵降维为单列。
import cv2 #生成随机数矩阵,计算矩阵的特征值和特征向量,矩阵转向量 import numpy as np m1 = np.zeros((2,3),dtype=np.int) #创建全0矩阵整型 m2 = np.ones((3,3),dtype=np.float) #创建全1矩阵浮点型 m1 = cv2.randn(m1,3,1) #生成服从正态分布的矩阵,均值3,标准差1 m2 = cv2.randu(m2,1,10) #生成服从均匀分布的矩阵,下界1,上界10 print('m1:\n',m1,'\nm2:\n',m2) eigen_res = cv2.eigen(m2) #计算对称矩阵的特征值和特征向量,矩阵必须是方阵,元素是float print('eigen_res:\n',eigen_res) eigen_res2 = cv2.eigenNonSymmetric(m2) ##计算非对称矩阵的特征值和特征向量,矩阵必须是方阵,元素是float print('eigen_res2:\n',eigen_res2) img = cv2.imread('./cat.jpg') dst = cv2.reduce(img,0,cv2.REDUCE_AVG) #矩阵转向量,对矩阵的行/列视为一组向量单独操作(取均值,最大,最小,和),参数0表示降为单行,1为单列 #每行/列返回一个值,从而将矩阵缩减为一个向量 print('dst:',dst.shape) #将输入矩阵的每一列取均值,组成一个单行的向量 print('cat:',img.shape)
6)图像通道分离与通道合并
对于一幅RGB格式的图像,包括R,G,B三个通道,opencv提供了通道分离函数split函数,和通道合并函数merge。
通道分离:b,g,r = split(img) 通道合并:dst = merge(mv) mv = [b,g,r]
import cv2 #图像通道分离/合并 import numpy as np img = cv2.imread('cat.jpg') #img[:,:,0]=0 #img[]前两个参数是指的h,w,冒号指全部取得,最后一个参数是指的通道序列号,图像的通道为BGR,分别对应012 b,g,r = cv2.split(img) #图像通道分离 cv2.imshow('cat',img) cv2.imshow('img_r',r) #显示红色通道图像,单通道图像是灰色 cv2.imshow('img_g',g) cv2.imshow('img_b',b) g[:] = 0 #绿色图像设置为0 b[:] = 0 #蓝色图像设置为0 merge_res = cv2.merge([b,g,r]) #图像通道合并,只剩下红色通道没变,图像会显示红色 cv2.imshow('merge:',merge_res) cv2.waitKey(0) cv2.destroyAllWindows()
有趣的事1:对单通道进行运算,用来改变图像的整体颜色:
import cv2 #对图像单通道进行操作 img = cv2.imread('./cat.jpg') print(img.shape) b,g,r = cv2.split(img) r2 = cv2.add(r,30) #只增加红色通道的像素值,给图像加红 b2 = cv2.add(r,30) #只增加蓝色通道的像素值,给图像加蓝 img2 = cv2.merge([b,g,r2]) img3 = cv2.merge([b2,g,r]) #img2 = cv2.add(img,-30) #用cv的add算法加减,只有第一列r像素值改变了,bg通道像素值不变,且像素值最大255,最小0(加变红紫,减变黄) #img3 = img - 30 #直接用+号加减数字,所有像素值都改变了,且超过255的像素值对256取余。 cv2.imshow('img',img) cv2.imshow('red',img2) cv2.imshow('blue',img3) cv2.waitKey(0) cv2.destroyAllWindows()
有趣的事2:计算一幅rgb图像中有多少种不同的颜色:
首先,什么是不同的颜色:对rgb图像来说,只要红绿蓝三个通道的任何一个通道的像素值不相同,那么就是不同的颜色。也就是说,要计算有多少中颜色,就是要计算三通道的不同像素值的排列组合有多少种。用split把图像分为三个通道brg,每个通道都是m*n的矩阵,每个矩阵的像素值范围是0-255。统计每个通道(矩阵)内不重复元素的个数(1 可以用count函数统计每个像素值出现的次数,然后相加。2 先用set集合去重,再用len统计个数。3 想要统计每个元素出现的次数,可以使用collections模块提供的Counter函数。4 numpy中的unique()函数可以用来统计一个数组中不同元素的个数,此处使用unique,然后用len计算),然后把三个通道的统计数相乘就可以了。
import cv2 import numpy as np img = cv2.imread('./cat.jpg') b,g,r = cv2.split(img) count_b = len(np.unique(b)) count_g = len(np.unique(g)) count_r = len(np.unique(r)) count = count_b * count_g * count_r print('color number:',count) #color number: 16386304
有趣的事3:利用进度条实现一个简单的颜色混合器(调色板),即在图像窗口中创建三个进度条,进度条的值为0-255,3个进度条的值对于图像的RGB 3个通道的值,调节进度条可以查看颜色混合后的效果。
opencv提供了创建进度条的函数 createTrackbar,createTrackbar(trackbarName, windowname, minval, maxval, callback)
opencv还提供了用于获取进度条位置的函数 getTrackbarPos,retval = getTrackbarPos(trackbarname, winname)
import cv2 import numpy as np #利用trackbar显示RGB颜色通道,方法一 cv2.namedWindow('trackbar',cv2.WINDOW_NORMAL) #定义窗口 cv2.resizeWindow('trackbar',640,480) def callback(value): #回调函数(回调函数不做操作) pass cv2.createTrackbar('R','trackbar',0,255,callback) #定义三个trackbar cv2.createTrackbar('G','trackbar',0,255,callback) cv2.createTrackbar('B','trackbar',0,255,callback) img = np.zeros((480,640,3),np.uint8) #定义一个全黑图像 while True: r = cv2.getTrackbarPos('R','trackbar') #循环获取trackbar的返回值 g = cv2.getTrackbarPos('G','trackbar') b = cv2.getTrackbarPos('B','trackbar') img[:] = [b,g,r] #给图像的brg三个颜色通道赋值
cv2.imshow('trackbar',img) #显示赋值后的当前图像
if cv2.waitKey(1) == ord('q'): #等待1ms或者键盘输入q进入下一次循环 break cv2.destroyAllWindows()
可以把获取进度条的值和把值赋给图像的代码放到回调函数中去:
import cv2 import numpy as np #利用trackbar显示RGB颜色通道,方法二 cv2.namedWindow('trackbar',cv2.WINDOW_NORMAL) cv2.resizeWindow('trackbar',(640,480)) def callback(pos): r = cv2.getTrackbarPos('R','trackbar') #回调函数(把获取进度条的值放在回调函数中) g = cv2.getTrackbarPos('G','trackbar') b = cv2.getTrackbarPos('B','trackbar') img[:] = [b,g,r] #给图像的三个颜色通道赋值 cv2.imshow('trackbar',img) #只要trackbar的值改变,就重置图片颜色 img = np.zeros((480,640,3),np.uint8) cv2.createTrackbar('R','trackbar',0,255,callback) cv2.createTrackbar('G','trackbar',0,255,callback) cv2.createTrackbar('B','trackbar',0,255,callback) while True: cv2.imshow('trackbar',img) #循环显示图片 key = cv2.waitKey(0) if key == ord('q'): break cv2.destroyAllWindows()
两种方法都可以实现拖动进度条来达到颜色混合的目的,如下:
利用进度条直接改变图片的颜色呢,比如直接通过进度条来加红加蓝,然后观察效果。
import cv2 import numpy as np #输入图片,将图片整体加红/加绿/加蓝 cv2.namedWindow('change_color',cv2.WINDOW_NORMAL) cv2.resizeWindow('change_color',(600,600)) def callback(value): #回调函数 pass cv2.createTrackbar('R','change_color',0,255,callback) #创建三个进度条 cv2.createTrackbar('G','change_color',0,255,callback) cv2.createTrackbar('B','change_color',0,255,callback) img = cv2.imread('./cat.jpg') while True: r = cv2.getTrackbarPos('R','change_color') g = cv2.getTrackbarPos('G','change_color') b = cv2.getTrackbarPos('B','change_color') img_b,img_g,img_r = cv2.split(img) #只增加红色通道的像素值,给图像加红 img_r = cv2.add(img_r,r) img2 = cv2.merge([img_b,img_g,img_r]) img_b,img_g,img_r = cv2.split(img2) #只增加绿色通道的像素值,给图像加绿 img_g = cv2.add(img_g,g) img3 = cv2.merge([img_b,img_g,img_r]) img_b,img_g,img_r = cv2.split(img3) #只增加蓝色通道的像素值,给图像加蓝 img_b = cv2.add(img_b,b) img4 = cv2.merge([img_b,img_g,img_r]) cv2.imshow('change_color',img4) key = cv2.waitKey(1) if key == ord('q'): break cv2.destroyAllWindows()