图像处理与计算机视觉实践:基于OpenCV和Python
上QQ阅读APP看书,第一时间看更新

1.2 图像的基本操作

1.2.1 数字图像的表示

我们在电子设备上看到的图像都可以称为数字图像,例如图1-2所示的Lena图像。

图1-2 Lena的数字图像

对计算机来说,这幅图像只是一些亮度不同的点。一幅尺寸为M×N的图像可以用M×N的矩阵(即M×N个点)表示,如图1-3所示。每个矩阵元素代表一个像素,元素的值表示这个位置图像的亮度,一般来说,值越大该点就越亮。放大图1-3(a)中白色方框区域可得到图1-3(b)所示效果,对应的像素的值为图1-3(c)中的值。通常,灰度图像用2维矩阵M×N表示,彩色(多通道)图像用3维矩阵M×N×3表示。对于图像显示来说,一般用无符号8位整数来表示像素亮度,取值范围为[0, 255]。

      (a)                        (b)                        (c)

图1-3 数字图像的表示

图像数据按照自左向右、自上向下的顺序存储在计算机内存中,即以图像的左上角为原点(也有自左向右、自下向上的顺序,即以图像的左下角为原点)。图1-4表示的是单通道灰度图像数据在计算机中的存储顺序,Iij代表第i行第j列的像素值。图1-5表示的是3通道BGR彩色图像数据在计算机中的存储顺序,每个像素用3个值表示,即。需要说明一下,OpenCV中RGB彩色图像的通道顺序为BGR。

图1-4 单通道灰度图像数据在计算机中的存储顺序

图1-5 3通道BGR彩色图像数据在计算机中的存储顺序

1.2.2 图像文件的读写与显示

OpenCV提供了函数cv.imread()、cv.imshow()和cv.imwrite()来处理图像文件的读取、显示和写入。

1. 图像文件的读取

使用cv.imread()函数将图像文件读入内存:

retval = cv.imread(filename[, flags])

其中的主要参数介绍如下。

filename:要读取的图像文件的文件名。

flags:控制如何读入图像文件的标志。flags的取值和含义如表1-1所示。

retval:读入的图像数据。

表1-1 参数flags的取值和含义

flags的默认值为cv.IMREAD_COLOR,即将读入的图像转换为3通道BGR图像数据。假如图像文件为单通道的灰度图像,读入后会被强制转换为3通道。cv.IMREAD_GRAYSCALE则返回单通道图像数据,假如图像文件为多通道图像,读入后会被强制转换为单通道图像。

cv.imread()支持多种格式图像文件的读取,OpenCV支持读取的图像文件格式如表1-2所示。

表1-2 OpenCV支持读取的图像文件格式

注意:想要OpenCV支持某种图像文件格式,需要有对应的文件格式库。只有在编译OpenCV时添加了相应的库,安装后OpenCV才能支持此格式。

2. 图像文件的显示

成功读取图像文件后,可以使用OpenCV提供的GUI(Graphical User Interface,图形用户界面)用cv.imshow()将图像在窗口中显示出来,如图1-6所示。

图1-6 OpenCV图像在窗口显示

cv.imshow(winname, mat)

其中的主要参数介绍如下。

winname:图像显示窗口的名称。

mat:要显示的图像数据。

前面提到对于图像显示来说,一般用无符号8位整数,取值范围为[0, 255]。根据mat的数据类型,cv.imshow()显示图像时会进行以下操作。

如果mat是8位无符号整数,则直接显示。

如果mat是16位无符号整数,则像素值域会做[0, 255*256]到[0, 255]的映射。

如果mat是32位或64位浮点数,则像素值域会做[0, 1]到[0, 255]的映射。

如果mat是32位整数,则需要用户根据应用上下文预先进行将像素值域映射到[0, 255]的处理。

通过函数cv.imshow()生成的窗口会根据显示的图像自动调整大小,用户不能手动改变窗口大小。如果想改变窗口大小,可以使用OpenCV提供的另一个函数cv.namedWindow()来生成窗口。

cv.namedWindow(winname[, flags])

其中的主要参数介绍如下。

winname:窗口名称。

flags:窗口的属性。flags值对应的窗口属性如表1-3所示。

表1-3 flags值对应的窗口属性

 flags的默认值为 cv.WINDOW_AUTOSIZE|cv.WINDOW_KEEPRATIO|cv.WINDOW_GUI_EXPANDED。

调用函数cv.imshow()后还需要紧接着调用函数cv.waitKey()来执行GUI的housekeeping任务,这样才能实际显示图像和响应鼠标、键盘事件,否则不会显示图像且窗口可能被锁住。函数cv.waitKey()的功能是等待键盘按键按下。

retval = cv.waitKey([delay])

其中的主要参数介绍如下。

delay:等待键盘事件的时间,单位为ms;如果值小于或等于0,则窗口会一直等待键盘按键按下。默认值为0。

retval:如果指定的时间内没有按键按下,则返回-1,否则返回被按下按键的ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)。

函数cv.destroyWindow(winname)和cv.destroyAllWindows()用于销毁生成的窗口。

3. 图像文件的写入

将图像数据写入文件,可使用cv.imwrite()函数:

retval = cv.imwrite(filename, img[, params])

其中的主要参数介绍如下。

filename:文件名。

img:待写入的图像数据。

params:指定文件格式。OpenCV可保存的文件格式如表1-4所示。

表1-4 OpenCV可保存的文件格式

存储的图像格式根据filename中的扩展名来决定,同时并不是所有的img都可以存为图像文件,目前只支持8位单通道和3通道(颜色顺序为BGR)矩阵。如果img为16位无符号整数类型,则需要存储为PNG、JPEG 2000或TIFF格式;若为32位浮点数类型,则需要存储为PFM、TIFF、OpenEXR或Radiance HDR格式。如果某格式的图像矩阵不支持保存为图像文件,可以先用cv.convertTo()函数或者cv.cvtColor()函数将矩阵转为可以保存的格式,再保存。另外需要注意的是,在保存文件时如果文件名已经存在,cv.imwrite()函数不会进行提醒,将直接覆盖以前的文件。

下面的例子展示了如何读入一幅彩色图像,读入的同时将原始图像转换为灰度图像,在窗口显示灰度图像,并将灰度图像保存到文件中。

import cv2 as cv
 
def main():
 
    # 读入图像, 同时转换为灰度图像
    im_grey = cv.imread("lena.jpg", cv.IMREAD_GRAYSCALE)
    
    # 将灰度图像写入文件
    cv.imwrite("lena_grey.jpg", im_grey)
 
    # 显示灰度图像
    cv.imshow("Lena", im_grey)
    cv.waitKey()
    # 销毁窗口
    cv.destroyAllWindows()
 
 
if __name__  == '__main__':
    main()

将lena.jpg放在与例子相同的目录下,运行该例子的代码后,lena_grey.jpg将会出现在此目录。读入的原始图像如图1-2所示,转为灰度图像的显示窗口如图1-7所示。

图1-7 灰度图像显示窗口

1.2.3 视频文件的读写与显示

在介绍OpenCV如何读写与显示视频文件之前,先介绍一下编解码器(codec)。如果是图像文件,我们可以根据文件扩展名得知图像的格式,但是此经验并不能推广到视频文件中,因为视频文件的格式主要由压缩算法决定。压缩算法称为编码器(coder),解压算法称为解码器(decoder),编解码算法统称为编解码器(codec)。视频文件能否读写,关键看是否有相应的编解码器。编解码器的种类非常多,常用的有MJPG、XVID、DIVX等。视频文件的扩展名(如avi等)往往只能表示这是一个视频文件,我们并不能由其得知实际的编解码器。

OpenCV提供了两个类来处理视频文件的读写。读视频文件的类是VideoCapture,写视频文件的类是VideoWriter。

VideoCapture类既可以从视频文件读取图像,也可以从摄像头读取图像,可以使用该类的构造函数打开视频文件或者摄像头。如果VideoCapture类对象已经创建,可以使用cv.VideoCapture.open()函数打开,该函数会自动调用cv.VideoCapture.release()函数,先释放已经打开的视频文件,再打开新视频文件。如果要读取一帧图像,可以使用cv.Video Capture.read()函数。

打开摄像头:

cv.VideoCapture(index[, apiPreference])

其中的主要参数介绍如下。

index:视频捕获设备的ID,0表示用默认后端打开默认摄像头。

apiPreference:在有多个视频捕获后端时指定一个后端,如cv.CAP_DSHOW、cv.CAP_MSMF、cv.CAP_V4L等。默认值为cv.CAP_ANY。

打开视频文件:

cv.VideoCapture(filename[, apiPreference])

其中的主要参数介绍如下。

filename:视频文件,它可以是以下类别。

视频文件名,如video.avi。

图像序列,如img_%02.jpg,会逐一读取图像文件img_00.jpg、img_01.jpg、img_ 02.jpg……

视频流的URL(Uniform Resource Locator,统一资源定位符)。

gst-launch格式的GStreamer pipeline字符串。

apiPreference:在有多个视频捕获后端时指定一个后端,如cv::CAP_FFMPEG、cv::CAP_IMAGES、cv::CAP_DSHOW。默认值为cv.CAP_ANY。

下面的例子演示了使用VideoCapture类读视频文件。

import sys
import cv2 as cv
 
 
def main():
    # 打开第一个摄像头
    #cap = cv.VideoCapture(0)
    # 打开视频文件
    cap = cv.VideoCapture("slow_traffic_small.mp4")
 
    # 检查是否打开成功
    if cap.isOpened() == False:
        print('Error opening the video source. ')
        sys.exit()
 
    while True:
        # 读取1帧视频,存放到im
        ret, im = cap.read()
        if not ret:
            print('No image read. ')
            break
 
        # 显示视频帧
        cv.imshow('Live', im)
        # 等待30ms,如果有按键按下则退出循环
        if cv.waitKey(30) >= 0:
            break
 
    # 销毁窗口
    cv.destroyAllWindows()
    # 释放cap
    cap.release()
 
if __name__ == '__main__':
main()

图1-8为读取1帧视频后窗口显示的效果。

图1-8 读取1帧视频后窗口显示的效果

OpenCV提供了VideoWriter类来创建视频文件(写视频),在Linux系统中使用FFMPEG来写视频文件,Windows系统中使用FFMPEG、MSWF或者DSHOW,macOS系统中使用AVFoundation。与读视频文件不同的是,写视频文件需要在创建视频时设置一系列参数,包括文件名、编解码器、视频帧率、视频帧宽度和高度等。

首先创建VideoWriter类对象:

writer=cv.VideoWriter(filename, fourcc, fps, framesize[, iscolor])

其中的主要参数介绍如下。

filename:创建的视频文件名。

fourcc:使用4个字符表示的编解码器,可以是cv.VideoWriter_fourcc ('M', 'J', 'P','G')、cv.VideoWriter_fourcc('X','V',' I','D')、cv.VideoWriter_fourcc ('D',' I','V','X')等。编解码器列表可以在MSDN[1](微软的一个期刊产品)查询。如果使用某种编解码器无法创建视频文件,请尝试其他的编解码器。


[1] 网址为https://docs.microsoft.com/en-us/windows/win32/medfound/video-fourccs

fps:视频帧率。

framesize:视频帧宽度和高度。

iscolor:如果值非0,编码器将按彩色帧进行编码;否则按灰度帧进行编码。

writer:创建的VideoWriter对象。

然后使用函数cv.VideoWriter.writer()将视频帧写入文件:

cv.VideoWriter.write(image)

其中,image 表示待写入的视频帧数据,通常是BGR格式的彩色图像。需要注意,image的尺寸必须与前面的framesize一致。

下面的例子演示了如何写视频文件。本示例将生成一个视频文件,视频的第0帧是一个白色的“0”,第1帧是个白色的“1”,以此类推,共100帧。生成视频文件的播放效果如图1-9所示。

import sys
import numpy as np
import cv2 as cv
 
def main():
    # 设置视频帧的宽度和高度
    frame_size = (320, 240)
 
    # 设置视频帧率
    fps = 25
 
    # 视频编解码格式
    fourcc = cv.VideoWriter_fourcc('M', 'J', 'P', 'G')
 
    # 创建writer
    writer = cv.VideoWriter("myvideo.avi", fourcc, fps, frame_size)
    # 检查是否创建成功
    if writer.isOpened() == False:
        print("Error creating video writer.")
        sys.exit()
 
    for i in range(0, 100):
 
        # 设置视频帧画面
        im = np.zeros((frame_size[1], frame_size[0], 3), dtype=np.uint8)
 
        # 将数字绘制到画面上
        cv.putText(im, str(i), (int(frame_size[0]/3), int(frame_size[1]*2/3)),                                 cv.FONT_HERSHEY_SIMPLEX, 3.0, (255, 255, 255), 3)
 
        # 保存视频帧到文件myvideo.avi
        writer.write(im)
 
    # 释放writer
    writer.release()
 
 
if __name__  == '__main__':
    main()

图1-9 生成视频文件的播放效果