Openvc對圖像和視頻進行訪問最全講解

運行平臺

  • OpenCV 4.2
  • VS 2019

圖像在內存中的存儲和操作

從圖像數據性質推理圖像在內存中的存儲方式:

圖像數據是多通道:1/3/4通道; 像素數據類型:1位,uchar,short,int,float,double等;

OpenCV中的C++ Mat類:

class CV_EXPORTS Mat
{
public:
    // …大量方法…
    ...
 
    /*! includes several bit-fields:
         - the magic signature
         - continuity flag
         - depth
         - number of channels
     */
    int flags;

    //! the array dimensionality, >= 2
    int dims;

    //! the number of rows and columns or (-1, -1) when the array has more than 2 dimensions
    int rows, cols;

    //! pointer to the data
    uchar* data;

    // other members
    ...
};

圖像在內存中的基本要素:
①列數cols = 寬度,行數rows = 高度,通道數(彩色空間類型),位深度(數據類型:8位無符號8U,32位浮點32F等);
②像素存儲區(uchar* data;)

創建Mat矩陣–通過構造函數:

Mat img(Size(320,240), CV_8UC3);`

寬度(320),高度(240),數據類型8U(8位無符號),3通道

創建Mat矩陣–通過create函數:


> Mat img;
> 
> img .create(Size(320,240), CV_32FC3);//32位浮點型

創建載入圖像文件:

Mat img = imread("lena.jpg", CV_LOAD_IMAGE_COLOR);

像素數據在內存中的組織方式:

PS:內存是順序排列的字節陣列
在這裏插入圖片描述

通過Mat方法訪問圖像像素

  • 3通道24位深度(表達每個通道佔用8位)彩色圖像像素的訪問:
cv::Vec3b pixlsValue; //==表達這是一個3字節像素數據節點;

pixlsValue = img.at<cv::Vec3b>(y, x);
//y表達行號;x表達列號;尖括號<...>中的內容是C++模板,指定圖像矩陣的數據類型;
  • 像素值的修改方法:
pixlsValue.val[0] = xxx;
pixlsValue.val[1] = xxx;
pixlsValue.val[2] = xxx;
  • 修改後的像素值返回到存儲器:

img.atcv::Vec3b(y, x) = pixlsValue;其中,y和x爲像素座標;

通過C++指針方法訪問圖像像素

1.圖像行列數Rows和Cols,數據首地址 uchar* data,通道數chaN,每行總字節數byteR可通過矩陣類接口獲得;

2.每個像素的字節數byteN= 位深度/8;位深度24位彩色圖像每個像素佔用3字節;float類型單通道圖像每個像素佔用4字節;…

3.每行總字節數byteR = 每像素字節數 * 列數 = byteN * Cols;

4.第row行第col列像素向量的地址偏移量(相對像素數據首字節):

pixlAddr = row * byteR + col * byteN ;

5.像素向量首字節地址爲 (uchar *)&data [pixlAddr];

6.如果是uchar型圖像,則像素首地址爲:

uchar  *pixlPTR = (uchar*) data [pixlAddr];

7.果是float型圖像,則像素首地址爲:

float *pixlPTR = (float*) data [pixlAddr];

8.對於3通道8位(位深度24位)彩色圖像(RGB),則3個彩色通道像素存儲位置爲:pixlPTR[0] = B 、pixlPTR[1] = G、pixlPTR[2] = R;

9.通過實驗可以證明,使用C++指針訪問像素,時間開銷遠低於調用cv::Mat.at()方法;

圖像像素訪問例程

#include "stdafx.h"
#include &lt;Windows.h&gt;
#include &lt;opencv2/core.hpp&gt;
#include &lt;opencv2/imgproc.hpp&gt;
#include &lt;opencv2/highgui.hpp&gt;
#include &lt;opencv2/features2d.hpp&gt;
 
#ifdef _DEBUG
#pragma comment(lib, "opencv_world420d.lib")
#else
#pragma comment(lib, "opencv_world420.lib")
#endif

int main(int argc, char* argv[])
{
    char curPathName[384] = "";

    char FilePath[384] = "lena.jpg";

    if (strlen(curPathName) > 0)
         sprintf(FilePath, "%s\\lena.jpg", curPathName); //圖片文件路徑

    cv::Mat readImg = cv::imread(FilePath, cv::IMREAD_COLOR); //讀取彩色圖片: opencv420的IMREAD_COLOR與之前版本不同

    if (readImg.empty())
    {
         printf("error#41: 未能讀取到圖片,檢查文件是否存在!回車退出!\n圖片路徑=%s\n", FilePath);
         fgets(FilePath, 127, stdin);

         return -1;

    }

    cv::imshow("readImg", readImg);//顯示圖片,窗口標題爲readImg
    cv::Mat imgFloat;
    readImg.convertTo(imgFloat, CV_32F);

    //在C++中,訪問矩陣中的像素有兩種方法:通過矩陣方法和通過指針;通過指針是最快的訪問方法
    cv::Vec3b colorValue; uchar greyValue;
    int channelNUMS = readImg.channels();

    uchar *pDATA = readImg.data;         //爲了通過指針訪問存儲區,我們獲取存儲區的首指針
    int dataSTEP = readImg.step1();  //注意,當圖像列數cols=512,3通道時,dataSTEP值爲512 * 3 = 1536;
    int dataSTEP2 = imgFloat.step1();    //注意,當圖像列數cols=512,3通道時,dataSTEP值爲512 * 3 = 1536;

    int64 timeSTART = cv::getTickCount();

    for (int i = 0; i< imgFloat.rows / 2; i++) {
         for (int j = 0; j< readImg.cols; j++) {
             //我們使用條件編譯開關來控制使用那一段代碼
             #ifndef ACCESS_IAMGE_BY_PTR      //使用Mat的At()方法訪問像素:花費時間幾乎是指針方法的10倍;

             switch (channelNUMS)
             {
             case 1:
                  readImg.at&lt;uchar&gt;(i, j) = 255 - readImg.at&lt;uchar&gt;(i, j);

                  break;
             case 3:
                  colorValue = readImg.at&lt;cv::Vec3b&gt;(i, j);
                  colorValue.val[0] = 255 - colorValue.val[0];
                  colorValue.val[1] = 255 - colorValue.val[1];
                  colorValue.val[2] = 255 - colorValue.val[2];
                  readImg.at&lt;cv::Vec3b&gt;(i, j) = colorValue;

                  break;
             }

             #else              //使用指針方法訪問像素:花費時間幾乎是Mat的At()方法的1/10倍;
             if (pDATA[i * dataSTEP * sizeof(uchar) + j * channelNUMS + 0] > 200 &&
                  pDATA[i * dataSTEP * sizeof(uchar) + j * channelNUMS + 1] > 200 &&
                  pDATA[i * dataSTEP * sizeof(uchar) + j * channelNUMS + 2] > 200)
             {
                  pDATA[i * dataSTEP * sizeof(uchar) + j * channelNUMS + 0] = 64;
                  pDATA[i * dataSTEP * sizeof(uchar) + j * channelNUMS + 1] = 64;
                  pDATA[i * dataSTEP * sizeof(uchar) + j * channelNUMS + 2] = 0;
             }
             else
             {
                  pDATA[i * dataSTEP * sizeof(uchar) + j * channelNUMS + 0] = 255 - pDATA[i * dataSTEP * sizeof(uchar) + j * channelNUMS + 0];
                  pDATA[i * dataSTEP * sizeof(uchar) + j * channelNUMS + 1] = 255 - pDATA[i * dataSTEP * sizeof(uchar) + j * channelNUMS + 1];
                  pDATA[i * dataSTEP * sizeof(uchar) + j * channelNUMS + 2] = 255 - pDATA[i * dataSTEP * sizeof(uchar) + j * channelNUMS + 2];
             }
         #endif ACCESS_IAMGE_BY_PTR
         }
    }

    int64 timeEND = cv::getTickCount(); //處理結束時間

    double usingTime = (double)(timeEND - timeSTART) / (double)cv::getTickFrequency();//統計矩陣操作花費的時間,單位ms;
    printf("INFO#128: 圖像處理耗時:%.6f 秒\n", usingTime);

    cv::imshow("Nagtive Film", readImg);
    cv::imwrite("Nagtive_Film.bmp", readImg);       //保存處理後的圖像爲bmp文件
    cv::waitKey();//等待按下鍵盤,這是程序退出前讓顯示窗得以被顯示的方法!

    return 0;
}

VS 2019實際運行效果如下:

在這裏插入圖片描述

視頻圖像的特點

1.視頻源:攝像頭和視頻文件;
2.視頻圖像格式:AVI、VCD、SVCD、DVD、MPG、WMV、RM、RMVB、MOV、MP4、MPEG4、3GP、H264等等。
3.視頻圖像在硬盤中存儲要求更高壓縮比,壓縮、解壓縮處理的計算開銷較大;
4.無論什麼格式視頻圖像,載入到內存後將被轉換爲像素矩陣,並存儲到內存中。此時,像素訪問方法將與靜止圖像沒有差異。

通過C++調用OpenCV庫載入圖像

這裏使用C++和OpenCV庫將視頻載入內存,並將視頻圖像記錄到另一個視頻文件中

可以發現:

  • 視頻是一個圖像序列;
  • 圖像序列中的每幅圖像仍以像素矩陣存儲在內存中,訪問方法與靜止圖像相同。
  • 使用OpenCV記錄的視頻文件,僅僅存儲了經過編碼後的視頻流數據,還不符合視頻文件格式標準,不能使用常規的播放器來播放,需要使用一些特殊的視頻播放器(例如VLC)來播放。
//#include "stdafx.h"
#include &lt;stdio.h&gt;
#include &lt;iostream&gt;   // for standard I/O
#include &lt;Windows.h&gt;
#include &lt;opencv2/core.hpp&gt;
#include &lt;opencv2/imgproc.hpp&gt;
#include &lt;opencv2/highgui.hpp&gt;
#include &lt;opencv2/features2d.hpp&gt;
#include &lt;opencv2/video.hpp&gt;

#ifdef _DEBUG0
#pragma comment(lib, "opencv_world420d.lib")
#else
#pragma comment(lib, "opencv_world420.lib")
#endif


int main(int argc, char **argv)
{

    char curPathName[384] = "";

    //==【01】== 打開視頻文件或攝像頭
    char FilePath[384] = "HighWay_Video_001.Avi";

    if (strlen(curPathName) > 0)
         sprintf(FilePath, "%s\\HighWay_Video_001.Avi", curPathName);   //圖片文件路徑
                                                             //==【01】== 視頻輸入選擇和打開:攝像頭或視頻文件
         cv::String  path = FilePath;

         if(argc > 1)
             path = argv[1];        //可通過命令行第二個參數指定視頻文件名
         //path = "";              //====>>>>如果啓用此行,將從攝像頭中讀取視頻圖像<<<<====
         cv::VideoCapture cap; //VideoCapture類實例化,使用缺省攝像頭

         if(path.length() == 0)
             cap.open(0);       //打開第一個攝像頭
         else
             cap.open(path);  //打開視頻文件

         if(!cap.isOpened()) // check if we succeeded
             return -1;

    cv::Mat frame, greyFrame;

    //==【02】== 視頻記錄類實例化
         cv::VideoWriter outputVideo;                                        // Open the output
         int saveAVIenable = 0;            //非0時記錄視頻

         if(saveAVIenable){
             bool rtn = outputVideo.open("Result003.avi", cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), 10,
                 cv::Size((int)cap.get(cv::CAP_PROP_FRAME_WIDTH),  (int)cap.get(cv::CAP_PROP_FRAME_HEIGHT) ) , true);
             if(!rtn)
                  return -2;
             }
 
    //==【03】== 創建一個運動視頻背景提取對象:分離背景和運動對象: 暫時未使用!
         cv::Ptr<cv::BackgroundSubtractorMOG2> bgsubtractor= cv::createBackgroundSubtractorMOG2();

    //==【04】== 命名兩個顯示窗口
         cv::namedWindow("SRC", 0);
         cv::namedWindow("GREY", 0);

    //==【05】== 循環讀取視頻
         int frameNums  = 0;

    for(;;)
         {
         double t1 = (double)cv::getCPUTickCount();

         cap.read(frame);     //讀取一個視頻幀
         if(frame.empty())
             break;

         if(saveAVIenable)
             outputVideo.write(frame);   //寫視頻文件

         //==>> 取視頻幀frame的一個矩形區,轉換爲灰度圖像,存儲到greyFrame
         cv::cvtColor(frame(cv::Rect(0, 150, frame.cols, frame.rows - 150)), greyFrame, cv::COLOR_RGB2GRAY);
         //==>> 將圖像greyFrame的長寬分別縮小到1/2
         cv::resize(greyFrame, greyFrame, cv::Size(greyFrame.cols/2, greyFrame.rows/2));
         //==>> 使用7*7卷積窗,平滑灰度圖像greyFrame
         cv::blur(greyFrame, greyFrame, cv::Size(7,7));
         // ==>> 計算處理時間
         double t2 = (double)cv::getCPUTickCount();
         printf("Line74 Video Play Delta Time = %.3fms\n", 1e0 * (t2 - t1)/(double)cv::getTickFrequency());

         // ==>> 顯示處理結果
         imshow("SRC", frame);
         if(!greyFrame.empty())
             imshow("GREY", greyFrame);

         // ==>> 按下鍵盤q退出
         int keycode = cv::waitKey(30);
         if( keycode == 'q')
             break;

         frameNums++;
         }//for

    if(saveAVIenable)
         outputVideo.release();
    cap.release();

return 0;
}

VS 2019實際運行效果如下:

在這裏插入圖片描述
創作不易,點個贊吧!!

版權聲明:如無特殊說明,文章均爲本站原創,轉載請註明出處
本文鏈接:https://blog.csdn.net/wsad861512140

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章