OpenCV 学习笔记 02 处理文件、摄像头和图形用户界面

在处理文件前需要引入OpenCV库,同时也引入unmpy库

import cv2
import numpy as np

1 基本的读写操作

1.1 图像文件的读写操作

1.1.1 图像文件的读取操作

opencv 的 imread() 函数和 imwrite() 函数支持各种静态图像文件格式。例如bmp, png, jpeg, tiff等。

cv2.imread(filename[, flags]) 

imread() 函数从文件中加载并返回该图像,若指定图像不能被读取(文件丢失、权限不正确、不支持或非法的格式等等),则返回一个空的矩阵。

opencv 为 flags 提供了枚举类型文档,主要有

  • cv2.IMREAD_UNCHANGED = -1    如果设置,则按原样返回加载的图像(使用Alpha通道,否则会被裁剪)。
  • cv2.IMREAD_GRAYSCALE = 0    如果设置,则始终将图像转换为单通道灰度图像。
  • cv2.IMREAD_COLOR = 1              如果设置,请始终将图像转换为3通道BGR彩色图像。
  • cv2.IMREAD_ANYDEPTH = 2        如果设置,当输入深度为16/32位时,则返回相应的深度,否则将其转换为8位。
  • cv2.IMREAD_ANYCOLOR = 4       如果设置,则以任何可能的颜色格式读取图像。
  • cv2.IMREAD_LOAD_GDAL = 8      如果设置,请使用gdal驱动程序加载图像。

具体参考 Image file reading and writing

备注1:

filename 可以是绝对路径,也可以是相对路径。

win环境下的绝对路径:D:\\grandfather\\father\\test.png

Unix下的文件间隔符为 /

备注2:

无论采用哪种模式,imread() 函数均会删除所有透明alpha 通道信息。

备注3:

当图像文件为灰度格式时,imread() 函数也会返回 BGR(red-green-blue) 格式的图像,BGR与RGB所表示的色彩空间相同,但是字节顺序相反。

备注4:

那怎么读取灰度图文件后再保存为灰度图呢?

增加imread()函数的 flags 参数即可

grayImage = cv2.imread('test.png', cv2.IMREAD_GRAYSCALE)
cv2.imwrite('NewTest.png', grayImage)

1.1.2 图像文件的写入操作

imwrite(filename, img[, params])

该函数将图像保存为指定文件中

图像格式是根据文件扩展名选择的,一般情况下,该函数只能保存8位单通道或3位通道(按“BGR”通道顺序)的图像。

具体可参看1.1.1 中的代码示例。

另:

opencv可以读取一个格式图像文件,保存时选取另一种格式图像文件。

将 png --> jpg 格式。

image = cv2.imread('test.png')
cv2.imwrite('test.jpg',image)

1.1.3 图像与原始字节之间的转换

1) 图像像素的基本介绍

无论哪种格式的图像,每个像素都有一个对应值,但不同格式所表示的像素方式也存在有差异。

一个 opencv 图像是 array 类型的二维或三维数组。8 位的灰度图像是一个含有字节值的二维数组,8 位图像有 2^8 = 256 种颜色(16 位图像的像素值有 2^16 = 65536 种颜色)

一个 24 位的 BGR 图像是一个 三维数组,它也包含了字节值。可以使用表达式访问这些值。

image[0,0] 或者 image[0, 0, 0] 。

  • 第一个值代表像素 y 坐标或者行
  • 第二个值代表像素 x 坐标或者列
  • 第三个值代表颜色通道

例如,

对于一个白色像素的 8 位灰度图像而言,image[0, 0] 的值为255,

对于一个蓝色像素的24位BGR图像而言,image[0, 0] 的值为[255, 0, 0 ]

2)素与字节的转换

若一副图像的每个通道为 8 位,则可将其显示转换为标准的一维python bytearray 格式。

byteArray = bytearray( image)

若bytearray含有恰当顺序的字节,可以通过显示转换和重构,得到numpy.array形式的图像。

grayImage = numpy.array(grayByteArray).reshape(height, width)

bgrImage = numpy.array(bgyByteArray).reshape(height, width, 3)

3) 像素与字节的转换示例

import cv2
import numpy as np
import os

# make an array of 120000 random bytes
randomByteArray = bytearray(os.urandom(120000))
flatNumypyArray = np.array(randomByteArray)

# convert the array to make a 400*300 grayscale image
grayImage = flatNumypyArray.reshape(300, 400)
cv2.imwrite('RandomGray.png', grayImage)

# convert the array to make a 400*100 color image
bgrImage = flatNumypyArray.reshape(100, 400, 3)
cv2.imwrite('RandomColor.png', bgrImage)

运行该脚本,会随机生成两个图像 RandomGray.png 和 RandomColor.png ,图像位于脚本所在的目录。

备注:

使用 python 标准的 os.urandom() 函数可随机生成原始字节,随后将该字节转换成 numpy 数组。

需要注意的是,numpy.random.randint(0, 256, 120000 ).reshape(300, 400) 语句也能直接随机生成 numpy 数组,且该方法更为高效;使用os.urandom 函数的唯一理由是该语句有助于展示原始字节的转换。

1.2 视频文件的读写操作

视频中包含的信息量远远大于图片,但本质上而言,视频是由一帧帧的图像组成的,所以视频处理最终还是归结于图像处理。

opencv 提供了 VideoCapture 类和 VideoWriter 类来支持个各种视频格式读写操作

1.2.1 视频文件的读取操作

1)打开一段已有视频或默认的摄像头

一种实例化 VideoCapture 类时直接传参,第二种是使用 open() 函数。

# 方法1
cv2.VideoCapture('../video.avi')

# 方法2
v = cv2.VideoCapture()
v.open('../video.avi')

备注:如果将文件参数更变为ID,则可打开该摄像头,默认摄像头为 0

2)获取视频的帧

在视频采集过程中,VideoCapture 类可通过 read() 函数来获取视频的帧,每帧是一幅基于 BGR 格式的图像。

v = cv2.VideoCapture()
v.read( [, image] )

抓取、解码并返回下一个视频帧。函数会返回视频帧,若没有抓住任何帧(相机已断开连接或视频文件中没有更多的帧),则该方法返回 false 并将该函数返回空图像。

3)获取视频的参数

一个视频的参数有尺寸、格式、帧率、总帧数等参数,get() 方法能够获取这些参数

v = cv2.VideoCapture()

a = v.get(cv2.CAP_PROP_FPS) # 获取帧率
b = v.get(cv2.CAP_PROP_FRAME_WIDTH) # 视频流中的帧宽度
c = v.get(cv2.CAP_PROP_FRAME_HEIGHT)# 视频流中的帧高度
d = v.get(cv2.CAP_PROP_FRAME_COUNT)# 视频文件中帧的总数
e = v.get(cv2.CV_CAP_PROP_POS_MSEC)# 视频目前指针位置,为毫秒数或时间戳
f = v.get(cv2.CV_CAP_PROP_POS_FRAMES)# 将被下一步解压/获取的帧索引,以0为起点
g = v.get(cv2.CV_CAP_PROP_POS_FRAMES)# 视频文件的相对位置,0视频起始处、1视频结尾处

4)读取视频中具体位置处的图像

VideoCapture类的set方法可以允许我们取出视频中某个位置的帧,它有一些参数,可以按时间,也可以按帧号,还可以按视频长短的比例。

v = cv2.VideoCapture()

#  第100帧
double position = 100.0
capture.set(CV2.CAP_PROP_POS_FRAMES, position)
# 第1e6毫秒
double position = 1e6
capture.set(CV2.CAP_PROP_POS_MSEC, position)
# 视频1/2位置
double position = 0.5
capture.set(CV2.CAP_PROP_POS_AVI_RATIO, position)

当然,set方法仅用于取视频帧的位置,还可以设置视频的帧率、亮度。

1.2.2 视频文件的写入操作

VideoWriter 类的 write() 函数可将图像写入到VideoWriter 类所指向的文件中。

下面的示例读取 MP4 文件的帧,并采用 YUV 颜色编码将其写入另一帧中。

import cv2

videoCapture = cv2.VideoCapture()
videoCapture.open('MyInputVid.mp4')
fps = videoCapture.get(cv2.CAP_PROP_FPS)
size = (int(videoCapture.get(cv2.CAP_PROP_FRAME_WIDTH)),
        int(videoCapture.get(cv2.CAP_PROP_FRAME_HEIGHT)))

videoWriter = cv2.VideoWriter(
    'MyOutputVid.avi',
    cv2.VideoWriter_fourcc('),
    fps,
    size
    )

success, frame = videoCapture.read()

# loop until there are no more frames
while success:
    videoWriter.write(frame)
    success, frame = videoCapture.read()

备注:

必须为 VideoWriter 类的构造函数指定视频文件名,若文件已存在,则被覆盖;

必须为视频指定编解码器,编解码器的可用性根据系统的不同而不同。

  • cv2.VideoWriter_fourcc('I', '4', '2', '0') - 该选项是一个未压缩的 YUV 颜色编码,是4:2:0 色度子采样,这种编码有很好的兼容性,但会产生较大文件,扩展名.avi
  • cv2.VideoWriter_fourcc('P', 'I', 'M', '1') - 该选项是MPEG-1编码类型,扩展名为.avi
  • cv2.VideoWriter_fourcc('X', 'V', 'I', 'D') - 该选项是MPEG-4编码类型,若希望得到视频大小为平均值,推荐使用此项,扩展名.avi
  • cv2.VideoWriter_fourcc('T', 'H', 'E', 'O') - 该选项是ogg vorbis,扩展名.ogv
  • cv2.VideoWriter_fourcc('F', 'L', 'V', 'I') - 该选项是一个 flash 视频,扩展名.flv

必须指定帧速率、帧大小,因需要从另一个视频文件复制视频帧;这些属性可从 VideoCapture 类的get() 函数中获取。

1.3 捕捉摄像头的帧

VideoCapture 类可以获得摄像头的帧流,参数则为摄像头的设备索引(device index),下段代码为捕捉摄像头 10 秒的视频信息;并将其写入一个avi文件中。

import cv2

cameraCapture = cv2.VideoCapture(0)
# 假设帧率为30
fps = 30
size = (int(cameraCapture.get(cv2.CAP_PROP_FRAME_WIDTH)),
        int(cameraCapture.get(cv2.CAP_PROP_FRAME_HEIGHT)))

videoWriter = cv2.VideoWriter(
    'MyOutputVid.avi',
    cv2.VideoWriter_fourcc('),
    fps,
    size
    )
success, frame = cameraCapture.read()
numFrameRemaining = 10 * fps - 1
while success and numFrameRemaining > 0:
    videoWriter.write(frame)
    success, frame = cameraCapture.read()
    numFrameRemaining -= 1
cameraCapture.release()

备注1:

为了针对摄像头创建合适的VideoWriter 类,这里设置帧速率fps=30,主要是因为VideoCapture 类的 get() 方法不能返回摄像头帧速率的准确值,它总是返回0,

return Value for the specified property. Value 0 is returned when querying a property that is  not supported by the backend used by the VideoCapture instance.

当 VideoCapture 类所使用的终端不支持所查询的这个属性时,则会返回0

另外,采用计时器来计量帧速率会更精确些。

1.4 在窗口显示图像

imshow(winname, mat)

该函数可实现指定窗口显示图像。但是该函数因代码的改变而实时改变窗口,必须再次运行代码后图像才能变化。

winname - 窗口的名称

mat - 要显示的图像。

例如下段代码

import cv2

img = cv2.imread('my_image.jpg')
cv2.imshow('A pretty girl', img)
cv2.waitKey()
cv2.destroyAllWindows()

运行则在窗口显示

OpenCV 学习笔记 02 处理文件、摄像头和图形用户界面

备注1:

如果窗口是使用cv::WINDOW_AUTOSIZE 标志flags创建的,则图像显示为原始大小,实际上,该值也为默认值。不过图像大小仍受屏幕分辨率的限制。

当然,图像也可按比例缩放以适合窗口,缩放图像取决于其深度。

  • 如果图像是8位无符号的,它将按原样显示。
  • 如果图像是16位无符号或32位整数,像素就除以256。也就是说,值范围[0,255*256]被映射到[0,255]。
  • 如果图像是32位或64位浮点数,像素值将乘以255。也就是说,值范围[0,1]被映射到[0,255]。

备注2:

调用 destoryAllWindows() 函数可以释放由OpenCV 创建的所有窗口

提示:

  • 按Ctrl + C将图像复制到剪贴板。
  • 按Ctrl + S将显示保存图像的对话框。

1.5 在窗口显示摄像头帧

opencv 的常用函数及其功能。

  • nameWindow() - 函数允许指定窗口来创建窗口
  • imshow() - 函数允许指定窗口来显示窗口
  • DestroyWindow()  - 函数允许指定窗口来销毁窗口
  • waitKey() - 函数可以获取键盘输入
  • setMouseCallback() - 函数可以获取鼠标输入

下段代码可以实时显示摄像头帧

import cv2

clicked = False
def clickMouse(event, x, y, flags, param):
    global clicked
    if event == cv2.EVENT_LBUTTONUP:
        clicked = True

# 开启ID为0的摄像头
cameraCapture = cv2.VideoCapture(0)
# 为窗口赋名MyWindow
cv2.namedWindow('MyWindow')
# 鼠标处理函数
cv2.setMouseCallback('MyWindow', clickMouse)

print('Showing camera feed. click window or press any key to stop.')
success, frame = cameraCapture.read()
while success and cv2.waitKey(1) == -1 and not clicked:
    cv2.imshow('MyWindow', frame)
    success, frame = cameraCapture.read()

cv2.destroyWindow('MyWindow')
cameraCapture.release()

1.5.1 对鼠标操作函数 setMouseCallback()

setMouseCallback(windowName, onMouse [, param])
  • windowName - 目标窗口名称
  • onMouse - 鼠标响应函数,回调函数名
  • param - 回调函数的参数,可选参数

在上述代码中,回调函数为clickMouse()。在回调函数中的参数可以取如下值,它们对应不同的鼠标事件。

假设定义回调函数为OnMouseAction()

def OnMouseAction(event,x,y,flags,param):
    pass

备注:鼠标操作函数为 cv2.setMouseCallback('img',OnMouseAction) ,其中 img 为被操作的窗口名称。

回调函数有 5 个参数,每一个参数对应释义如下。

Event(对应的鼠标标志参数有):

  • cv2.EVENT_MOUSEMOVE - 鼠标移动
  • cv2.EVENT_LBUTTONDOWN - 鼠标左键按下
  • cv2.EVENT_RBUTTONDOWN - 鼠标右键按下
  • cv2.EVENT_MBUTTONDOWN - 鼠标中间键按下
  • cv2.EVENT_LBUTTONUP - 鼠标左键松开
  • cv2.EVENT_RBUTTONUP - 鼠标右键松开
  • cv2.EVENT_MBUTTONUP - 鼠标中间键松开
  • cv2.EVENT_LBUTTONDBLCLK - 双击鼠标左键
  • cv2.EVENT_RBUTTONDBLCLK - 双击鼠标右键
  • cv2.EVENT_MBUTTONDBLCLK - 双击鼠标中间键

x, y 代表鼠标位于窗口坐标位置(x,y),即Point(x,y)

flags - 代表鼠标的拖拽事件,以及键盘鼠标联合事件,共有 32 件事件:

  • cv2.EVENT_FLAG_LBUTTON - 按下鼠标左键
  • cv2.EVENT_FLAG_RBUTTON - 按下鼠标右键
  • cv2.EVENT_FLAG_MBUTTON - 按下鼠标中间键
  • cv2.EVENT_FLAG_CTRLKEY - 按下Ctrl键
  • cv2.EVENT_FLAG_SHIFTKEY - 按下Shift键
  • cv2.EVENT_FLAG_ALTKEY - 按下Alt键

param - 函数指针,标识了所响应的事件函数,相当于自定义了一个 OnMouseAction() 函数的 ID;可选参数

然而,opencv 不提供任何处理窗口事件的方法,例如,当单击窗口的关闭按钮时,并不能关闭应用程序,由于 opencv 有限的事件处理能力和 GUI 处理能力,许多开发人员更喜欢将opencv集成到其他应用程序框架中。

更多内容可以参考【AI基础】python:openCV——处理鼠标事件(1)

1.5.2 waitKey()

imshow() 函数后面应该配套使用 cv::waitKey() 函数,如果没有该函数,则图像在屏幕上一闪即过;

waitKey() 或waitKey(0) 将无限显示窗口,任意键结束窗口显示,waitKey(25) 表示图像显示一个 25 毫秒的帧,之后显示将自动关闭。如果将其放在一个循坏中来阅读视频,它将逐帧显示视频。

返回值 -1(表示没有键被按下)或 ASCII 码。注意:ASCII码可以通过 python 提供的函数 old() 来实现字符转换为 ASCII 码。old('a') = 97。

opencv 的窗口函数和 waitKey() 函数相互依赖。opencv 的窗口只有在调用waitkey() 函数时才会更新,waitkey() 函数只有在 opencv 窗口成为活动窗口时,才能捕获输入信息。

======2019.01.19 更新========

在运行过程中发生

AttributeError: module 'cv2.cv2' has no attribute 'waitkey'

查找原因才发现问题出在 waitkey() 函数的书写上,该函数应该是 waitKey() ,K 为大写。

实际上,K 与 k的大小写很容易混肴,所以造成了报错。

============================

1.6 转载两个相关小程序代码

原创 OpenCV-Python:处理鼠标事件

1.6.1 查询鼠标事件方法

C:\Users>python>>> import cv2
>>> events = [i for i in dir(cv2) if 'EVENT' in i]
>>> print(events)
['EVENT_FLAG_ALTKEY', 'EVENT_FLAG_CTRLKEY', 'EVENT_FLAG_LBUTTON', 'EVENT_FLAG_MBUTTON', 'EVENT_FLAG_RBUTTON', 'EVENT_FLAG_SHIFTKEY', 'EVENT_LBUTTONDBLCLK', 'EVENT_LBUTTONDOWN', 'EVENT_LBUTTONUP', 'EVENT_MBUTTONDBLCLK', 'EVENT_MBUTTONDOWN', 'EVENT_MBUTTONUP', 'EVENT_MOUSEHWHEEL', 'EVENT_MOUSEMOVE', 'EVENT_MOUSEWHEEL', 'EVENT_RBUTTONDBLCLK', 'EVENT_RBUTTONDOWN', 'EVENT_RBUTTONUP']
>>>

1.6.2 在窗口上绘制圆圈

import cv2
import numpy as np

# 鼠标回调函数
def draw_circle(event,x,y,flags,param):
    if event == cv2.EVENT_LBUTTONDBLCLK:   # 处理鼠标左键双击
        cv2.circle(img,(x,y),100,(255,0,0),-1)  # 在鼠标点击位置创建圆

# 创建一个黑背景图像
img = np.zeros((512,512,3), np.uint8)
cv2.namedWindow('image')
cv2.setMouseCallback('image',draw_circle)  # 设置回调函数

while(1):
    cv2.imshow('image',img)
    if cv2.waitKey(20) & 0xFF == 27:  # esc键退出
        break
cv2.destroyAllWindows()

运行

OpenCV 学习笔记 02 处理文件、摄像头和图形用户界面

1.6.3 绘制长方形

import cv2
import numpy as np

drawing = False # 鼠标左键是不是按下
mode = True # True,激活绘制正方形. False 激活绘制曲线;使用m键切换
ix,iy = -1,-1

# mouse callback function
def draw_circle(event,x,y,flags,param):
    global ix,iy,drawing,mode

    if event == cv2.EVENT_LBUTTONDOWN:
        drawing = True
        ix,iy = x,y

    elif event == cv2.EVENT_MOUSEMOVE:
        if drawing == True:
            if mode == True:
                cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),-1)
            else:
                cv2.circle(img,(x,y),5,(0,0,255),-1)

    elif event == cv2.EVENT_LBUTTONUP:
        drawing = False
        if mode == True:
            cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),-1)
        else:
            cv2.circle(img,(x,y),5,(0,0,255),-1)

img = np.zeros((512,512,3), np.uint8)
cv2.namedWindow('image')
cv2.setMouseCallback('image',draw_circle)

while(1):
    cv2.imshow('image',img)
    k = cv2.waitKey(1) & 0xFF
    if k == ord('m'):
        mode = not mode
    elif k == 27:
        break

cv2.destroyAllWindows()

运行

OpenCV 学习笔记 02 处理文件、摄像头和图形用户界面

2 人脸跟踪与图像处理(Cameo项目)

为了开发人脸跟踪和图像处理的交互式作用,需要实时获取摄像头的输入;该类的应用覆盖了 opencv 大部分的功能。

就项目具体而言,该应用将实时进行脸部合并(facial merging) ,即对给定的两个摄像头的输入流(或选择性输入预先录制的视频),应用要对两个输入流的人脸进行叠加;为了使混合的场景看起来具有统一的感觉,可采用滤波器和扭曲(distortion)技术,用户会有一种融入现场表演的感觉。在这样的应用中,如果有帧率低、不精确跟踪等缺陷,用户会立即感受得到,为了达到最好的效果,可以尝试采用传统成像和深度成像的方法。

将本应用程序命名为Cameo

2.1 Cameo - 面向对象的设计

由上文可知,opencv 的图像 I/O 功能都是相似的,所以可创建CaptureManager 类和 WindowManager 类作为高级的 I/O 流接口。

CaptureManager 来读取新的帧,并将帧分派到一个或多个输出中,这些输出包括静止的图像文件、视频文件及窗口(可通过WindowManager 类实现)。

WindowManager 类使应用程序代码能以面向对象的形式处理窗口和事件。

CaptureManager 和 WindowManager 都具有可扩展性,因此实现时可不依赖OpenCV 的I/O。

2.2 使用manager.CaptureManager 提取视频流

未完待续2019.01.05

上一篇:Yii CDbCriteria


下一篇:字段自动递增的数据库建表的SQL写法