番外2. OpenCV 中摄像头捕获与视频处理与常见问题解决方案

本系列专栏写作方式

本系列专栏写作将采用首创的问答式写作形式,快速让你学习到 OpenCV 的初级、中级、高级知识。

2. OpenCV 中摄像头捕获与视频处理

OpenCV 除了应用在图像处理领域外,还会应用到视频处理领域,接下来我们就将学习到,如何通过Python OpenCV 对摄像头捕获或者视频文件进行处理。

视频文件将从三个方向入手,分别是读取文件,显示视频,保存视频。

本文将为你核心解决以下2个函数:<kbd>cv2.VideoCapture</kbd> 与 <kbd>cv2.VideoWrite</kbd>,基于这2个函数,会衍生出其它相关函数,具体参照下文。

在 OpenCV 中操作摄像头相关注意事项和解决方案

摄像头相较于视频,差异在一个是实时图像,一个是既定内容。如果想要捕获到视频,用到的是<kbd>cv2.VideoCapture</kbd> 函数,该函数的第一个参数是设备的索引号,如果你使用的是笔记本电脑,默认一般是0,表示的内置摄像头,如果链接了外置,可以修改该数字即可。

最简单的测试代码如下:

import cv2
import numpy as np

cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()

    # 转变成灰度图
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    cv2.imshow("frame", gray)
    if cv2.waitKey() & 0xFF == ord("x"):
        break

# 关闭摄像
cap.release()
cv2.destroyAllWindows()

该代码在运行时,如果你电脑未安装摄像头或者驱动异常,都会导致报错,例如下述BUG

error: (-215:Assertion failed) !_src.empty() in function 'cv::cvtColor'

该问题虽然报错出现在 <kbd>cv2.cvtColor</kbd>函数,但是原因是 frame 为空,即<kbd>cap.read()</kbd>读取失败导致的。

这里在实际编码过程中,需要增加一个判断,<kbd>cap.read()</kbd> 函数会返回一个状态布尔值,如果读取到数据,返回真,否则返回假,测试如下代码(下述代码的停止,需要按键盘上的<kbd>CTRL+C</kbd> ):

import cv2
import numpy as np

cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    print(ret)

# 关闭摄像
cap.release()
cv2.destroyAllWindows()

测试完毕之后,我们修改本文一开始的代码,让其更加健壮。

while True:
    ret, frame = cap.read()
    if ret:
        # 转变成灰度图
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        cv2.imshow("frame", gray)
        # pass ...

这里存在一个常见,但是及容易被忽视的问题,就是很多时候,我们并不知道电脑上有没有摄像头,或者摄像头的设备号,该如何进行操作

你可以按照下述代码进行检测:

import cv2
import numpy as np


for cn_num in range(0, 5):
    cap = cv2.VideoCapture(cn_num)
    if cap.isOpened():
        break

以上代码运行的原理是,按照顺序依次打开设备 0~5,当发现摄像头被成功打开,返回跳出。

上述代码还提及了一个新的函数 <kbd>cap.isOpened</kbd>,该函数用来检测摄像头是否初始化(能被打开),初始化成功返回 True。

在 OpenCV 中操作视频文件相关注意事项和解决方案

其实操作视频文件与操作摄像头操作基本一致,唯一修改的就是<kbd>cv2.VideoCapture</kbd> 函数设备号即可。

这里将设备号修改为视频文件路径。

接下来,我们先看一段代码,认识基本用法。

import numpy as np
import cv2

cap = cv2.VideoCapture('test.mp4')

while cap.isOpened():
    ret, frame = cap.read()
    # gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    cv2.imshow('frame', frame)
    if cv2.waitKey(25) & 0xFF == 27:
        break

cap.release()
cv2.destroyAllWindows()

其中最需要注意的一个地方,不是 <kbd>cv2.VideoCapture('test.mp4')</kbd> 函数读取文件,而是 <kbd>cv2.waitKey(25)</kbd>,这个地方需要设置好对应的视频,如果数值特别小,例如 1,视频就会快速播放,设置太高播放又会变慢,一般建议设置为 25

当然,上述代码注释的部分,如果取消,就会变成一个灰度视频。

甚至可以直接将视频进行翻转,用到函数为:<kbd>cv2.flip(frame, 0)</kbd>,

import numpy as np
import cv2 

cap = cv2.VideoCapture('test.mp4')

while cap.isOpened():
    ret, frame = cap.read()
    if ret == True:
        frame = cv2.flip(frame, 0)

        cv2.imshow('frame', frame)
        if cv2.waitKey(25) & 0xFF == 27:
            break

cap.release()
cv2.destroyAllWindows()

上述代码运行如下:

a325e56a7c294705a5dbb7475a6fc3a2[1].png

接下来我们用视频来学习一下一些操作属性的方法。

<kbd>cv2.VideoCapture.get(propId)</kbd> 可以访问视频的某些功能,其中 <kbd>propId</kbd> 是一个从0到18的数字,每个数字表示视频的属性。

取值清单如下(该清单只作为查询参考使用):

  • 0 - <kbd>cv2.CAP_PROP_POS_MSEC</kbd>: 视频文件的当前位置(ms);
  • 1 - <kbd>cv2.CAP_PROP_POS_FRAMES</kbd>:从0开始索引帧,帧位置;
  • 2 - <kbd>cv2.CAP_PROP_POS_AVI_RATIO</kbd>:视频文件的相对位置(0表示开始,1表示结束);
  • 3 - <kbd>cv2.CAP_PROP_FRAME_WIDTH</kbd>:视频流的帧宽度;
  • 4 - <kbd>cv2.CAP_PROP_FRAME_HEIGHT</kbd>: 视频流的帧高度;
  • 5 - <kbd>cv2.CAP_PROP_FPS</kbd>:帧率;
  • 6 - <kbd>cv2.CAP_PROP_FOURCC</kbd> : 编解码器四字符代码;
  • 7 - <kbd>cv2.CAP_PROP_FRAME_COUNT</kbd> :视频文件的帧数;
  • 8 - <kbd>cv2.CAP_PROP_FORMAT</kbd>:retrieve()返回的Mat对象的格式;
  • 9 - <kbd>cv2.CAP_PROP_MODE</kbd> :后端专用的值,指示当前捕获模式;
  • 10 - <kbd>cv2.CAP_PROP_BRIGHTNESS</kbd> :图像的亮度,仅适用于支持的相机;
  • 11 - <kbd>cv2.CAP_PROP_CONTRAST</kbd> :图像对比度,仅适用于相机;
  • 12 - <kbd>cv2.CAP_PROP_SATURATION</kbd>:图像饱和度,仅适用于相机;
  • 13 - <kbd>cv2.CAP_PROP_HUE</kbd>:图像色调,仅适用于相机;
  • 14 - <kbd>cv2.CAP_PROP_GAIN</kbd>:图像增益,仅适用于支持的相机;
  • 15 - <kbd>cv2.CAP_PROP_EXPOSURE</kbd>:曝光,仅适用于支持的相机;
  • 16 - <kbd>cv2.CAP_PROP_CONVERT_RGB</kbd>:布尔标志,指示是否应将图像转换为RGB;
  • 17 - <kbd>cv2.CAP_PROP_WHITE_BALANCE</kbd>:目前不支持;
  • 18 - <kbd>cv2.CAP_PROP_RECTIFICATION</kbd>:立体摄像机的整流标志。

有了上述清单,可以获取视频较多的属性值了,例如获取宽度与高度。

cap = cv.VideoCapture('test.mp4')
print("宽度:%s,高度:%s" % (cap.get(3), cap.get(4)))

有 <kbd>get</kbd> 方法,对应的就存在 <kbd>set</kbd> 函数,函数原型如下:

cap.set(propId,value) # 进行修改,value 是修改后的值

但这个地方要注意下,上面 <kbd>set</kbd> 函数只对采集摄像头有用,如果读取的是视频文件,上述内容就不在起作用了。

如果你还是希望实现修改视频文件的窗口大小,那可以使用下述方案。

cv2.namedWindow("frame", 0)
cv2.resizeWindow("frame", 800,300)

如果想希望查看视频播放的当前位置、帧率、帧数,可以直接使用下述代码。

time = cap.get(cv2.CAP_PROP_POS_MSEC)
fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT)

当然,使用属性值也可以。

time = cap.get(0)
fps = cap.get(5)
total_frames = cap.get(7)

在 OpenCV 中捕获视频并保存

图片的保存使用函数 <kbd>cv2.imwrite</kbd>即可实现,但视频保存,需要与<kbd>cv2.VideoCapture</kbd>对应着,创建一个 <kbd>VideoWriter</kbd>对象,而且还需要确定文件的名称,视频 <kbd>FourCC</kbd> 编码,频率,帧大小,<kbd>isColor</kbd>标签,该标签如果为 True 则保存彩色图,否则是灰度图。

首先对 <kbd>FourCC</kbd> 进行一下说明,该编码是一个4字节码,表示的是视频编码格式。

例如对于 <kbd>MJPG</kbd>,它有两种写法。

注意这是 <kbd>OpenCV 3</kbd> 写法,网上现在大多数博客都是 <kbd>OpenCV 2</kbd> 写法。

cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')
cv2.VideoWriter_fourcc(*'MJPG')

常见编码说明:

  • <kbd>MJPG</kbd>编码器编码的视频非常大;
  • <kbd>X264</kbd> 编码器编码的视频很小;
  • <kbd>XVID</kbd>编码器优先选择,质量较高且体积较小。

<kbd>cv2.VideoWriter</kbd>函数原型如下:

cv2.VideoWriter( filename, fourcc, fps, frameSize[, isColor] )

相关参数:

  • <kbd>filename</kbd>:视频保存文件名称;
  • <kbd>fourcc</kbd>:视频编解码器的4字节代码;
  • <kbd>fps</kbd>:帧率;
  • <kbd>frameSize</kbd>:帧大小;
  • <kbd>isColor</kbd>:如果为True,则视频为彩色,否则为灰度视频,默认为True。

测试代码如下,代码之后跟相关问题的说明。

import numpy as np
import cv2

cap = cv2.VideoCapture("test.mp4")

width = int(cap.get(3))
height = int(cap.get(4))
fps = cap.get(5)

# FourCC 编码是 XVID
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter("test1.avi", fourcc, fps, (width, height))

while 1:
    ret, frame = cap.read()
    if frame is None:
        break
    else:
        # 对每一帧都进行处理
        # frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        out.write(frame)
        cv2.imshow("video", frame)
        k = cv2.waitKey(25) & 0xFF 
        if k == 27:
            break

cap.release()
out.release()
cv2.destroyAllWindows()

在调用函数 cv2.VideoWriter("test1.avi", fourcc, fps, (width, height)) 的时候,注意需要 (width, height) 必须是整数,需要强制转换一下。

其中 20 是 fps ,如果想保存成灰度视频,按照下述代码修改即可。

out = cv2.VideoWriter("test1.avi", fourcc, fps, (width, height),False)
# ......
# 注意在读取每一帧的时候,都要转换成灰度图
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# ......
7a4d23f393c34e7bbe5f48be12a7dfcd[1].png

如果在编码时出现如下错误,注意是 frameSize 参数设置的问题

VideoWriter() missing required argument 'frameSize' (pos 5)

对应的代码位置按照如下修改即可

out = cv2.VideoWriter("test1.avi", fourcc, fps, (int(width), int(height)))

下一个常见的问题是,如果不是读取视频文件,直接采集的摄像头数据,使用 <kbd>fps = cap.get(5)</kbd> 是无法获取到具体的 fps 值的,此时只能进行手动设定。

该值的设定需要依据自己电脑的配置,一般设置为10~20即可。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容