運行平臺
- 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 <Windows.h>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/features2d.hpp>
#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<uchar>(i, j) = 255 - readImg.at<uchar>(i, j);
break;
case 3:
colorValue = readImg.at<cv::Vec3b>(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<cv::Vec3b>(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 <stdio.h>
#include <iostream> // for standard I/O
#include <Windows.h>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/video.hpp>
#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