这里写自定义目录标题
版权声明:本文为博主原创文章,未经博主允许不得转载(pan_jinquan) https://blog.csdn.net/guyuealian/article/details/78540206 </div>
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-cd6c485e8b.css">
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-cd6c485e8b.css">
<div class="htmledit_views" id="content_views">
<h1 id="OpenCV%E5%B8%B8%E8%A7%81%E7%9A%84%E4%BC%98%E5%8C%96%E9%97%AE%E9%A2%98%E5%92%8C%E6%8A%80%E5%B7%A7" style="margin-left:0px;"><a name="t0"></a>OpenCV常见的优化方法和技巧总结</h1>
【尊重原创,转载请注明出处】http://blog.csdn.net/guyuealian/article/details/78540206
目录
一、OpenCV常见的优化方法总结
1.1 cv::imread()设置reduce模式:
cv::imread()设置reduce模式, 读取缩放的低分辨率小图,或者直接读取灰度图,可以做到自适应,先用EXIF信息读取图像的分辨率,当分辨率大于一定阈值,则设置读取模式为:IMREAD_REDUCED_COLOR_2或者IMREAD_REDUCED_COLOR_4 ,避免内存过大以及编程resieze耗时
1.2 查表法:LUT,
使用lut的方法法,远快于每个像素都计算的方法
1.3 像素遍历
openCV像素遍历常用的是三种方法:ptr指针,迭代器(iterator)以及动态地址at。
使用Mat的ptr指针进行图像遍历更加高效,
特别的:一般图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,Mat提供了一个检测图像是否连续的函数isContinuous()。当图像连通时,我们就可以把图像完全展开,看成是一行进行处理。
动态地址at不适合用于像素遍历,速度太慢了,比较适合随机访问的方式
1.4 openCL加速:使用UMat结构代替Mat
高性能:OpenCL的相关用法:UMat
在OpenCV3中,OCL module已经被舍弃。而是使用更易上手的Transparent API来替代 OCL module。因此只需要使用 UMat来替换Mat,而其余的代码保持不变,即可实现加速.
Mat转换成UMat可以使用Mat::copyTo(OutputArray dst),也可以使用Mat::getUMat(int access_flags)
-
#include <chrono>
-
#include <opencv2/opencv.hpp>
-
#define millisecond 1000000
-
#define DEBUG_PRINT(...) printf( __VA_ARGS__); printf("\n")
-
#define DEBUG_TIME(time_) auto time_ =std::chrono::high_resolution_clock::now()
-
#define RUN_TIME(time_) (double)(time_).count()/millisecond
-
using
namespace
std;
-
-
-
void main() {
-
string image_path =
"1.jpg";
-
cv::Mat image1 = cv::imread(image_path);
-
cv::Mat dest1;
-
DEBUG_PRINT(
"image size:[%d,%d]", image1.cols, image1.rows);
-
//Mat convert to UMat
-
//cv::UMat image2= image1.getUMat(cv::ACCESS_FAST);//ACCESS_READ, ACCESS_WRITE, ACCESS_RW和ACCESS_FAST
-
cv::UMat image2;
-
image1.copyTo(image2);
-
cv::UMat dest2;
-
-
DEBUG_TIME(T0);
-
cv::blur(image1, dest1, cv::Size(
15,
15));
-
DEBUG_TIME(T1);
-
cv::blur(image2, dest2, cv::Size(
15,
15));
-
DEBUG_TIME(T2);
-
-
//UMat convert to Mat
-
cv::Mat dest3;
-
dest2.copyTo(dest3);
-
DEBUG_PRINT(
"CPU:%3.3fms", RUN_TIME(T1 - T0));
-
DEBUG_PRINT(
"GPU:%3.3fms", RUN_TIME(T2 - T1));
-
}
image size:[2000,3008]
CPU:18.039ms
GPU:9.623ms
说明:图像越大,计算越复杂时,使用OpenCL加速的效果更明显,如果使用分辨率小图,其GPU的计算速度未必比CPU的快!!
参考资料: https://blog.csdn.net/amusi1994/article/details/79529870
《OpenCV3.x中UMat对象介绍与使用》https://blog.csdn.net/jia20003/article/details/69802932
1.5 release内存释放
1.6 多线程加速处理OpenMP
举个例子:并行化for语句
-
#pragma omp parallel for [clause[clause…]]
-
for(index = first; qualification; index_expr)
-
{…}
第一句中[]的部分是可选的,由自己的程序并行特点而定。大家先不要把精力放到这里面。后面的文章中会继续讲解的。
编写规则
1、index的值必须是整数,一个简单的for形式:for(int i = start; i < end; i++){…} 。
2、start和end可以是任意的数值表达式,但是它在并行化的执行过程中值不能改变,也就是说在for并行化执行之前,编译器必须事先知道你的程序执行多少次,因为编译器要把这些计算分配到不同的线程中执行。
3、循环语句只能是单入口但出口的。这里只要你避免使用跳转语句就行了。具体说就是不能使用goto、break、return。但是可以使用continue,因为它并不会减少循环次数。另外exit语句也是可以用的,因为它的能力太大,他一来,程序就结束了。
openMP参考资料:
https://www.cnblogs.com/ospider/p/5265975.html
《openMP编程探索1——编程基础》:https://blog.csdn.net/bendanban/article/details/6302857
《openMP编程探索2——循环并行化》:https://blog.csdn.net/bendanban/article/details/6303100
1.7 使用积分图:integral
积分图定义为:积分图中坐P(x,y)的值为其左上角的所有像素之和
如上图所示,为了求该矩形区域的灰度之和。我们可以用以下公式表示:
需要注意的是,这里的总和并不包含点A,B,C的像素值。如上图所示的小像素点,A,B,C所在的像素点不在矩形区域以内。
另外,有的时候不一定只是像素灰度值和,只要符合积分图这一种计算思想的,均可以用积分图来简化计算。
如果是求矩形区域的像素点平方和,也可以用。
二、OpenCV常用技巧总结
释放Mat图像内存空间:
-
Mat image = imread(
"D:\\OpencvTest\\1.jpg");
-
image.release();
Mat多通道合并和分割
-
-
#include <opencv2/opencv.hpp>
-
using
namespace
std;
-
using
namespace cv;
-
-
/*
-
将多个mat合并为多通道mat
-
*/
-
cv::
Mat mergeMultiChannels(cv::Mat A,cv::Mat B) {
-
cv::Mat AB;
-
vector<cv::Mat> ABchannels;
-
ABchannels.push_back(A);
-
ABchannels.push_back(B);
-
cv::merge(ABchannels, AB);
-
return AB;
-
}
-
-
/*
-
将6通道的mat分割成2个三通道的mat
-
*/
-
void splitMultiChannels(cv::Mat mat,cv::Mat &A,cv::Mat &B) {
-
vector<cv::Mat> channels;
-
cv::split(mat, channels);
//分割image1的通
-
vector<cv::Mat> Avec, Bvec;
-
Avec.push_back(channels[
0]);
-
Avec.push_back(channels[
1]);
-
Avec.push_back(channels[
2]);
-
-
Bvec.push_back(channels[
3]);
-
Bvec.push_back(channels[
4]);
-
Bvec.push_back(channels[
5]);
-
-
cv::merge(Avec, A);
-
cv::merge(Bvec, B);
-
}
-
void main(){
-
int width =
100;
-
int height =
100;
-
//cv::Mat AB = cv::Mat::zeros(cv::Size(width,height),CV_32FC(6));
-
cv::Mat A1 = cv::Mat::zeros(cv::Size(width, height), CV_32FC3) + cv::Scalar(
0.1,
0.2,
0.3);
-
cv::Mat B1 = cv::Mat::zeros(cv::Size(width, height), CV_32FC3) + cv::Scalar(
0.4,
0.5,
0.6);
-
cv::Mat AB=mergeMultiChannels(A1, B1);
//合并为6通道
-
cv::Mat A2,B2;
-
splitMultiChannels(AB, A2, B2);
//分割为三通道
-
-
return;
-
}
释放图像通道分割的图像空间
-
std::
vector<cv::Mat> layers;
-
split(image, layers);
-
// free memory
-
for (
auto ii =
0; ii < layers.size(); ii++)
-
layers[ii].release();
-
layers.clear();
计算运行时间
-
using
namespace cv;
-
-
//设置宏定义
-
#define TB__(A) int64 A; A = cv::getTickCount()
-
#define TE__(A) cout << #A << " : " << 1.E3 * double(cv::getTickCount() - A)/double(cv::getTickFrequency()) << "ms" << endl
-
-
-
// 使用方法:
-
TB__(cpu_cvt);
-
#pragma omp parallel for num_threads(4)
-
for (
int k =
0; k < REPEATES; k++)
-
cv::cvtColor(cpu_src, cpu_dst, CV_BGR2Lab);
-
TE__(cpu_cvt);
-
-
#include <chrono>
-
-
//run times test...
-
#ifdef __DEBUG__TIME__ON
-
#define LOG_TIME LOGE
-
#define RUN_TIME(time_) (double)(time_).count()/millisecond
-
-
-
//设置计算运行时间的宏定义
-
#define DEBUG_TIME(time_) auto time_ =std::chrono::high_resolution_clock::now()
-
#define DEBUG_TIME_PRINT(time_) printf("run time: %s=%3.3f ms\n", #time_,(double)(time_).count()/millisecond)
-
#else
-
#define DEBUG_TIME(time_)
-
#endif
关于vector内存释放的问题:
由于vector的内存占用空间只增不减,比如你首先分配了10,000个字节,然后erase掉后面9,999个,留下一个有效元素,但是内存占用仍为10,000个。所有内存空间是在vector析构时候才能被系统回收。empty()用来检测容器是否为空的,clear()可以清空所有元素。但是即使clear(),vector所占用的内存空间依然如故,无法保证内存的回收。
如果需要空间动态缩小,可以考虑使用deque。如果是vector类型,可以考虑用swap()来帮助你释放内存。具体方法如下:
标准模板:
-
//放在头文件
-
template <
class T >
-
void ClearVector(vector< T >& vt)
-
{
-
vector<T> vtTemp;
-
vtTemp.swap(vt);
-
};
swap()是交换函数,使vector离开其自身的作用域,从而强制释放vector所占的内存空间,总而言之,释放vector内存最简单的方法是vector<Point>().swap(pointVec)。当时如果pointVec是一个类的成员,不能把vector<Point>().swap(pointVec)写进类的析构函数中,否则会导致double free or corruption (fasttop)的错误,原因可能是重复释放内存。(前面的pointVec.swap(vector<Point> ())用G++编译没有通过)
如果vector中存放的是指针,那么当vector销毁时,这些指针指向的对象不会被销毁,那么内存就不会被释放。如下面这种情况,vector中的元素时由new操作动态申请出来的对象指针:
-
#include <vector>
-
using
namespace
std;
-
vector<
void *> v;
每次new之后调用v.push_back()该指针,在程序退出或者根据需要,用以下代码进行内存的释放:
-
for (vector<
void *>::iterator it = v.begin(); it != v.end(); it ++)
-
if (NULL != *it)
-
{
-
delete *it;
-
*it = NULL;
-
}
-
v.clear();
OpenCV LUT:
多用LUT可以大大降低处理时间:比如下面是实现图像Gamma矫正的函数,其中使用了LUT,这比使用for循环遍历每个像素值,快很多,特别是图片很大的时候:
-
void gammaCorrection(cv::Mat& dst, float fGamma)
-
{
-
cv::Mat lut(
1,
256, CV_8U);
-
uchar *p = lut.data;
-
for (int i =
0; i <
256; i++)
-
{
-
p[i] = cv::saturate_cast<uchar>(pow((float)(i /
255.0), fGamma) *
255.0f);
-
}
-
cv::LUT(dst, lut, dst);
-
}
opencv多通道的使用
-
typedef cv::Vec<float,
8> Vec8f;
-
E2 = cv::Mat::zeros(srcImage.rows, srcImage.cols, CV_32FC(
8));//背景 E(
0)值
-
//Mat bigMat = cv::Mat::zeros(cv::Size(
256,
256), CV_8UC(
8));
-
//CV_32FC3
-
std::cout <<
"channel = " << E2.channels() <<
" \n ";
-
for (int i =
0; i < E2.rows; i++)
-
{
-
for (int j =
0; j < E2.cols; j++)
-
{
-
for (int c =
0; c < E2.channels(); c++)
-
{
-
std::cout <<
"channel = " << c <<
" ";
-
std::cout << E2.at<Vec8f>(i, j)[c] <<
" ;i = " << i <<
" j= " << j <<
" c= " << c <<
"\n";
-
}
-
}
-
}
opencv中Mat与数组之间值传递的快速方法
利用Mat来存储数据,避免使用数组等操作
-
cv::Mat mean = (cv::Mat_<
float>(
2,
1) <<
0.4404,
0.3111);
-
cout <<
"mean=" << mean <<
endl;
-
float a=mean.at<
float>(
0,
0);
-
float b = mean.at<
float>(
0,
0);
将数组内容传递给Mat,示例代码:
-
unsigned
char cbuf[height][width];
-
cv::
Mat img(height, width, CV_8UC1, (unsigned char*)cbuf);
将Mat中的内容传递给数组,如果Mat中的数据是连续的,那么对于传递到一维vector我们可以这样:
-
std::
vector<uchar>
array(mat.rows*mat.cols);
-
if (mat.isContinuous())
-
array = mat.data;
同样的,传递到一维数组我们可以这样
-
unsigned
char *
array=
new
unsigned
char[mat.rows*mat.cols];
-
if (mat.isContinuous())
-
array = mat.data;
对于二维vector的传值,我们可以这样处理
-
uchar **
array =
new uchar*[mat.rows];
-
for (
int i=
0; i<mat.rows; ++i)
-
array[i] =
new uchar[mat.cols];
-
-
for (
int i=
0; i<mat.rows; ++i)
-
array[i] = mat.ptr<uchar>(i);
图像的遍历的快速方法
OpenCV图像遍历最高效的方法是指针遍历方法。因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元(一般是补够4的倍数或8的倍数,有些地方也称作“位对齐”,目前我用到的FreeImage和c#中的bitmap中的存储机制也是这样的)。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。Mat提供了一个检测图像是否连续的函数isContinuous()。当图像连通时,我们就可以把图像完全展开,看成是一行。因此最高效的遍历方法如下:
-
void imageCopy(const Mat& image,Mat& outImage)
-
{
-
int nr=image.rows;
-
int nc=image.cols;
-
outImage.create(image.size(),image.type());
-
if(image.isContinuous()&&outImage.isContinuous())
-
{
-
nr=
1;
-
nc=nc*image.rows*image.channels();
-
}
-
for(
int i=
0;i<nr;i++)
-
{
-
const uchar* inData=image.ptr<uchar>(i);
-
uchar* outData=outImage.ptr<uchar>(i);
-
for(
int j=
0;j<nc;j++)
-
{
-
*outData++=*inData++;
-
}
-
}
-
}
PS:一般经过裁剪的Mat图像,都不再连续了,如cv::Mat crop_img = src(rect);crop_img 是不连续的Mat图像,如果想转为连续的,最简单的方法,就是将不连续的crop_img 重新clone()一份给新的Mat就是连续的了。关于Mat连续存储的问题,可见:http://blog.csdn.net/guyuealian/article/details/78614662
图像遍历优化和加速的常用方法:
1、加减法比乘除法快,因此应避免使用乘除法
2、不能避免乘除法时,考虑使用“移位运算”
3、像素查表法LUT,远快于每个像素都计算的方法
4、使用常量,会比使用变量快:如:
-
//使用变量相乘
-
int a=
100;
-
int b=
200;
-
int c=a*b;
-
-
//使用常量相乘
-
int c=
100*
200;
PS:特别是在for循环中,能用常量表示的,就不要用变量表示,所以“多使用宏定义define,准没错”
实例代码:第四种的fast_ergodic4遍历方法是最快的
-
#include <opencv2/opencv.hpp>
-
#include <iostream>
-
-
using
namespace
std;
-
#define chans 3
-
-
cv::
Mat fast_ergodic4(cv::Mat image) {
-
if (!image.isContinuous())
-
{
-
return image;
-
}
-
int rows = image.rows;
-
int cols = image.cols;
-
/*使用常量会比变量快,因此当图像通道确定时,请将chans改为数字常量,如1,3,4等*/
-
//int chans = image.channels();
-
cv::Mat outImage = cv::Mat::zeros(image.size(), image.type());
-
cols = cols*rows*chans;
-
uchar* inData = image.data;
-
uchar* outData = outImage.data;
-
for (
int j =
0; j < cols; j += chans)
-
{
-
outData[j] =
10 + inData[j];
//B
-
outData[j +
1] =
10 + inData[j +
1];
//G
-
outData[j +
2] =
10 + inData[j +
2];
//R
-
//outData[j + 3] = 10 + inData[j + 3];//A
-
}
-
return outImage;
-
}
-
-
cv::
Mat fast_ergodic3(cv::Mat image) {
-
int rows = image.rows;
-
int cols = image.cols;
-
/*使用常量会比变量快,因此当图像通道确定时,请将chans改为数字常量,如1,3,4等*/
-
//int chans = image.channels();
-
cv::Mat outImage = cv::Mat::zeros(image.size(), image.type());
-
//outImage.create(image.size(), image.type());
-
if (image.isContinuous())
-
{
-
cols = cols*rows;
-
rows =
1;
-
}
-
//else
-
//{
-
// image = image.clone();
-
// rows = 1;
-
// cols = cols*image.rows*chans;
-
//}
-
cols *= chans;
-
for (
int i =
0; i < rows; i++)
-
{
-
uchar* inData = image.ptr<uchar>(i);
-
uchar* outData = outImage.ptr<uchar>(i);
-
for (
int j =
0; j < cols; j += chans)
-
{
-
outData[j] =
10 + inData[j];
//B
-
outData[j +
1] =
10 + inData[j +
1];
//G
-
outData[j +
2] =
10 + inData[j +
2];
//R
-
//outData[j + 3] = 10 + inData[j + 3];//A
-
}
-
}
-
return outImage;
-
}
-
-
/*避免乘法*/
-
cv::
Mat fast_ergodic2(cv::Mat image) {
-
int rows = image.rows;
-
int cols = image.cols;
-
/*使用常量会比变量快,因此当图像通道确定时,请将chans改为数字常量,如1,3,4等*/
-
//int chans = image.channels();
-
cv::Mat outImage = cv::Mat::zeros(image.size(), image.type());
-
cols *= chans;
-
for (
int i =
0; i < rows; i++)
-
{
-
uchar* inData = image.ptr<uchar>(i);
-
uchar* outData = outImage.ptr<uchar>(i);
-
for (
int j =
0; j < cols; j += chans)
-
{
-
outData[j] =
10 + inData[j];
//B
-
outData[j +
1] =
10 + inData[j +
1];
//G
-
outData[j +
2] =
10 + inData[j +
2];
//R
-
//outData[j + 3] = 10 + inData[j + 3];//A
-
-
}
-
}
-
return outImage;
-
}
-
-
/*一般的指针遍历方法*/
-
cv::
Mat fast_ergodic1(cv::Mat image) {
-
-
int rows = image.rows;
-
int cols = image.cols;
-
/*使用常量会比变量快,因此当图像通道确定时,请将chans改为数字常量,如1,3,4等*/
-
//const int chans = image.channels();
-
cv::Mat outImage = cv::Mat::zeros(image.size(), image.type());
-
for (
int i =
0; i < rows; i++)
-
{
-
uchar* inData = image.ptr<uchar>(i);
-
uchar* outData = outImage.ptr<uchar>(i);
-
for (
int j =
0; j < cols; j++)
-
{
-
outData[j * chans] =
10 + inData[j * chans];
//B
-
outData[j * chans +
1] =
10 + inData[j * chans +
1];
//G
-
outData[j * chans +
2] =
10 + inData[j * chans +
2];
//R
-
//outData[j * chans + 3] = 10 + inData[j * chans + 3];//A
-
}
-
}
-
return outImage;
-
}
-
-
int main() {
-
string path =
"D:\\imageEnhance\\images\\1.jpg";
-
cv::Mat src = cv::imread(path);
-
printf(
"image size = w=%d, h=%d\n", src.cols, src.rows);
-
-
cv::Mat image1 = src.clone();
-
cv::Mat image2 = src.clone();
-
cv::Mat image3 = src.clone();
-
cv::Mat image4 = src.clone();
-
-
double T0 =
static_cast<
double>(cv::getTickCount());
-
cv::Mat outImage1 = fast_ergodic1(image1);
-
double T1 =
static_cast<
double>(cv::getTickCount());
-
cv::Mat outImage2 = fast_ergodic2(image2);
-
double T2 =
static_cast<
double>(cv::getTickCount());
-
cv::Mat outImage3 = fast_ergodic3(image3);
-
double T3 =
static_cast<
double>(cv::getTickCount());
-
cv::Mat outImage4 = fast_ergodic4(image4);
-
double T4=
static_cast<
double>(cv::getTickCount());
-
-
printf(
"fast_ergodic1=%3.3fms\n", (T1 - T0) *
1000 / cv::getTickFrequency());
-
printf(
"fast_ergodic2=%3.3fms\n", (T2 - T1) *
1000 / cv::getTickFrequency());
-
printf(
"fast_ergodic3=%3.3fms\n", (T3 - T2) *
1000 / cv::getTickFrequency());
-
printf(
"fast_ergodic4=%3.3fms\n", (T4 - T3) *
1000 / cv::getTickFrequency());
-
-
//cv::imshow("src", src); cv::waitKey(30);
-
//cv::imshow("outImage1", outImage1);
-
//cv::imshow("outImage2", outImage2);
-
//cv::imshow("outImage3", outImage3);
-
//cv::imshow("outImage4", outImage4);
-
cv::waitKey(
0);
-
}
防止图像Rect区域越界的好方法
OpenCV的cv::Rect提供了很多实用的方法,可参考:http://blog.csdn.net/da_yuan8421/article/details/60959419:
在对图像进行处理时,经常需要截取图像中的某一区域进行处理,如果截取的区域越界时,就容易导致图像崩溃。
-
//求两个矩形的交集和并集
-
rect = rect1 & rect2;
-
rect = rect1 | rect2;
-
//对矩形进行对比,返回布尔变量
-
rect1 == rect2;
-
rect1 != rect2;
利用两个Rect的交集,我们可以很轻松的避免图像裁剪区域越界的情况,如下:
-
Rect rect;
-
rect.x =
-10;
-
rect.y =
-10;
-
rect.height =
100000;
-
rect.width =
20000;
-
rect &= Rect(
0,
0, src.cols, src.rows);
//求交集
-
cv::Mat crop_img = src(rect);
上例子,原图src的大小=200*200,需要裁剪为rect=[-10,-10,10000,20000],为了避免裁剪Rect越界,需要特殊的保护,最简单的方法就是,加入这句话:rect &= Rect(0, 0, src.cols, src.rows),这个交集的Rect肯定是不会越界。
获取OpenCV版本
-
#define CV_VERSION_ID CVAUX_STR(CV_MAJOR_VERSION) CVAUX_STR(CV_MINOR_VERSION) CVAUX_STR(CV_SUBMINOR_VERSION)
-
-
//若你OpenCV的版本是3.2.0,那麽输出为:
-
cout << CV_VERSION_ID <<
endl;
//320
-
cout << CVAUX_STR(CV_MAJOR_VERSION) <<
endl;
//3
-
cout << CVAUX_STR(CV_MINOR_VERSION) <<
endl;
//2
-
cout << CVAUX_STR(CV_SUBMINOR_VERSION) <<
endl;
//0
读写XML或者yml文件数据的
read.xml文本内容:
-
<?xml version="1.0"?>
-
<opencv_storage>
-
<TrainingData type_id="opencv-matrix">
-
<rows>10
</rows>
-
<cols>8
</cols>
-
<dt>f
</dt>
-
<data>
-
10. 10. 10. 10. 10. 10. 10. 10.
-
11. 11. 11. 11. 11. 11. 11. 11.
-
12. 12. 12. 12. 12. 12. 12. 12.
-
13. 13. 13. 13. 13. 13. 13. 13.
-
14. 14. 14. 14. 14. 14. 14. 14.
-
15. 15. 15. 15. 15. 15. 15. 15.
-
16. 16. 16. 16. 16. 16. 16. 16.
-
17. 17. 17. 17. 17. 17. 17. 17.
-
18. 18. 18. 18. 18. 18. 18. 18.
-
19. 19. 19. 19. 19. 19. 19. 19.
-
</data>
</TrainingData>
-
<classes type_id="opencv-matrix">
-
<rows>10
</rows>
-
<cols>1
</cols>
-
<dt>f
</dt>
-
<data>
-
0. 1. 2. 3. 4. 5. 6. 7. 8. 9.
-
</data>
</classes>
-
</opencv_storage>
OpenCV读写方法:
-
//读xml_test.xml文本的数据
-
FileStorage fs_read;
-
Mat TrainningData;
-
Mat Classes;
-
string readPath =
"D:\\SmartAlbum\\image1\\read.xml";
-
bool bR = fs_read.open(readPath, FileStorage::READ);
-
if (bR)
-
{
-
fs_read[
"TrainingData"] >> TrainningData;
-
fs_read[
"classes"] >> Classes;
-
cout << TrainningData <<
endl;
-
cout << Classes <<
endl;
-
}
-
fs_read.release();
-
-
//将数据写到xml_write.xml文本中 (若不存在会自动创建一个空的xml文件)
-
string writePath =
"D:\\SmartAlbum\\image1\\write.xml";
-
cv::FileStorage fs_write;
-
bool bW=fs_write.open(writePath, FileStorage::WRITE);
-
if (bW)
-
{
-
fs_write <<
"TrainingData" << TrainningData;
-
fs_write <<
"classes" << Classes;
-
}
-
fs_write.release();
保存Vector数据的方法
-
#include <vector>
-
#include<algorithm>
-
#include <iostream>
-
#include <string>
-
#include "opencv2/opencv.hpp"
-
-
using
namespace
std;
-
using
namespace cv;
-
-
template<
typename _Tp>
-
void saveVector(FileStorage &fs, vector<_Tp> v,string nodeName) {
-
fs << nodeName <<
"[";
// 开始时,先输入"["
-
for (
size_t i =
0; i < v.size(); i++)
-
{
-
fs << v.at(i);
-
}
-
fs <<
"]";
-
}
-
-
template<
typename _Tp>
-
bool readVector(FileStorage &fs, vector<_Tp> &v, string nodeName) {
-
-
FileNode n = fs[nodeName];
-
if (n.type() != FileNode::SEQ)
-
{
-
cout <<
"err" <<
endl;
-
return
false;
-
}
-
FileNodeIterator it = n.begin(), it_end = n.end();
-
for (; it != it_end; ++it) {
-
v.push_back((_Tp)*it);
-
}
-
return
true;
-
}
-
-
-
int main()
-
{
-
string savePath =
"D:\\SmartAlbum\\image1\\data.xml";
-
vector<
string> imageName;
-
imageName.push_back(
"image1.jpg");
-
imageName.push_back(
"image2.jpg");
-
imageName.push_back(
"image3.jpg");
-
-
vector<
int> level;
-
level.push_back(
1);
-
level.push_back(
2);
-
level.push_back(
3);
-
FileStorage fw;
-
string nodeName1 =
"imageName";
-
string nodeName2 =
"level";
-
//将数据写到xml_write.xml文本中 (若已存在该文件,则会清空当前文件内容再写入)
-
if (fw.open(savePath, FileStorage::WRITE)) {
-
saveVector<
string>(fw, imageName, nodeName1);
-
saveVector<
int>(fw, level, nodeName2);
-
}
-
fw.release();
-
-
//读取文件内容
-
vector<
string> imageName2;
-
vector<
int> level2;
-
string readPath = savePath;
-
FileStorage fr;
-
if (fr.open(readPath, FileStorage::READ)) {
-
readVector<
string>(fr, imageName2, nodeName1);
-
readVector<
int>(fr, level2, nodeName2);
-
}
-
fr.release();
-
-
system(
"pause");
-
return
0;
-
}
若不未知结点名称,可以直接遍历文件的结点,访问元素,如:
-
cv::
FileStorage pfs(fileToRead, cv::FileStorage::READ);
-
cv::FileNode fn = pfs.root();
-
for (cv::FileNodeIterator fit = fn.begin(); fit != fn.end(); ++fit)
-
{
-
cv::FileNode item = *fit;
-
std::
string somekey = item.name();
//可以获得node的名称
-
std::
cout << somekey <<
std::
endl;
-
}
Mat矩阵的运算,易错的问题
注意Mat矩阵可以进行加减乘除的基本运算,但一个int型的常数和一个Scalar类型的常数进行运算是有区别的,以“+”为例子(也可以用cv::add()代替)
-
cv::Mat test = cv::Mat::zeros(cv::Size(
100,
100), CV_8UC3);
-
cv::Mat test1 = test +
128;
//仅第1通道被赋值为128
-
cv::Mat test2 = test + cv::Scalar(
128,
128,
128);
//三个通道都被赋值为128
Mat和IplImage相互转换
Mat 是OpenCV和C++的接口矩阵类,ImlImage是OpenCV和C语言的接口的结构体,但是C++程序有时候时候还是要用到ImlImage,例如在MFC中的Picture Control显示图片。Mat和IplImage相互转换方法:
-
//IplImage—>Mat
-
//EXAMPLE:
-
//浅拷贝:
-
IplImage* pBinary=cvLoadImage(
"c://temp.jpg",
0);
-
Mat Img;
-
Img=cvarrToMat(pBinary);
-
//深拷贝只需要再在Mat里创建一个新的Mat对象,然后进行数据的复制,再用上述的函数进行数据头的复制(浅拷贝):
-
IplImage* pBinary=cvLoadImage(
"c://temp.jpg",
0);
-
Mat ImgTemp;
-
Img=cvarrToMat(pBinary);
-
Mat Img = ImgTemp.clone();
-
-
-
//Mat—>IplImage
-
//EXAMPLE:
-
//浅拷贝:
-
Mat Img=imread(
"1.jpg");
-
IplImage* pBinary = &IplImage(Img);
-
//深拷贝只要再加一次复制数据:
-
IplImage *input = cvCloneImage(pBinary);
https://blog.csdn.net/lijiayu2015/article/details/52438160
Mat::data指针讲解
http://lib.csdn.net/article/opencv/24030
OpenCV Mat数据类型及位数总结
-
char ->CV_8SC
-
unsigned
char,uchar ->CV_8UC
-
unsigned
short
int,ushort->CV_16UC
-
short
int->CV_16SC
-
int ->CV_32SC
-
float ->CV_32FC
-
double->CV_64FC
float: 4字节,6-7位有效数字 -3.4E-38 到 3.4E38
double: 8字节,15~16位有效数字 -1.7E-308 到 1.7E308
在OpenCV里面,许多数据结构为了达到內存使用的最优化,通常都会用它最小上限的空间来分配变量,有的数据结构也会因为图像文件格式的关系而给予适当的变量,因此需要知道它们声明的空间大小来配置适当的变量。一 般标准的图片,为RGB格式它们的大小为8bits格式,范围为0~255,对一个int空间的类型来说实在是太小,整整浪费了24bits的空间,假设有个640*480的BMP文件空间存储內存,那整整浪费了640*480*3*(32-8)bits的內存空间,总共浪费了2.6MB!,也就是那 2.6MB内什么东西都没存储,如果今天以8bits的格式来存储则只使用到0.6MB的內存而已(640*480*3*(8)+54 bits),因此,对于文件格式的对应是一件很重要的事。
在这边除了要考虑bits的空间大小外,还要考虑使用类型的正负号的问题,一般的图像文件是不存在负号的,如果今天即使选则正确的空间大小,可是出现的结果却是负的,那就功亏一篑了。这里除了Float及double类型,char,int,short int都是用二的补数表示法,它们不具正负号bit,而Float,double则是用IEEE 754,在第32bit,64bit上有一个正负号bit.
cvCreateImage()及cvCreateMat()对应
1.Unsigned 8bits(一般的图像文件格式使用的大小)
IplImage数据结构参数:IPL_DEPTH_8U
CvMat数据结构参数:CV_8UC1,CV_8UC2,CV_8UC3,CV_8UC4
变量类型 | 空间大小 | 范围 | 其他 |
---|---|---|---|
uchar | 8bits | 0~255 | (OpenCV缺省变量,同等unsigned char) |
unsigned char | 8bits | 0~255 |
2.Signed 8bits
IplImage数据结构参数:IPL_DEPTH_8S
CvMat数据结构参数:CV_8SC1,CV_8SC2,CV_8SC3,CV_8SC4
变量类型 | 空间大小 | 范围 | 其他 |
---|---|---|---|
char | 8bits | -128~127 |
3.Unsigned 16bits
IplImage数据结构参数:IPL_DEPTH_16U
CvMat数据结构参数:CV_16UC1,CV_16UC2,CV_16UC3,CV_16UC4
变量类型 | 空间大小 | 范围 | 其他 |
---|---|---|---|
ushort | 16bits | 0~65535 | (OpenCV缺省变量,同等unsigned short int) |
unsigned short int | 16bits | 0~65535 | (unsigned short) |
4.Signed 16bits
IplImage数据结构参数:IPL_DEPTH_16S
CvMat数据结构参数:CV_16SC1,CV_16SC2,CV_16SC3,CV_16SC4
变量类型 | 空间大小 | 范围 | 其他 |
---|---|---|---|
short int | 16bits | -32768~32767 | (short) |
5.Signed 32bits
IplImage数据结构参数:IPL_DEPTH_32S
CvMat数据结构参数:CV_32SC1,CV_32SC2,CV_32SC3,CV_32SC4
变量类型 | 空间大小 | 范围 | 其他 |
---|---|---|---|
int | 32bits | -2147483648~2147483647 | (long) |
6.Float 32bits
IplImage数据结构参数:IPL_DEPTH_32F
CvMat数据结构参数:CV_32FC1,CV_32FC2,CV_32FC3,CV_32FC4
变量类型 | 空间大小 | 范围 | 其他 |
---|---|---|---|
float | 32bits | 1.18*10-38~3.40*1038 |
7.Double 64bits
CvMat数据结构参数:CV_64FC1,CV_64FC2,CV_64FC3,CV_64FC4
变量类型 | 空间大小 | 范围 | 其他 |
---|---|---|---|
double | 64bits | 2.23*10-308~1.79*10308 |
8.Unsigned 1bit
IplImage数据结构参数:IPL_DEPTH_1U
变量类型 | 空间大小 | 范围 | 其他 |
---|---|---|---|
bool | 1bit | 0~1 |
其他变量对应
1.Signed 64bits
int64
long long
2.Unsigned 64 bits
uint64
unsigned long long
Mat矩阵插入新的矩阵
-
enum insertType
-
{
-
Top =
0,
-
Bottom =
1,
-
Left =
2,
-
Right =
3
-
};
-
-
/*
-
* cv::Mat src:原始数据矩阵
-
* cv::Mat inMat:被插入的数据矩阵
-
* insertType fea_type:插入类型:
-
Top = 0 //在src矩阵顶部插入矩阵inMat
-
Bottom = 1 //在src矩阵低部插入矩阵inMat
-
Left = 2 //在src矩阵左部插入矩阵inMat
-
Right = 3 //在src矩阵右部插入矩阵inMat
-
*/
-
cv::
Mat insertMat(cv::Mat src, cv::Mat inMat,insertType type) {
-
cv::Mat dest= src.clone();
-
int inNum =
0;
-
if (type== Top)
-
{
-
inNum = inMat.rows;
-
cv::copyMakeBorder(dest, dest, inNum,
0,
0,
0, cv::BORDER_CONSTANT, cv::Scalar(
0,
0,
0,
0));
-
cv::
Rect r(0, 0, dest.cols, inNum);
-
inMat.copyTo(dest(r));
-
}
else
if(type == Bottom)
-
{
-
inNum = inMat.rows;
-
cv::copyMakeBorder(dest, dest,
0, inNum,
0,
0, cv::BORDER_CONSTANT, cv::Scalar(
0,
0,
0,
0));
-
cv::
Rect r(0, dest.rows - inNum, dest.cols, inNum);
-
inMat.copyTo(dest(r));
-
}
else
if (type == Left)
-
{
-
inNum = inMat.cols;
-
cv::copyMakeBorder(dest, dest,
0,
0, inNum,
0, cv::BORDER_CONSTANT, cv::Scalar(
0,
0,
0,
0));
-
cv::
Rect r(0, 0, inNum, dest.rows);
-
inMat.copyTo(dest(r));
-
}
else
if (type == Right)
-
{
-
inNum = inMat.cols;
-
cv::copyMakeBorder(dest, dest,
0,
0,
0, inNum, cv::BORDER_CONSTANT, cv::Scalar(
0,
0,
0,
0));
-
cv::
Rect r(dest.cols- inNum, 0, inNum, dest.rows);
-
inMat.copyTo(dest(r));
-
}
-
return dest;
-
}
颜色空间缩减的方法
-
cv::
Mat CreatTable(int level) {
-
Mat lookUpTable(1, 256, CV_8UC1);
-
uchar *p = lookUpTable.data;
-
int div =
256 / level;
-
for (
size_t i =
0; i <
256; i++)
-
{
-
p[i] = (i / div)*div;
-
}
-
return lookUpTable;
-
}
-
static cv::Mat lookUpTable = CreatTable(
64);
-
-
void main() {
-
string p1=
"D:\\SmartAlbum\\image1\\B\\B15.jpg";
-
cv::Mat image1 = cv::imread(p1);
-
cv::Mat dest;
-
cv::LUT(image1, lookUpTable2, dest);
-
cv::waitKey(
0);
-
}
YUV444,YUV422,YUV420解释
https://blog.csdn.net/mandagod/article/details/78605586
通常我们用RGB表示一种彩色。计算机系统里的LCD显示的数据就是RGB来表示每个像素的颜色。
而在我们生活里,有黑白电视机与彩色电视机两种,拍摄节目源时不可以用两种不同的摄像机来存放两种图像数据。
所以为了兼容两种电视机,专家就引入YUV格式代替RGB,其中Y表示亮度, U和V表示色差。 黑白电视机只用Y信号, 而彩色电视机可由YUV转换成RGB再显示颜色。
通常我们所用的YUV格式是 ITU-R 的标准 , 也叫YCbCr。YUV是由RGB格式的数据转换得来。
-
Y Y =
0.299 x R +
0.587 x G +
0.114 x B +
0
-
U Cb =
-0.169 x R -
0.331 x G +
0.499 x B +
128
-
V Cr =
0.499 x R -
0.418 x G -
0.0813 x B +
128
-
-
Y Y =
0.299 x R +
0.587 x G +
0.114 x B +
0
-
U Cb =
-0.169 x R -
0.331 x G +
0.499 x B +
128
-
V Cr =
0.499 x R -
0.418 x G -
0.0813 x B +
128
YUV4:4:4
其实就是YUV的数据各占用8位, 每个像素都由YUV组成
同一行的相邻4个像素数据: Y0U0V0 Y1U1V1 Y2U2V2 Y3U3V3
存储时: Y0 U0 V0 Y1 U1 V1 Y2 U2 V2 Y3 U3 V3 //即每个像素YUV的数据都会存放起来
为什么叫4:4:4 , 意思就是4个像素里的数据有4个Y, 4个U, 4个V
YUV4:2:2
其实绝大部分相邻的两个像素,数据差异应不大。所以为了节点空间便于存储,丢失每个像素的部分数据。
专家研究表明我们人对亮度比较敏感,而对色彩不怎么敏感。所以每个像素的亮度Y数据是绝对不动的,而色差数据可以进行丢弃。
同一行的相邻4个像素数据: Y0U0V0 Y1U1V1 Y2U2V2 Y3U3V3
存储时: Y0 U0 Y1 V1 Y2 U2 Y3 V3 // 每两个相邻的像素, 一个丢弃V数据,一个丢弃U数据
为什么叫4:2:2, 意思就是相邻的4个像素里有4个Y, 2个U, 2个V。 按上面存储的顺序也叫YUYV.
但还原成RGB数据必须需要YUV, 像第一个像素只有Y0U0是没法还原的,这时只能用下一像素的V1数据。
还原时的YUV: [Y0U0V1] [Y1U0V1] [Y2U2V3] [Y3U2V3] //这样还原理论上会对图像的质量有影响的,但我们看不出来的.
YUV4:2:0
专家们进一步研究表示,每一行的相邻两个像素与下一行同位置的两个像素数据差异不大,可以进一步的丢数据。
如两行的像素数据:
Y00U00V00 Y01U01V01 Y02U02V02 Y03U03V03 ....
Y88U88V88 Y89U89V89 Y90U90V90 Y91U91V91 ....
存储时: Y00U00 Y01 Y02U02 Y03 //每个像素的Y数据保留, 两个像素数据只保留一个U数据。这一行不保留V数据(YUV: 420)
Y88V88 Y89 Y90V90 Y91 // .... 两个像素数据只保留一个V数据, 这行不保留U数据(YUV: 402)
还原时只能相同位置的上下两行4个像素结合还原:
Y00U00V88 Y01U00V88 Y02U02V90 Y03U02V90
Y88U00V88 Y89U00V88 Y90U02V90 Y91U02V90
yuv数据还分成打包的,平面的。
打包的意思是: yuv数据是顺序存放Y,接着U,再接着V数据存放。
平面的意思是: yuv数据是分成三个地方存放, 一个地方只存Y数据, 一个只存U数据, 一个只存V数据
你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
新的改变
我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:
- 全新的界面设计 ,将会带来全新的写作体验;
- 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
- 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
- 全新的 KaTeX数学公式 语法;
- 增加了支持甘特图的mermaid语法1 功能;
- 增加了 多屏幕编辑 Markdown文章功能;
- 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
- 增加了 检查列表 功能。
功能快捷键
撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
合理的创建标题,有助于目录的生成
直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC
语法后生成一个完美的目录。
如何改变文本的样式
强调文本 强调文本
加粗文本 加粗文本
标记文本
删除文本
引用文本
H2O is是液体。
210 运算结果是 1024.
插入链接与图片
链接: link.
图片:
带尺寸的图片:
居中的图片:
居中并且带尺寸的图片:
当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。
如何插入一段漂亮的代码片
去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片
.
// An highlighted block
var foo = 'bar';
生成一个适合你的列表
- 项目
- 项目
- 项目
- 项目
- 项目1
- 项目2
- 项目3
- 计划任务
- 完成任务
创建一个表格
一个简单的表格是这么创建的:
项目 | Value |
---|---|
电脑 | $1600 |
手机 | $12 |
导管 | $1 |
设定内容居中、居左、居右
使用:---------:
居中
使用:----------
居左
使用----------:
居右
第一列 | 第二列 | 第三列 |
---|---|---|
第一列文本居中 | 第二列文本居右 | 第三列文本居左 |
SmartyPants
SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:
TYPE | ASCII | HTML |
---|---|---|
Single backticks | 'Isn't this fun?' | ‘Isn’t this fun?’ |
Quotes | "Isn't this fun?" | “Isn’t this fun?” |
Dashes | -- is en-dash, --- is em-dash | – is en-dash, — is em-dash |
创建一个自定义列表
-
Markdown
- Text-to- HTML conversion tool Authors
- John
- Luke
如何创建一个注脚
一个具有注脚的文本。2
注释也是必不可少的
Markdown将文本转换为 HTML。
KaTeX数学公式
您可以使用渲染LaTeX数学表达式 KaTeX:
Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n−1)!∀n∈N 是通过欧拉积分
Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=∫0∞tz−1e−tdt.
你可以找到更多关于的信息 LaTeX 数学表达式here.
新的甘特图功能,丰富你的文章
- 关于 甘特图 语法,参考 这儿,
UML 图表
可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图::
这将产生一个流程图。:
- 关于 Mermaid 语法,参考 这儿,
FLowchart流程图
我们依旧会支持flowchart的流程图:
- 关于 Flowchart流程图 语法,参考 这儿.
导出与导入
导出
如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。
导入
如果你想加载一篇你写过的.md文件或者.html文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。
注脚的解释 ↩︎