cv2

用 OpenCV 开发难免会针对 Camera 或者是 Video 做处理,有的时候需要将画面保留下来,这个时候最佳方案是保存成一个 .avi 的文件。

OpenCV 底层是用 FFMEPG 进行多媒体开发的,所以 OpenCV 它的长项不在于此,它只是提供了这种能力而已,如果要针对多媒体文件做复杂的处理,推荐的还是 FFMEPG 专业库。

OpenCV 用来创建视频文件的类是 VideoWriter。

但首先,给大家普及一些视频类相关的知识点。

1. 文件后缀名

我们一般都知道视频文件是 .mp4、.3gp、.rmvb 等等格式的,但一个文件取这样的后缀名是为了告诉用户或者操作系统,它的内容是什么格式的。我们也可以将 rmvb 格式的文件取名为 ***.avi。后缀的目的是为了方便用专业的工具或者软件操作它们。

2. 文件格式

我们可以将一个视频文件看做一个容器。

简单地说就是可以看做是一个盒子。

这个盒子里面有视频画面数据、音频数据、字幕数据等等。

OpenCV视频解码mp4 opencv 视频格式_OpenCV

不同的文件格式如 mp4、avi、mkv 等等,它们存放 打包数据的方式不一样,文件内部文件编码方式也可能不一样。

3. 编码格式

视频容器中,一般有视频和音频数据,它们采取的编码方式不一样。

视频常见的编码方式通常有: x264、h264、mpeg-4

音频常见的编码方式通常有: mp3、AAC、flac

编码的目的主要是为了高效存储和传输,如果你不采用编码压缩的话,那么视频可以看做是一系列的图片序列,体积会非常大。

4. 编码器和解码器

把视频或者音频按照编码格式,编码成特定文件格式需要编码器的参与,不然每次开发重新写代码代价很高。

把特定文件格式解码成特定的编码格式数据,这个过程称为解码,需要解码器的存在。

解码器和编码器都有开源的或者收费的工具库,极大方便了开发者。

5. FPS 帧率

我们读初中物理时,大概了解过电影画面一秒钟 24 帧,其实对应的就是 24 fps,frame per second,有些手机有高速摄像的功能,原理就是能够 1 秒钟拍摄 960 张图片,然后用正常的速度放映出来,所以细节比较多。

fps 越高,细节越好,体验也越好,但是文件容量也越高。

VideoWriter

用 OpenCV 保存视频非常简单,通过调用它的 VideoWriter 这个类就好了。

VideoWriter(filename, fourcc, fps, frameSize[, isColor]) -> <VideoWriter object>
  1. 第一个参数是要保存的文件的路径
  2. fourcc 指定编码器
  3. fps 要保存的视频的帧率
  4. frameSize 要保存的文件的画面尺寸
  5. isColor 指示是黑白画面还是彩色的画面

fourcc

fourcc 本身是一个 32 位的无符号数值,用 4 个字母表示采用的编码器。 常用的有 “DIVX"、”MJPG"、“XVID”、“X264"。可用的列表在这里。

推荐使用 ”XVID",但一般依据你的电脑环境安装了哪些编码器。

如果 fourcc 采用 -1,系统可能会弹出一个对话框让你进行选择,但是我没有试验成功过。

OpenCV视频解码mp4 opencv 视频格式_ide_02

代码示例

import numpy as np
import cv2

cap = cv2.VideoCapture(0)    #(0)是调用笔记本内置摄像头,(1)是调用usb摄像头,如果是文件路径则读取文件。
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('testwrite.avi',fourcc, 20.0, (1920,1080),True)

while(cap.isOpened()): #判断相机是否打开
    ret, frame = cap.read()
    if ret==True:
        cv2.imshow('frame',frame) #显示图片
        out.write(frame) #写入文件
        if cv2.waitKey(10) & 0xFF == ord('q'):
            break
    else:
        break

cap.release() #释放资源
out.release()
cv2.destroyAllWindows() #关闭所有窗口

这段代码的目的就是获取摄像头的视频流,然后保存到本地,帧率是 20fps,尺寸是 1920x1080.

ret, frame = cv2.VideoCapture.read()函数输出的是两个参数,第一个参数ret 为True 或者False,代表有没有读取到图片:第二个参数frame表示截取到一帧的图片。

OpenCV读取视频, 获得视频的格式, 读取视频的每一帧, 播放控制; 使用VideoCapture类和read()函数;

获取视频属性(码率\尺寸)使用VideoCapture的get()方法;

param                                      define
cv2.VideoCapture.get(0)     视频文件的当前位置(播放)以毫秒为单位
cv2.VideoCapture.get(1)     基于以0开始的被捕获或解码的帧索引
cv2.VideoCapture.get(2)     视频文件的相对位置(播放):0=电影开始,1=影片的结尾。
cv2.VideoCapture.get(3)     在视频流的帧的宽度
cv2.VideoCapture.get(4)     在视频流的帧的高度
cv2.VideoCapture.get(5)     帧速率
cv2.VideoCapture.get(6)     编解码的4字-字符代码
cv2.VideoCapture.get(7)     视频文件中的帧数
cv2.VideoCapture.get(8)     返回对象的格式
cv2.VideoCapture.get(9)     返回后端特定的值,该值指示当前捕获模式
cv2.VideoCapture.get(10)     图像的亮度(仅适用于照相机)
cv2.VideoCapture.get(11)     图像的对比度(仅适用于照相机)
cv2.VideoCapture.get(12)     图像的饱和度(仅适用于照相机)
cv2.VideoCapture.get(13)     色调图像(仅适用于照相机)
cv2.VideoCapture.get(14)     图像增益(仅适用于照相机)(Gain在摄影中表示白平衡提升)
cv2.VideoCapture.get(15)     曝光(仅适用于照相机)
cv2.VideoCapture.get(16)     指示是否应将图像转换为RGB布尔标志
cv2.VideoCapture.get(17)     × 暂时不支持
cv2.VideoCapture.get(18)     立体摄像机的矫正标注(目前只有DC1394 v.2.x后端支持这个功能)

View Code

"avi"是所有系统都会支持的视频格式;

OpenCV写视频, 需要指定视频的格式, 可以从原视频中获取; 使用VideoWriter类和write()函数

VideoWriter类写入视频时, 需要提供视频名, 格式, 码率(fps), 帧的尺寸等参数;

视频格式包括: 

  • I420(适合处理大文件) -> .avi;
  • PIMI -> .avi;
  • MJPG -> .avi & .mp4
  • THEO -> .ogv;
  • FLV1(flash video, 流媒体视频) -> .flv
  • cv2.VideoWriter_fourcc('M','J','P','G') = motion-jpeg codec
  • cv2.VideoWriter_fourcc('P','I','M','1') = MPEG-1 codec
  • cv2.VideoWriter_fourcc('M', 'P', '4', '2') = MPEG-4.2 codec
  • cv2.VideoWriter_fourcc('D', 'I', 'V', '3') = MPEG-4.3 codec
  • cv2.VideoWriter_fourcc('D', 'I', 'V', 'X') = MPEG-4 codec
  • cv2.VideoWriter_fourcc('U', '2', '6', '3') = H263 codec
  • cv2.VideoWriter_fourcc('I', '2', '6', '3') = H263I codec
  • cv2.VideoWriter_fourcc('F', 'L', 'V', '1') = FLV1 codec

 

1、cap = cv2.VideoCapture(0)

VideoCapture()中参数是0,表示打开笔记本的内置摄像头,参数是视频文件路径则打开视频,如cap = cv2.VideoCapture("../test.avi")

2、ret,frame = cap.read()

  •  cap.read()按帧读取视频,ret,frame是获cap.read()方法的两个返回值。其中ret是布尔值,如果读取帧是正确的则返回True,如果文件读取到结尾,它的返回值就为False。frame就是每一帧的图像,是个三维矩阵。

3、cv2.waitKey(1),waitKey()方法本身表示等待键盘输入,

  • 参数是1,表示延时1ms切换到下一帧图像,对于视频而言;
  • 参数为0,如cv2.waitKey(0)只显示当前帧图像,相当于视频暂停,;
  • 参数过大如cv2.waitKey(1000),会因为延时过久而卡顿感觉到卡顿。
  • c得到的是键盘输入的ASCII码,esc键对应的ASCII码是27,即当按esc键是if条件句成立

4、调用release()释放摄像头,调用destroyAllWindows()关闭所有图像窗口。

需要注意的是在 VideoWriter 中指定的尺寸要和 write() 中写进去的一样,不然视频会存储失败的。

如果需要读取视频文件,那么就将 VideoCapture 指定文件路径。

如果,将图片序列合成为视频文件,其实原理也一样,一张一张读取,然后写到 VideoWriter 当中去。

问题:1、VideoWriter不能写MP4或是其他格式视频;

           2、VideoWriter写入保存的视频大小为0kb;

           3、VideoCapture读取打开不了MP4格式的视频。

解决:将OpenCV中的opencv_ffmpeg330_64.dll(在OpenCV\build\bin文件夹下,注意这是我的opencv版本)文件拷贝到Python的Lib\site-packages文件夹下(与cv2.pyd文件放在一起解码用)。

编码格式(('M', 'P', '4', '2') 视频大小最小)

跳帧截取视频图片

import cv2
import os


def save_img():
    video_path = r'D:\python3-PycharmProjects1\video2picture\20180911-12-48-31\data\123/'
    videos = os.listdir(video_path)
    for video_name in videos:
        file_name = video_name.split('.')[0]
        folder_name = file_name
        os.makedirs(folder_name, exist_ok=True)
        vc = cv2.VideoCapture(video_path + video_name)  # 读入视频文件
        c = 1
        if vc.isOpened():  # 判断是否正常打开
            rval, frame = vc.read()
        else:
            rval = False

        timeF = 300  # 视频帧计数间隔频率

        while rval:  # 循环读取视频帧
            rval, frame = vc.read()
            pic_path = folder_name + '/'
            if (c % timeF == 0):  # 每隔timeF帧进行存储操作
                cv2.imwrite(pic_path + file_name + '_' + str(c) + '.jpg', frame)  # 存储为图像,保存名为 文件夹名_数字(第几个文件).jpg
            c = c + 1
            cv2.waitKey(1)
        vc.release()

save_img()

 

 

Opencv与Pillow图像格式转换以及转为numpy数组

PIL.Image模块有函数Image.fromarray()函数 
cv2有cv2.cvtColor()函数 
numpy有numpy.asarray()函数

值得注意的是cv2用的是BGR,PIL用的是RGB

# -*- coding=utf-8 -*-
import os
import cv2
from PIL import Image
'''
opencv中图片格式为BGR
pil中为RGB,需要转换一下
'''
import numpy as np
def numpy_to_cv():
    image = np.zeros((300, 300, 1), dtype=np.uint8)
    cv2.rectangle(image, (10, 10), (100, 100), (255, 255, 255), 10)
    cv2.imshow('mask',image)
    cv2.waitKey(0)
def numpy_to_PIL():
    image = np.zeros((300, 300, 3), dtype=np.uint8)
    image=Image.fromarray(image)
    image.show()
def cv2_show():
    '''opencv显示图片'''
    image=cv2.imread('lena.png')
    cv2.imshow('lena',image)
    cv2.waitKey()
def PIL_show():
    '''PIL显示图片'''
    image=Image.open('lena.png')
    image.show()

# cv2_show()
# PIL_show()
def cv2_to_PIL():
    image=cv2.imread('lena.png')
    image=Image.fromarray(cv2.cvtColor(image,cv2.COLOR_BGR2RGB))
    image.show()
def PIL_to_cv2():
    image=Image.open('lena.png')
    image=cv2.cvtColor(np.asarray(image),cv2.COLOR_RGB2BGR)
    cv2.imshow('lena',image)
    cv2.waitKey()
# cv2_to_PIL()
# PIL_to_cv2()

numpy.ndarray转换为OpenCV图像的关键点就是numpy.ndarray中的每个元素的dtype应该为numpy.uint8

OpenCV视频解码mp4 opencv 视频格式_ide_03

总结

其实出现这么多的坑就是因为对opencv读取图片的颜色空间和数据格式不清楚,他们里面有BGR和RGB颜色空间,而一般cv2.imread()读取的图片都是BGR颜色空间的图片,cv2.VideoCapture()获取的视频帧是RGB颜色空间的图片。PIL(Python Image Library)读取的图片是RGB颜色空间的。

opencv读取的图片不管是视频帧还是图片都是矩阵形式,即np.array,转PIL.Image格式用PIL.Image.fromarray()函数即可。

颜色空间转换的方法:

利用cv2.split()cv2.merge()函数将加载的图像先按照BGR模式分开哥哥通道,然后再按照RGB模式合并图像,代码如下:

import cv2
import matplotlib.pyplot as plt
 
b, g, r = cv2.split(img)
img2 = cv2.merge([r, g, b])
plt.subplot(1, 2, 1)
plt.imshow(img)
plt.title("BGR")
plt.subplot(1, 2, 2)
plt.imshow(img2)
plt.title('RGB')
plt.show()

这种方法是使用数组的逆序,将最后一位转置一下,代码如下:

import cv2
import matplotlib.pyplot as plt
 
img = cv2.imread("messi5.jpg")
img2 = img[...,::-1]
 
plt.subplot(1, 2, 1)
plt.imshow(img)
plt.title("BGR")
plt.subplot(1, 2, 2)
plt.imshow(img2)
plt.title('RGB')
plt.show()

使用opencv自带的模式转换函数cv2.cvtColor(),代码如下:

import cv2
import matplotlib.pyplot as plt
 
img = cv2.imread("messi5.jpg")
img2 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
 
plt.subplot(1, 2, 1)
plt.imshow(img)
plt.title("BGR")
plt.subplot(1, 2, 2)
plt.imshow(img2)
plt.title('RGB')
plt.show()