模块cv2的用法
Posted 我的人生只需要一次成功
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模块cv2的用法相关的知识,希望对你有一定的参考价值。
安装
pip install opencv-python pip install opencv-python==3.3.0.10 -i https://pypi.doubanio.com/simple
一、读入图像
使用函数cv2.imread(filepath,flags)读入一副图片
- filepath:要读入图片的完整路径
- flags:读入图片的标志
- cv2.IMREAD_COLOR:默认参数,读入一副彩色图片,忽略alpha通道
- cv2.IMREAD_GRAYSCALE:读入灰度图片
- cv2.IMREAD_UNCHANGED:顾名思义,读入完整图片,包括alpha通道
import cv2 img = cv2.imread(\'1.jpg\',cv2.IMREAD_GRAYSCALE)
二、显示图像
使用函数cv2.imshow(wname,img)显示图像,第一个参数是显示图像的窗口的名字,第二个参数是要显示的图像(imread读入的图像),窗口大小自动调整为图片大小
cv2.imshow(\'image\',img) cv2.waitKey(0) cv2.destroyAllWindows() #dv2.destroyWindow(wname)
- cv2.waitKey顾名思义等待键盘输入,单位为毫秒,即等待指定的毫秒数看是否有键盘输入,若在等待时间内按下任意键则返回按键的ASCII码,程序继续运行。若没有按下任何键,超时后返回-1。参数为0表示无限等待。不调用waitKey的话,窗口会一闪而逝,看不到显示的图片。
- cv2.destroyAllWindow()销毁所有窗口
- cv2.destroyWindow(wname)销毁指定窗口
三、保存图像
使用函数cv2.imwrite(file,img,num)保存一个图像。第一个参数是要保存的文件名,第二个参数是要保存的图像。可选的第三个参数,它针对特定的格式:对于JPEG,其表示的是图像的质量,用0 - 100的整数表示,默认95;对于png ,第三个参数表示的是压缩级别。默认为3.
注意:
- cv2.IMWRITE_JPEG_QUALITY类型为 long ,必须转换成 int
- cv2.IMWRITE_PNG_COMPRESSION, 从0到9 压缩级别越高图像越小。
cv2.imwrite(\'1.png\',img, [int( cv2.IMWRITE_JPEG_QUALITY), 95]) cv2.imwrite(\'1.png\',img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
四、图片操作
1、使用函数cv2.flip(img,flipcode)翻转图像,flipcode控制翻转效果。
- flipcode = 0:沿x轴翻转
- flipcode > 0:沿y轴翻转
- flipcode < 0:x,y轴同时翻转
imgflip = cv2.flip(img,1)
2、复制图像
imgcopy = img.copy()
3、颜色空间转换
#彩色图像转为灰度图像 img2 = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY) #灰度图像转为彩色图像 img3 = cv2.cvtColor(img,cv2.COLOR_GRAY2RGB) # cv2.COLOR_X2Y,其中X,Y = RGB, BGR, GRAY, HSV, YCrCb, XYZ, Lab, Luv, HLS
示例
读入一副图像,按’s’键保存后退出,其它任意键则直接退出不保存
import cv2 img = cv2.imread(\'1.jpg\',cv2.IMREAD_UNCHANGED) cv2.imshow(\'image\',img) k = cv2.waitKey(0) if k == ord(\'s\'): # wait for \'s\' key to save and exit cv2.imwrite(\'1.png\',img) cv2.destroyAllWindows() else: cv2.destroyAllWindows()
示例
读入一副图像,给图片加文本
import cv2 # img=cv2.imread(\'1.jpg\',cv2.IMREAD_COLOR) img=cv2.imread(\'1.png\',cv2.IMREAD_COLOR) # 打开文件 font = cv2.FONT_HERSHEY_DUPLEX # 设置字体 # 图片对象、文本、像素、字体、字体大小、颜色、字体粗细 imgzi = cv2.putText(img, "zhengwen", (1100, 1164), font, 5.5, (0, 0, 0), 2,) # cv2.imshow(\'lena\',img) cv2.imwrite(\'5.png\',img) # 写磁盘 cv2.destroyAllWindows() # 毁掉所有窗口 cv2.destroyWindow(wname) # 销毁指定窗口
配合画图
import numpy as np import cv2 np.set_printoptions(threshold=\'nan\') # 创建一个宽512高512的黑色画布,RGB(0,0,0)即黑色 img=np.zeros((512,512,3),np.uint8) # 画直线,图片对象,起始坐标(x轴,y轴),结束坐标,颜色,宽度 cv2.line(img,(0,0),(311,511),(255,0,0),10) # 画矩形,图片对象,左上角坐标,右下角坐标,颜色,宽度 cv2.rectangle(img,(30,166),(130,266),(0,255,0),3) # 画圆形,图片对象,中心点坐标,半径大小,颜色,宽度 cv2.circle(img,(222,222),50,(255.111,111),-1) # 画椭圆形,图片对象,中心点坐标,长短轴,顺时针旋转度数,开始角度(右长轴表0度,上短轴表270度),颜色,宽度 cv2.ellipse(img,(333,333),(50,20),0,0,150,(255,222,222),-1) # 画多边形,指定各个点坐标,array必须是int32类型 pts=np.array([[10,5],[20,30],[70,20],[50,10]], np.int32) # -1表示该纬度靠后面的纬度自动计算出来,实际上是4 pts = pts.reshape((-1,1,2,)) # print(pts) # 画多条线,False表不闭合,True表示闭合,闭合即多边形 cv2.polylines(img,[pts],True,(255,255,0),5) #写字,字体选择 font=cv2.FONT_HERSHEY_SCRIPT_COMPLEX # 图片对象,要写的内容,左边距,字的底部到画布上端的距离,字体,大小,颜色,粗细 cv2.putText(img,"OpenCV",(10,400),font,3.5,(255,255,255),2) a=cv2.imwrite("picture.jpg",img) cv2.imshow("picture",img) cv2.waitKey(0) cv2.destroyAllWindows()
图像的表示
前面章节已经提到过了单通道的灰度图像在计算机中的表示,就是一个8位无符号整形的矩阵。在OpenCV的C++代码中,表示图像有个专门的结构叫做cv::Mat,不过在Python-OpenCV中,因为已经有了numpy这种强大的基础工具,所以这个矩阵就用numpy的array表示。如果是多通道情况,最常见的就是红绿蓝(RGB)三通道,则第一个维度是高度,第二个维度是高度,第三个维度是通道,比如图6-1a是一幅3×3图像在计算机中表示的例子:
图6-1 RGB图像在计算机中表示的例子
图6-1中,右上角的矩阵里每个元素都是一个3维数组,分别代表这个像素上的三个通道的值。最常见的RGB通道中,第一个元素就是红色(Red)的值,第二个元素是绿色(Green)的值,第三个元素是蓝色(Blue),最终得到的图像如6-1a所示。RGB是最常见的情况,然而在OpenCV中,默认的图像的表示确实反过来的,也就是BGR,得到的图像是6-1b。可以看到,前两行的颜色顺序都交换了,最后一行是三个通道等值的灰度图,所以没有影响。至于OpenCV为什么不是人民群众喜闻乐见的RGB,这是历史遗留问题,在OpenCV刚开始研发的年代,BGR是相机设备厂商的主流表示方法,虽然后来RGB成了主流和默认,但是这个底层的顺序却保留下来了,事实上Windows下的最常见格式之一bmp,底层字节的存储顺序还是BGR。OpenCV的这个特殊之处还是需要注意的,比如在Python中,图像都是用numpy的array表示,但是同样的array在OpenCV中的显示效果和matplotlib中的显示效果就会不一样。下面的简单代码就可以生成两种表示方式下,图6-1中矩阵的对应的图像,生成图像后,放大看就能体会到区别:
import numpy as np import cv2 import matplotlib.pyplot as plt # 图6-1中的矩阵 img = np.array([ [[255, 0, 0], [0, 255, 0], [0, 0, 255]], [[255, 255, 0], [255, 0, 255], [0, 255, 255]], [[255, 255, 255], [128, 128, 128], [0, 0, 0]], ], dtype=np.uint8) # 用matplotlib存储 plt.imsave(\'img_pyplot.jpg\', img) # 用OpenCV存储 cv2.imwrite(\'img_cv2.jpg\', img)
不管是RGB还是BGR,都是高度×宽度×通道数,H×W×C的表达方式,而在深度学习中,因为要对不同通道应用卷积,所以用的是另一种方式:C×H×W,就是把每个通道都单独表达成一个二维矩阵,如图6-1c所示。
6.2.2 基本图像处理
存取图像
读图像用cv2.imread(),可以按照不同模式读取,一般最常用到的是读取单通道灰度图,或者直接默认读取多通道。存图像用cv2.imwrite(),注意存的时候是没有单通道这一说的,根据保存文件名的后缀和当前的array维度,OpenCV自动判断存的通道,另外压缩格式还可以指定存储质量,来看代码例子:
import cv2 # 读取一张400x600分辨率的图像 color_img = cv2.imread(\'test_400x600.jpg\') print(color_img.shape) # 直接读取单通道 gray_img = cv2.imread(\'test_400x600.jpg\', cv2.IMREAD_GRAYSCALE) print(gray_img.shape) # 把单通道图片保存后,再读取,仍然是3通道,相当于把单通道值复制到3个通道保存 cv2.imwrite(\'test_grayscale.jpg\', gray_img) reload_grayscale = cv2.imread(\'test_grayscale.jpg\') print(reload_grayscale.shape) # cv2.IMWRITE_JPEG_QUALITY指定jpg质量,范围0到100,默认95,越高画质越好,文件越大 cv2.imwrite(\'test_imwrite.jpg\', color_img, (cv2.IMWRITE_JPEG_QUALITY, 80)) # cv2.IMWRITE_PNG_COMPRESSION指定png质量,范围0到9,默认3,越高文件越小,画质越差 cv2.imwrite(\'test_imwrite.png\', color_img, (cv2.IMWRITE_PNG_COMPRESSION, 5))
缩放,裁剪和补边
缩放通过cv2.resize()实现,裁剪则是利用array自身的下标截取实现,此外OpenCV还可以给图像补边,这样能对一幅图像的形状和感兴趣区域实现各种操作。下面的例子中读取一幅400×600分辨率的图片,并执行一些基础的操作:
import cv2 # 读取一张四川大录古藏寨的照片 img = cv2.imread(\'tiger_tibet_village.jpg\') # 缩放成200x200的方形图像 img_200x200 = cv2.resize(img, (200, 200)) # 不直接指定缩放后大小,通过fx和fy指定缩放比例,0.5则长宽都为原来一半 # 等效于img_200x300 = cv2.resize(img, (300, 200)),注意指定大小的格式是(宽度,高度) # 插值方法默认是cv2.INTER_LINEAR,这里指定为最近邻插值 img_200x300 = cv2.resize(img, (0, 0), fx=0.5, fy=0.5, interpolation=cv2.INTER_NEAREST) # 在上张图片的基础上,上下各贴50像素的黑边,生成300x300的图像 img_300x300 = cv2.copyMakeBorder(img, 50, 50, 0, 0, cv2.BORDER_CONSTANT, value=(0, 0, 0)) # 对照片中树的部分进行剪裁 patch_tree = img[20:150, -180:-50] cv2.imwrite(\'cropped_tree.jpg\', patch_tree) cv2.imwrite(\'resized_200x200.jpg\', img_200x200) cv2.imwrite(\'resized_200x300.jpg\', img_200x300) cv2.imwrite(\'bordered_300x300.jpg\', img_300x300)
这些处理的效果见图6-2。
色调,明暗,直方图和Gamma曲线
除了区域,图像本身的属性操作也非常多,比如可以通过HSV空间对色调和明暗进行调节。HSV空间是由美国的图形学专家A. R. Smith提出的一种颜色空间,HSV分别是色调(Hue),饱和度(Saturation)和明度(Value)。在HSV空间中进行调节就避免了直接在RGB空间中调节是还需要考虑三个通道的相关性。OpenCV中H的取值是[0, 180),其他两个通道的取值都是[0, 256),下面例子接着上面例子代码,通过HSV空间对图像进行调整:
# 通过cv2.cvtColor把图像从BGR转换到HSV img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # H空间中,绿色比黄色的值高一点,所以给每个像素+15,黄色的树叶就会变绿 turn_green_hsv = img_hsv.copy() turn_green_hsv[:, :, 0] = (turn_green_hsv[:, :, 0]+15) % 180 turn_green_img = cv2.cvtColor(turn_green_hsv, cv2.COLOR_HSV2BGR) cv2.imwrite(\'turn_green.jpg\', turn_green_img) # 减小饱和度会让图像损失鲜艳,变得更灰 colorless_hsv = img_hsv.copy() colorless_hsv[:, :, 1] = 0.5 * colorless_hsv[:, :, 1] colorless_img = cv2.cvtColor(colorless_hsv, cv2.COLOR_HSV2BGR) cv2.imwrite(\'colorless.jpg\', colorless_img) # 减小明度为原来一半 darker_hsv = img_hsv.copy() darker_hsv[:, :, 2] = 0.5 * darker_hsv[:, :, 2] darker_img = cv2.cvtColor(darker_hsv, cv2.COLOR_HSV2BGR) cv2.imwrite(\'darker.jpg\', darker_img)
无论是HSV还是RGB,我们都较难一眼就对像素中值的分布有细致的了解,这时候就需要直方图。如果直方图中的成分过于靠近0或者255,可能就出现了暗部细节不足或者亮部细节丢失的情况。比如图6-2中,背景里的暗部细节是非常弱的。这个时候,一个常用方法是考虑用Gamma变换来提升暗部细节。Gamma变换是矫正相机直接成像和人眼感受图像差别的一种常用手段,简单来说就是通过非线性变换让图像从对曝光强度的线性响应变得更接近人眼感受到的响应。具体的定义和实现,还是接着上面代码中读取的图片,执行计算直方图和Gamma变换的代码如下:
import numpy as np # 分通道计算每个通道的直方图 hist_b = cv2.calcHist([img], [0], None, [256], [0, 256]) hist_g = cv2.calcHist([img], [1], None, [256], [0, 256]) hist_r = cv2.calcHist([img], [2], None, [256], [0, 256]) # 定义Gamma矫正的函数 def gamma_trans(img, gamma): # 具体做法是先归一化到1,然后gamma作为指数值求出新的像素值再还原 gamma_table = [np.power(x/255.0, gamma)*255.0 for x in range(256)] gamma_table = np.round(np.array(gamma_table)).astype(np.uint8) # 实现这个映射用的是OpenCV的查表函数 return cv2.LUT(img, gamma_table) # 执行Gamma矫正,小于1的值让暗部细节大量提升,同时亮部细节少量提升 img_corrected = gamma_trans(img, 0.5) cv2.imwrite(\'gamma_corrected.jpg\', img_corrected) # 分通道计算Gamma矫正后的直方图 hist_b_corrected = cv2.calcHist([img_corrected], [0], None, [256], [0, 256]) hist_g_corrected = cv2.calcHist([img_corrected], [1], None, [256], [0, 256]) hist_r_corrected = cv2.calcHist([img_corrected], [2], None, [256], [0, 256]) # 将直方图进行可视化 import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() pix_hists = [ [hist_b, hist_g, hist_r], [hist_b_corrected, hist_g_corrected, hist_r_corrected] ] pix_vals = range(256) for sub_plt, pix_hist in zip([121, 122], pix_hists): ax = fig.add_subplot(sub_plt, projection=\'3d\') for c, z, channel_hist in zip([\'b\', \'g\', \'r\'], [20, 10, 0], pix_hist): cs = [c] * 256 ax.bar(pix_vals, channel_hist, zs=z, zdir=\'y\', color=cs, alpha=0.618, edgecolor=\'none\', lw=0) ax.set_xlabel(\'Pixel Values\') ax.set_xlim([0, 256]) ax.set_ylabel(\'Channels\') ax.set_zlabel(\'Counts\') plt.show()
上面三段代码的结果统一放在下图中:
可以看到,Gamma变换后的暗部细节比起原图清楚了很多,并且从直方图来看,像素值也从集中在0附近变得散开了一些。
6.2.3 图像的仿射变换
图像的仿射变换涉及到图像的形状位置角度的变化,是深度学习预处理中常到的功能,在此简单回顾一下。仿射变换具体到图像中的应用,主要是对图像的缩放,旋转,剪切,翻转和平移的组合。在OpenCV中,仿射变换的矩阵是一个2×3的矩阵,其中左边的2×2子矩阵是线性变换矩阵,右边的2×1的两项是平移项:
对于图像上的任一位置(x,y),仿射变换执行的是如下的操作:
需要注意的是,对于图像而言,宽度方向是x,高度方向是y,坐标的顺序和图像像素对应下标一致。所以原点的位置不是左下角而是右上角,y的方向也不是向上,而是向下。在OpenCV中实现仿射变换是通过仿射变换矩阵和cv2.warpAffine()这个函数,还是通过代码来理解一下,例子中图片的分辨率为600×400:
import cv2 import numpy as np # 读取一张斯里兰卡拍摄的大象照片 img = cv2.imread(\'lanka_safari.jpg\') # 沿着横纵轴放大1.6倍,然后平移(-150,-240),最后沿原图大小截取,等效于裁剪并放大 M_crop_elephant = np.array([ [1.6, 0, -150], [0, 1.6, -240] ], dtype=np.float32) img_elephant = cv2.warpAffine(img, M_crop_elephant, (400, 600)) cv2.imwrite(\'lanka_elephant.jpg\', img_elephant) # x轴的剪切变换,角度15° theta = 15 * np.pi / 180 M_shear = np.array([ [1, np.tan(theta), 0], [0, 1, 0] ], dtype=np.float32) img_sheared = cv2.warpAffine(img, M_shear, (400, 600)) cv2.imwrite(\'lanka_safari_sheared.jpg\', img_sheared) # 顺时针旋转,角度15° M_rotate = np.array([ [np.cos(theta), -np.sin(theta), 0], [np.sin(theta), np.cos(theta), 0] ], dtype=np.float32) img_rotated = cv2.warpAffine(img, M_rotate, (400, 600)) cv2.imwrite(\'lanka_safari_rotated.jpg\', img_rotated) # 某种变换,具体旋转+缩放+旋转组合可以通过SVD分解理解 M = np.array([ [1, 1.5, -400], [0.5, 2, -100] ], dtype=np.float32) img_transformed = cv2.warpAffine(img, M, (400, 600)) cv2.imwrite(\'lanka_safari_transformed.jpg\', img_transformed)
代码实现的操作示意在下图中:
6.2.4 基本绘图
OpenCV提供了各种绘图的函数,可以在画面上绘制线段,圆,矩形和多边形等,还可以在图像上指定位置打印文字,比如下面例子:
import numpy as np import cv2 # 定义一块宽600,高400的画布,初始化为白色 canvas = np.zeros((400, 600, 3), dtype=np.uint8) + 255 # 画一条纵向的正中央的黑色分界线 cv2.line(canvas, (300, 0), (300, 399), (0, 0, 0), 2) # 画一条右半部份画面以150为界的横向分界线 cv2.line(canvas, (300, 149), (599, 149), (0, 0, 0), 2) # 左半部分的右下角画个红色的圆 cv2.circle(canvas, (200, 300), 75, (0, 0, 255), 5) # 左半部分的左下角画个蓝色的矩形 cv2.rectangle(canvas, (20, 240), (100, 360), (255, 0, 0), thickness=3) # 定义两个三角形,并执行内部绿色填充 triangles = np.array([ [(200, 240), (145, 333), (255, 333)], [(60, 180), (20, 237), (100, 237)]]) cv2.fillPoly(canvas, triangles, (0, 255, 0)) # 画一个黄色五角星 # 第一步通过旋转角度的办法求出五个顶点 phi = 4 * np.pi / 5 rotations = [[[np.cos(i * phi), -np.sin(i * phi)], [i * np.sin(phi), np.cos(i * phi)]] for i in range(1, 5)] pentagram = np.array([[[[0, -1]] + [np.dot(m, (0, -1)) for m in rotations]]], dtype=np.float) # 定义缩放倍数和平移向量把五角星画在左半部分画面的上方 pentagram = np.round(pentagram * 80 + np.array([160, 120])).astype(np.int) # 将5个顶点作为多边形顶点连线,得到五角星 cv2.polylines(canvas, pentagram, True, (0, 255, 255), 9) # 按像素为间隔从左至右在画面右半部份的上方画出HSV空间的色调连续变化 for x in range(302, 600): color_pixel = np.array([[[round(180*float(x-302)/298), 255, 255]]], dtype=np.uint8) line_color = [int(c) for c in cv2.cvtColor(color_pixel, cv2.COLOR_HSV2BGR)[0][0]] cv2.line(canvas, (x, 0), (x, 147), line_color) # 如果定义圆的线宽大于半斤,则等效于画圆点,随机在画面右下角的框内生成坐标 np.random.seed(42) n_pts = 30 pts_x = np.random.randint(310, 590, n_pts) pts_y = np.random.randint(160, 390, n_pts) pts = zip(pts_x, pts_y) # 画出每个点,颜色随机 for pt in pts: pt_color = [int(c) for c in np.random.randint(0, 255, 3)] cv2.circle(canvas, pt, 3, pt_color, 5) # 在左半部分最上方打印文字 cv2.putText(canvas, \'Python-OpenCV Drawing Example\', (5, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1) cv2.imshow(\'Example of basic drawing functions\', canvas) cv2.waitKey()
执行这段代码得到如下的图像:
6.2.4 视频功能
视频中最常用的就是从视频设备采集图片或者视频,或者读取视频文件并从中采样。所以比较重要的也是两个模块,一个是VideoCapture,用于获取相机设备并捕获图像和视频,或是从文件中捕获。还有一个VideoWriter,用于生成视频。还是来看例子理解这两个功能的用法,首先是一个制作延时摄影视频的小例子:
import cv2 import time interval = 60 # 捕获图像的间隔,单位:秒 num_frames = 500 # 捕获图像的总帧数 out_fps = 24 # 输出文件的帧率 # VideoCapture(0)表示打开默认的相机 cap = cv2.VideoCapture(0) # 获取捕获的分辨率 size =(int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))) # 设置要保存视频的编码,分辨率和帧率 video = cv2.VideoWriter( "time_lapse.avi", cv2.VideoWriter_fourcc(\'M\',\'P\',\'4\',\'2\'), out_fps, size ) # 对于一些低画质的摄像头,前面的帧可能不稳定,略过 for i in range(42): cap.read() # 开始捕获,通过read()函数获取捕获的帧 try: for i in range(num_frames): _, frame = cap.read() video.write(frame) # 如果希望把每一帧也存成文件,比如制作GIF,则取消下面的注释 # filename = \'{:0>6d}.png\'.format(i) # cv2.imwrite(filename, frame) print(\'Frame {} is captured.\'.format(i)) time.sleep(interval) except KeyboardInterrupt: # 提前停止捕获 print(\'Stopped! {}/{} frames captured!\'.format(i, num_frames)) # 释放资源并写入视频文件 video.release() cap.release()
这个例子实现了延时摄影的功能,把程序打开并将摄像头对准一些缓慢变化的画面,比如桌上缓慢蒸发的水,或者正在生长的小草,就能制作出有趣的延时摄影作品。比如下面这个链接中的图片就是用这段程序生成的:
http://images.cnitblog.com/blog2015/609274/201503/251904209276278.gif
程序的结构非常清晰简单,注释里也写清楚了每一步,所以流程就不解释了。需要提一下的有两点:一个是VideoWriter中的一个函数cv2.VideoWriter_fourcc()。这个函数指定了视频编码的格式,比如例子中用的是MP42,也就是MPEG-4,更多编码方式可以在下面的地址查询:
还有一个是KeyboardInterrupt,这是一个常用的异常,用来获取用户Ctrl+C的中止,捕获这个异常后直接结束循环并释放VideoCapture和VideoWriter的资源,使已经捕获好的部分视频可以顺利生成。
从视频中截取帧也是处理视频时常见的任务,下面代码实现的是遍历一个指定文件夹下的所有视频并按照指定的间隔进行截屏并保存:
import cv2 import os import sys # 第一个输入参数是包含视频片段的路径 input_path = sys.argv[1] # 第二个输入参数是设定每隔多少帧截取一帧 frame_interval = int(sys.argv[2]) # 列出文件夹下所有的视频文件 filenames = os.listdir(input_path) # 获取文件夹名称 video_prefix = input_path.split(os.sep)[-1] # 建立一个新的文件夹,名称为原文件夹名称后加上_frames frame_path = \'{}_frames\'.format(input_path) if not os.path.exists(frame_path): os.mkdir(frame_path) # 初始化一个VideoCapture对象 cap = cv2.VideoCapture() # 遍历所有文件 for filename in filenames: filepath = os.sep.join([input_path, filename]) # VideoCapture::open函数可以从文件获取视频 cap.open(filepath) # 获取视频帧数 n_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 同样为了避免视频头几帧质量低下,黑屏或者无关等 for i in range(42): cap.read() for i in range(n_frames): ret, frame = cap.read() # 每隔frame_interval帧进行一次截屏操作 if i % frame_interval == 0: imagename = \'{}_{}_{:0>6d}.jpg\'.format(video_prefix, filename.split(\'.\')[0], i) imagepath = os.sep.join([frame_path, imagename]) print(\'exported {}!\'.format(imagepath)) cv2.imwrite(imagepath, frame) # 执行结束释放资源 cap.release()
6.3 用OpenCV实现数据增加小工具
到目前我们已经熟悉了numpy中的随机模块,多进程调用和OpenCV的基本操作,基于这些基础,本节将从思路到代码一步步实现一个最基本的数据增加小工具。
第三章和第四章都提到过数据增加(data augmentation),作为一种深度学习中的常用手段,数据增加对模型的泛化性和准确性都有帮助。数据增加的具体使用方式一般有两种,一种是实时增加,比如在Caffe中加入数据扰动层,每次图像都先经过扰动操作,再去训练,这样训练经过几代(epoch)之后,就等效于数据增加。还有一种是更加直接简单一些的,就是在训练之前就通过图像处理手段对数据样本进行扰动和增加,也就是本节要实现的。
这个例子中将包含三种基本类型的扰动:随机裁剪,随机旋转和随机颜色/明暗。
6.3.1 随机裁剪
AlexNet中已经讲过了随机裁剪的基本思路,我们的小例子中打算更进一步:在裁剪的时候考虑图像宽高比的扰动。在绝大多数用于分类的图片中,样本进入网络前都是要变为统一大小,所以宽高比扰动相当于对物体的横向和纵向进行了缩放,这样除了物体的位置扰动,又多出了一项扰动。只要变化范围控制合适,目标物体始终在画面内,这种扰动是有助于提升泛化性能的。实现这种裁剪的思路如下图所示:
图中最左边是一幅需要剪裁的画面,首先根据这幅画面我们可以算出一个宽高比w/h。然后设定一个小的扰动范围δ和要裁剪的画面占原画面的比例β,从-到之间按均匀采样,获取一个随机数作为裁剪后画面的宽高比扰动的比例,则裁剪后画面的宽和高分别为:
想象一下先把这个宽为w’,高为h’的区域置于原画面的右下角,则这个区域的左上角和原画面的左上角框出的小区域,如图中的虚线框所示,就是裁剪后区域左上角可以取值的范围。所以在这个区域内随机采一点作为裁剪区域的左上角,就实现了如图中位置随机,且宽高比也随机的裁剪。
6.3.2 随机旋转
前面讲到过的旋转比起来,做数据增加时,一般希望旋转是沿着画面的中心。这样除了要知道旋转角度,还得计算平移的量才能让仿射变换的效果等效于旋转轴在画面中心,好在OpenCV中有现成的函数cv2.getRotationMatrix2D()可以使用。这个函数的第一个参数是旋转中心,第二个参数是逆时针旋转角度,第三个参数是缩放倍数,对于只是旋转的情况下这个值是1,返回值就是做仿射变换的矩阵。
直接用这个函数并接着使用cv2.warpAffine()会有一个潜在的问题,就是旋转之后会出现黑边。如果要旋转后的画面不包含黑边,就得沿着原来画面的轮廓做个内接矩形,该矩形的宽高比和原画面相同,如下图所示:
在图中,可以看到,限制内接矩形大小的主要是原画面更靠近中心的那条边,也就是图中比较长的一条边AB。因此我们只要沿着中心O和内接矩形的顶点方向的直线,求出和AB的交点P,就得到了内接矩形的大小。先来看长边的方程,考虑之前画面和横轴相交的点,经过角度-θ旋转后,到了图中的Q点所在:
因为长边所在直线过Q点,且斜率为1/tan(θ),所以有:
这时候考虑OP这条直线:
把这个公式带入再前边一个公式,求解可以得到:
注意到在这个问题中,每个象限和相邻象限都是轴对称的,而且旋转角度对剪裁宽度和长度的影响是周期(T=π)变化,再加上我们关心的其实并不是四个点的位置,而是旋转后要截取的矩形的宽w’和高h’,所以复杂的分区间情况也简化了,首先对于旋转角度,因为周期为π,所以都可以化到0到π之间,然后因为对称性,进一步有:
于是对于0到π/2之间的θ,有:
当然需要注意的是,对于宽高比非常大或者非常小的图片,旋转后如果裁剪往往得到的画面是非常小的一部分,甚至不包含目标物体。所以是否需要旋转,以及是否需要裁剪,如果裁剪角度多少合适,都要视情况而定。
6.3.3 随机颜色和明暗
比起AlexNet论文里在PCA之后的主成分上做扰动的方法,本书用来实现随机的颜色以及明暗的方法相对简单很多,就是给HSV空间的每个通道,分别加上一个微小的扰动。其中对于色调,从-到之间按均匀采样,获取一个随机数作为要扰动的值,然后新的像素值x’为原始像素值x +;对于其他两个空间则是新像素值x’为原始像素值x的(1+)倍,从而实现色调,饱和度和明暗度的扰动。
因为明暗度并不会对图像的直方图相对分布产生大的影响,所以在HSV扰动基础上,考虑再加入一个Gamma扰动,方法是设定一个大于1的Gamma值的上限γ,因为这个值通常会和1是一个量级,再用均匀采样的近似未必合适,所以从-logγ到logγ之间均匀采样一个值α,然后用
作为Gamma值进行变换。
6.3.4 多进程调用加速处理
做数据增加时如果样本量本身就不小,则处理起来可能会很耗费时间,所以可以考虑利用多进程并行处理。比如我们的例子中,设定使用场景是输入一个文件夹路径,该文件夹下包含了所有原始的数据样本。用户指定输出的文件夹和打算增加图片的总量。执行程序的时候,通过os.listdir()获取所有文件的路径,然后按照上一章讲过的多进程平均划分样本的办法,把文件尽可能均匀地分给不同进程,进行处理。
6.3.5 代码:图片数据增加小工具
按照前面4个部分的思路和方法,这节来实现这么一个图片数据增加小工具,首先对于一些基础的操作,我们定义在一个叫做image_augmentation.py的文件里:
import numpy as np import cv2 \'\'\' 定义裁剪函数,四个参数分别是: 左上角横坐标x0 左上角纵坐标y0 裁剪宽度w 裁剪高度h \'\'\' crop_image = lambda img, x0,以上是关于模块cv2的用法的主要内容,如果未能解决你的问题,请参考以下文章
OpenCV AttributeError 模块“cv2.cv2”没有属性“Tracker_create”
在 Jetson Nano 上安装 OpenCV 4.1.2 时出现问题。导入 cv2,没有名为“cv2”的模块