美文网首页
OpenCV 4.0 笔记(一) 图像视频读写与显示

OpenCV 4.0 笔记(一) 图像视频读写与显示

作者: 木一易一 | 来源:发表于2019-06-01 23:02 被阅读0次

    OpenCV 笔记 图像与视频的读写

    读取图片

    文档地址

    imread方法

    Mat imread(const String & filename ,int flags=IMREAD_COLOR);
    

    imread读取图像,返回Mat对象,两个参数,第一个是文件名,支持位图bmp、dib,JPEG图像,PNG,webp,pbm,pgm,ppm,TIFF等多种图像,读取失败则返回空矩阵

    第二个读取方式,默认值是1 flag = 1 返回三通道彩色图 flag = 0 灰度图 flag = -1 原图带alpha通道

    如果返回三通道,编码顺序是BGR。支持读取的图像格式如下

    Currently, the following file formats are supported:

    • Windows bitmaps - *.bmp, *.dib (always supported)
    • JPEG files - *.jpeg, *.jpg, *.jpe (see the Note section)
    • JPEG 2000 files - *.jp2 (see the Note section)
    • Portable Network Graphics - *.png (see the Note section)
    • WebP - *.webp (see the Note section)
    • Portable image format - *.pbm, *.pgm, *.ppm *.pxm, *.pnm (always supported)
    • Sun rasters - *.sr, *.ras (always supported)
    • TIFF files - *.tiff, *.tif (see the Note section)
    • OpenEXR Image files - *.exr (see the Note section)
    • Radiance HDR - *.hdr, *.pic (always supported)
    • Raster and Vector geospatial data supported by GDAL (see the Note section)
    #include<opencv2/opencv.hpp>
    #include<iostream>
    using namespace cv;
    using namespace std;
    int main(int argc, char **argv) {
        // 图片路径 可以是/或者//或者\\或者 /、//、\\混合;但不能是单独的反斜杠 \
        Mat src = imread("C:/Users/muyi/Pictures/pic/5503.jpg");
        imshow("input", src);
        waitKey(0);
        destroyAllWindows();
        return 0;
    }
    
    import cv2
    img = cv2.imread(filename,flags)  # 返回array矩阵,读取失败则是nonetype
    

    图片读取出错处理

    文件损坏、不存在、权限错误等问题导致读取图片失败,程序==不会报错==,但是返回的是空矩阵,不处理后续使用可能会出错。

    Mat src = imread("test.png");
    if (src.empty()) {
            printf("could not load image...\n");
            return -1;
        }
    
    src = cv2.imread("test.png")
    # 断言判断读取是否成功
    assert type(src)==None,"load image error"
    # 或者if语句判断
    if src.all() == None:  #
        print("load image error")
    
    # 或者 try,自定义异常
    def read_img(path):
        img = cv2.imread(path)
        if img == None:   
            raise Exception("load image error")
        return img
    try :
        read_img('eee.png')
    except Exception as err:
        print(err)
    else:
        后续处理
    

    读取的图片属性

    ==C++版本,读取图像以Mat对象形式存储,Python版本以np.array形式存储==

    Mat img = imread("C:\Users\muyi\Pictures\pic\5503.jpg");
    // Mat类的部分属性
    cout << "dims:" << img.dims << endl; // 矩阵的维度
    cout << "rows:" << img.rows << endl; // 矩阵的行数
    cout << "cols:" << img.cols << endl; // 矩阵列数
    cout << "channels:" << img.channels() << endl; // 图像通道数
    cout << "type:" << img.type() << endl; // 表示了矩阵中元素的类型以及矩阵的通道个数
    cout << "depth:" << img.depth() << endl; // 深度
    cout << "elemSize:" << img.elemSize() << endl; // 矩阵一个元素占用的字节数
    cout << "elemSize1:" << img.elemSize1() << endl; //矩阵元素一个通道占用的字节数= elemSize / channels
    // type为 CV_16SC3,那么elemSize = 3 * 16 / 8 = 6 bytes
    

    图像矩阵的type取值:参考博客
    它是一系列的预定义的常量,其命名规则为CV_(位数)+(数据类型)+(通道数)U(unsigned integer)表示的是无符号整数,S(signed integer)是有符号整数,F(float)是浮点数。 C1,C2,C3,C4则表示通道是1,2,3,4

    ==type一般是在创建Mat对象时设定==,如果要取得Mat的元素类型,则无需使用type,使用depth

    depth 矩阵中元素的一个通道的数据类型,这个值和type是相关的。例如 type为 CV_16SC2,一个2通道的16位的有符号整数。那么,depth则是CV_16S。depth也是一系列的预定义值,
    将type的预定义值去掉通道信息就是depth值: CV_8U CV_8S CV_16U CV_16S CV_32S CV_32F CV_64F

    表1. Mat对象type的取值表

    CV_8UC1 CV_8UC2 CV_8UC3 CV_8UC4
    CV_8SC1 CV_8SC2 CV_8SC3 CV_8SC4
    CV_16UC1 CV_16UC2 CV_16UC3 CV_16UC4
    CV_16SC1 CV_16SC2 CV_16SC3 CV_16SC4
    CV_32SC1 CV_32SC2 CV_32SC3 CV_32SC4
    CV_32FC1 CV_32FC2 CV_32FC3 CV_32FC4
    CV_64FC1 CV_64FC2 CV_64FC3 CV_64FC4
    Mat img(3, 4, CV_16UC4, Scalar_<uchar>(1, 2, 3, 4));//3X4的矩阵 16位无符号4通道
    //Scalar_是一个模板向量,用来初始化矩阵的每个像素,因为矩阵具有4个通道,Scalar_有四个值。
    

    python版本cv图像格式,type(img) = numpy.ndarray 因此,它具有array的一切属性和方法,而不同于c++版本中Mat的deepth()、type()等写法。

    img = cv2.imread("test.png")
    print(img.shape) # [M,N,K] 行 列 通道数
    print(img.size) # M*N*K
    # 一些特殊方法
    print(img.mean()) #平均值 img.sum() 元素和 std()标准差 等等
    img = img.flatten()
    img = img.ravel()
    img = img.reshape()
    

    写入图片

    [==文档地址==][https://docs.opencv.org/4.0.0/d4/da8/group__imgcodecs.html#gabbc7ef1aa2edfaa87772f1202d67e0ce]

    imwrite方法

    bool cv::imwrite(const String & filename, InputArray img , const std::vector<int>&params = std::vector<int>)
    
    
    retval = cv2.imwrite(filename, img[, params])
    

    支持写入的图像格式与imread一致。8位单通道或3通道BGR编码数据才可以使用该函数。

    16位无符号图像数据,可以保存为PNG, JPEG2000 和TIFF

    32位浮点图像数据,可以保存为TIFF,OpenEXR,Radiance HDR格式

    3通道32位浮点图像数据被保存TIFF格式,则存储为高动态范围图像(High-Dynamic Range,HDR),每个像素4字节

    8位带透明度的PNG图像保存时需要创建第四个通道,最终数据格式是BGRA

    ==对于非上述类型数据,可以通过mat.convertTo函数进行转换,然后写入图像文件==

    In general, only 8-bit single-channel or 3-channel (with 'BGR' channel order) images can be saved using this function

    • 16-bit unsigned (CV_16U) images can be saved in the case of PNG, JPEG 2000, and TIFF formats
    • 32-bit float (CV_32F) images can be saved in TIFF, OpenEXR, and Radiance HDR formats; 3-channel (CV_32FC3) TIFF images will be saved using the LogLuv high dynamic range encoding (4 bytes per pixel)
    • PNG images with an alpha channel can be saved using this function. To do this, create 8-bit (or 16-bit) 4-channel image BGRA, where the alpha channel goes last. Fully transparent pixels should have alpha set to 0, fully opaque pixels should have alpha set to 255/65535 (see the code sample below).

    带透明度的png图像

    c++源码,创建带透明度的PNG图像。(出自cv文档imwrite函数说明)

    #include <opencv2/imgcodecs.hpp>
    using namespace cv;
    using namespace std;
    static void createAlphaMat(Mat &mat)
    {
        CV_Assert(mat.channels() == 4); // 等同于c++里面的assert,条件为false返回错误信息
        for (int i = 0; i < mat.rows; ++i)
        {
            for (int j = 0; j < mat.cols; ++j)
            {  // 行列遍历进行赋值  Vec是OpenCV定义的向量模板类
                Vec4b& bgra = mat.at<Vec4b>(i, j);
                bgra[0] = UCHAR_MAX; // Blue define UCHAR_MAX 0xff 
                bgra[1] = saturate_cast<uchar>((float (mat.cols - j)) / ((float)mat.cols) * UCHAR_MAX); // Green  saturate_cast<unchar>是一个防止颜色操作溢出的函数,数据小于0置0;大于255置255
                bgra[2] = saturate_cast<uchar>((float (mat.rows - i)) / ((float)mat.rows) * UCHAR_MAX); // Red
                bgra[3] = saturate_cast<uchar>(0.5 * (bgra[1] + bgra[2])); // Alpha
            }
        }
    }
    int main()
    {
        // Create mat with alpha channel
        Mat mat(480, 640, CV_8UC4);
        createAlphaMat(mat); // 创建矩阵数据
        vector<int> compression_params;
        compression_params.push_back(IMWRITE_PNG_COMPRESSION); // 枚举值16
        compression_params.push_back(9);
        bool result = false;
        try
        {
            result = imwrite("alpha.png", mat, compression_params);
        }
        catch (const cv::Exception& ex)
        {
            fprintf(stderr, "Exception converting image to PNG format: %s\n", ex.what());
        }
        if (result)
            printf("Saved PNG file with alpha data.\n");
        else
            printf("ERROR: Can't save PNG file.\n");
        return result ? 0 : 1;
    }
    
    

    python实现

    import cv2
    import numpy as np
    img = np.zeros((480,640,4),np.float)
    def saturate(num):  ## 不清楚cv2防止颜色溢出函数,因此自定义了一个类似的函数
        if num >255.0:
            return 255.0
        if num <0.0:
            return 0.0
        else:
            return num
    rows = img.shape[0]
    cols = img.shape[1]
    for i in range(rows):
        for j in range(cols):
            temp = img[i][j] # 像素[i][j]位置的四个通道数据
            temp[0] = 0xff
            temp[1] = saturate((cols-j)/rows*255.0)
            temp[2] = saturate((rows-j) /cols *255.0)
            temp[3] = saturate(temp[2]+temp[1])  #可以看到不同透明度,显示效果不一样
    cv2.imwrite('alpha.png',img)
    
    

    效果如图


    python写入带透明度的png图

    读取视频

    videoCapture结构体,可以读取文件视频、网页视频流和摄像头的数据。

    [文档地址][https://docs.opencv.org/4.0.0/d8/dfe/classcv_1_1VideoCapture.html#a57c0e81e83e60f36c83027dc2a188e80]

    capture结构体

    函数原型 ==VideoCapture (const String &filename, int apiPreference=CAP_ANY)==

    ==cv2.VideoCapture(filename[, apiPreference])==

    Opens a video file or a capturing device or an IP video stream for video capturing with API Preference

    第一个参数是数据流或视频路径,第二个是API设置,读取的摄像头编号,默认CAP_ANY=0,自动检测摄像头。多个摄像头时,使用索引0,1,2,...进行编号调用摄像头。 apiPreference = -1时单独出现窗口,选取相应编号摄像头

    通常使用 ==bool isOpened()==判断是否打开视频或摄像头成功。

    下一帧与释放

    cap >> frame或者 ==cap.read(frame)==读取下一帧,函数定义是CV_WRAP virtual bool read(OutputArray image);读取帧失败会返回布尔值false,因此可以进行判断

    ==视频帧读取的 read 、grab 、retrieve三种方式==:

    retrieve速度比grab慢很多,有时可以通过grab跳过不需要的帧,而不需要用read解码每一帧。

    1. cap.read(frame) 结合grab和retrieve的功能,抓取下一帧并解码
    2. cap.grap() 从设备或视频获取下一帧,获取成功返回true否则false
    3. cap.retrieve(frame) 在grab后使用,对获取到的帧进行解码,也返回true或false
    #include <opencv2/opencv.hpp>
    #include <iostream>
    using namespace cv;
    using namespace std;
    int main(int, char**)
    {
        Mat frame; //定义帧
        VideoCapture cap;
        int deviceID = 0;             // 0 = open default camera
        int apiID = cv::CAP_ANY;      // 0 = autodetect default API
        
        cap.open(deviceID + apiID);  //打开摄像头
        // check if we succeeded
        if (!cap.isOpened()) {
            cerr << "ERROR! Unable to open camera\n";
            return -1;
        }
        //--- GRAB AND WRITE LOOP
        cout << "Start grabbing" << endl
            << "Press any key to terminate" << endl;
        for (;;)
        {
            // wait for a new frame from camera and store it into 'frame'
            cap.read(frame);  //读取下一帧,并可以返回读取成功与否
            //等价于 cap >> frame 同时等价于 cap.grab();cap.retrieve(frame);
            if (frame.empty()) {
                cerr << "ERROR! blank frame grabbed\n";
                break;
            }
            imshow("Live", frame);
            if (waitKey(100) >= 0)
                break;
        }
        // the camera will be deinitialized automatically in VideoCapture destructor
        return 0;
    }
    
    
    import cv2
    cap = cv2.VideoCapture(0)
    if cap.isOpened():
        while True:
            ret, prev = cap.read()  # ret是读取状态,prev下一帧
            """
            等价于
           if cap.grab():
                ret, prev = cap.retrieve()
            """
            if ret==True:
                cv2.imshow('video', prev)
            else:
                break
            if cv2.waitKey(20)==27:
                break
    cv2.destroyAllWindows()
    
    

    release()在使用完后进行手动释放capture对象

    读取视频属性 get()方法

    函数原型:==double cv::VideoCapture::get(int propId)==

    ==retval=cv2.VideoCapture.get(propId)==

    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后端支持这个功能)
    

    写入视频

    函数文档https://docs.opencv.org/4.0.0/dd/d9e/classcv_1_1VideoWriter.html#ad59c61d8881ba2b2da22cff5487465b5

    VideoWriter类

    cv::VideoWriter::VideoWriter(const String & filename,int fourcc,double fps,Size frameSize,bool  isColor = true )  // isColor
    
    
    cv2.VideoWriter(filename, fourcc, fps, frameSize[, isColor])
    cv.VideoWriter(filename, apiPreference, fourcc, fps, frameSize[, isColor])
    
    

    写入视频需要指定视频的==帧率fps== , ==帧尺寸framesize== ,==编码格式fourcc==

    framesize的大小应该与写入的每一帧图像尺寸大小一致

    输出文件类型要与编码类型一致

    几种常用视频编解码器

    生成文件占用空间最小的编码方式是MPEG-4.2 。在VideoWriter类的构造函数参数为CV_FOURCC('M', 'P', '4', '2') 。

    最大的是MPEG-1,对应在VideoWriter类的构造函数参数为CV_FOURCC('P','I','M','1') ,所占磁盘空间是前者的5.7倍。

    fourcc定义

    static int cv::VideoWriter::fourcc(char c1,char c2,char c3,char c4 )
    
    retval  =  cv2.VideoWriter_fourcc(  c1, c2, c3, c4  )
    
    cv2.VideoWriter_fourcc('m','p','a','v') AVI或者mp4文件 
    cv2.VideoWriter_fourcc('M','J','P','G') avi或者mp4 motion-jpeg编码
    cv2.VideoWriter_fourcc('P','I','M','I') 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','1')  flash视频,后缀名 flv
    
    

    c++创建writer对象和写入图像帧有两种方式

    #include <opencv2/opencv.hpp>
    #include<opencv2/video.hpp>
    using namespace cv;
    using namespace std;
    int main() {
        //方法1  定义对象,然后使用open方法开启  opencv2/videoio.hpp定义了fourcc
        VideoWriter out;
        int fourcc = out.fourcc('M', 'J','P', 'G');
        out.open("video.mp4", fourcc, 30.0, cv::Size(640, 480), // 单帧图片分辨率为 640x480
        true // 只输入彩色图
        );
    
    /* 方法2  构造函数*/  
       VideoWriter out(
           const string& filename, // 输入文件名
            int fourcc, // 编码形式,
            double fps, // 输出视频帧率
            cv::Size frame_size, // 单帧图片的大小
            bool is_color = true // 如果是false,可传入灰度图像 
        );
    }
    
    

    python将文件夹中所有图片写入视频

    import cv2
    import os
    
    im_dir = 'det'  #图片路径
    video_dir = 'out4.avi' #输出视频路径
    fps = 23.977 #输出视频路径
    #图片数 
    num = 888
    img_size = (500,300)
    #fourcc = cv2.cv.CV_FOURCC('M','J','P','G')#opencv2.4
    fourcc = cv2.VideoWriter_fourcc('M','J','P','G') #opencv3.0之后的写法
    videoWriter = cv2.VideoWriter(video_dir, fourcc, fps, img_size) #定义视频写入类
    
    pics = os.listdir(im_dir)
    
    for i in range(num):
        im_name = os.path.join(im_dir, str(i)+'.jpg')
        frame = cv2.imread(im_name)
        if type(frame) != None:
            videoWriter.write(frame) #写入帧
    
    videoWriter.release()
    print ('finish')
    
    

    显示窗口Windows

    可以自定义窗口显示图像的模式

    窗口函数

    [官方文档][https://docs.opencv.org/4.0.0/d7/dfc/group__highgui.html#ggabf7d2c5625bc59ac130287f925557ac3acf621ace7a54954cbac01df27e47228f]

    ==函数原型== void nameWindow(const string& winname,int flags = WINDOW_AUTOSIZE) ;第一个参数是窗口名字,第二个是显示模式,flag 默认值 是window_autosize

    1. WINDOW_AUTOSIZE 窗口大小自动适应图片大小,并且不可手动更改
    2. WINDOW_NORMAL 用户可以改变这个窗口大小
    3. WINDOW_OPENGL 窗口创建的时候会支持OpenGL ,且可以更改窗口大小

    定义窗口名称,imshow时可以指定让图像在该窗口显示

    namedWindow("input", WINDOW_AUTOSIZE);
    imshow("input", src);
    
    
    cv2.namedWindow("input", cv2.WINDOW_AUTOSIZE)
    cv2.imshow("input", src)
    
    

    一个窗口显示多张图片

    另一种方法是matplotlib

    OpenCV一个窗口只能显示一张图片。但可以通过组合多张图,实现多张图在一个窗口的视觉效果。

    Python numpy组合

    用np.hstack或者np.vstack组合矩阵 ==若是要让各个图片之间显示间隔,可以插入0或255的列做间隔==

    #图1
    img = cv2.imread(r'C:\Users\muyi\Pictures\pic\489321.jpg')
    #图2
    img2 = cv2.imread(r'C:\Users\muyi\Pictures\pic\489323.jpg')
    #拼接
    imgs = np.hstack([img,img2])
    #展示多个
    cv2.imshow("mutil_pic", imgs)
    #等待关闭
    cv2.waitKey(0)
    
    
    显示多个图片

    ==OpenCV一个窗口显示多个图片的注意事项==:

    读入的多个图片必须具有==相同的尺寸、通道数==。若是彩图、灰度图同时显示,会出现窗口黑屏现象

    matplotlib划分窗口显示多张图:

    利用 plt.subplot(m,n,k)划分窗口即可,类似matlab的绘图,而且各个窗口可以显示不同格式的图像

    相关文章

      网友评论

          本文标题:OpenCV 4.0 笔记(一) 图像视频读写与显示

          本文链接:https://www.haomeiwen.com/subject/wwuztctx.html