前言
这是我《OpenCV:从零到一》专栏的第二篇博客,想看跟多请戳这。
本文概要
CV_Assert()
saturate_cast()
filter2D()
Mat对象的rows,cols,channels,depth(),copyTo(),ptr()
getTickCount() getTickFrequency()
图像的遍历
掩膜(mask也称为kennel),在数字图像处理中对应的就是模板运算,是处理图像常用的运算。说白了就是将某个像素的附近的像素的灰度值进行加权运算得到的值再赋给这个像素。实现方法有两种:1.两层循环遍历 2.调用函数
案例代码
大概内容:两种矩阵的掩膜操作。计时操作
#include<opencv2/opencv.hpp>//包含了大多数头文件
#include<opencv2/highgui/highgui_c.h>
#include<iostream>
using namespace cv;
//main的参数可填可不填,主要用于命令行执行函数的时候输入
int main(int argc, char** argv) {
String pic_url = "D:\\86186\\Documents\\opencv\\lena.jpg";
Mat myImage = imread(pic_url);
CV_Assert(myImage.depth() == CV_8U);//宏 判断语句是否正确,错误抛出异常,判断图片是否为8bit无符号类型
namedWindow("LENA", CV_WINDOW_AUTOSIZE);//CV_WINDOW_AUTOSIZE要#include<opencv2/highgui/highgui_c.h>
imshow("LENA", myImage);
//方法一:
Mat resultImage;
myImage.copyTo(resultImage);//函数本身作用是复制,这里主要是为了给resultImage分配空间
int nchannels = myImage.channels();//Returns the number of matrix channels.返回矩阵通道数
int height = myImage.rows;//Mat的属性,像素矩阵的行数
int cols = myImage.cols;//Mat的属性,像素矩阵的列数
int width = myImage.cols * nchannels;//可以认为一个像素是由一个一维数组,数组长度为通道数,即实际矩阵长度要乘上矩阵
double t1 = (double)getTickCount();//获取开始时间信息
for (int row = 1;row < height - 1;row++) {
//myImage.ptr<uchar>(int)是一个函数模板,看到<>应该想到模板,看到()应该想到函数.
//下面每次循环都会声明这几个变量,在下一次循环之前会被销毁
const uchar* previous = myImage.ptr<uchar>(row - 1);//返回值是一个一级指针,或者说是一个一维数组
const uchar* current = myImage.ptr<uchar>(row);
const uchar* next = myImage.ptr<uchar>(row + 1);
uchar* output = resultImage.ptr<uchar>(row);//生成的图像
for (int col = nchannels;col < nchannels*(myImage.cols - 1);col++) {//前面int col=nchannels改为int col=nchannels*1会更好理解,即刚才提到的:遍历的时候掐头去尾
//卷积(saturate_cast来约束灰度范围)
*output = saturate_cast<uchar>(5 * current[col] - previous[col] - next[col] - current[col - nchannels] - current[col + nchannels]);
output++;
}
}
namedWindow("result", CV_WINDOW_AUTOSIZE);
imshow("result", resultImage);
//方法二
Mat result2;
Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);//模板矩阵或者称为算子,核
//测量卷积消耗的时间
double t2 = (double)getTickCount();//用于返回从操作系统启动到当前所经的计时周期数
filter2D(myImage, result2, myImage.depth(), kernel);//调用线性滤波器,参数为(原图,结果图,原图深度,核)
t2 = ((double)getTickCount() - t2) / getTickFrequency();//getTickFrequency()用于返回CPU的频率,周期差/频率=时间s
std::cout << "built-in filter2D time passed in seconds: " << t2 << std::endl;//注意命名域的使用
namedWindow("result2", CV_WINDOW_AUTOSIZE);//若不写这句imshow依旧可以生效,会自动创建,但是还是要养成良好的习惯
imshow("result2", result2);//若这里的窗口名换成result,之前的会被覆盖
waitKey(0);
return 0;
}
解析及注意事项
- int main()可以加参数,主要用于命令行传参
- CV_Assert()是宏,用来判断语句是否出错,出错则抛出错误
- Mat.depth()用来获取图像深度,常见的深度有CV_8U和CV_8S 其中CV_是固定前缀,数字代表比特数,还有16、32等,而U则代表unsigned,S代表signed,F代表floating-point
- Mat.cols不代表实际矩阵的列数,每一个像素还包括若干通道,每个通道横向排列,因此实际行数要乘上通道数。如图所示(仅代表笔者的理解),一个像素就是一个黑色的框,而图中是一个三通道的图,图中情况是首次循环的的操作位置(下面和右边未画出)
- saturate 使饱和,浸透,saturate_cast用来约束范围,当saturate_cast的函数参数值超出模板参数对于的范围的话,结果会被剪裁(取最值)
- getTickCount和getFrequency()是一对常用来测运算时间的函数,第一个函数返回周期个数(次),第二个返回频率(次/秒),相除可以得到秒,可以精确到毫秒级别。
- 命名空间出现了交叉,因此最好尽早适应多个命名空间的情况
- filter2D是一个线性滤波器,函数可以订制成对任意图片的任意线性滤波器,函数不是真正的做卷积,而是通过公式运算(我认为速度理论上比法一快但是测量结果男默女泪,差了4倍有多)。参数为(原图,结果图,原图深度,核,[锚点,delta,borderType])。其中前四个比较常用。锚点就是要变化的点,默认是核的中心,delta为在每次卷积结束加一个值,默认为0,borderType是图像外的填充方式。
- 有一个地方值得注意的是法一的第二行一个复制操作,醉翁之意不在酒,这是为了为resultImage赋值,初始化,防止后面出现野指针的情况,如果注释掉了就会报错,如图。
在给指针赋值之前一定要初始化,否则就会出现野指针情况,我在这篇文章后面提到过。
全注释代码
#include<opencv2/opencv.hpp>//包含了大多数头文件
#include<opencv2/highgui/highgui_c.h>
#include<iostream>
using namespace cv;
//main的参数可填可不填,主要用于命令行执行函数的时候输入
int main(int argc, char** argv) {
String pic_url = "D:\\86186\\Documents\\opencv\\lena.jpg";
Mat myImage = imread(pic_url);
CV_Assert(myImage.depth() == CV_8U);//宏 判断语句是否正确,错误抛出异常
/*
Checks a condition at runtime and throws exception if it fails.运行时判断语句是否正确,错误抛出异常
The macro CV_Assert checks the condition in both Debug and Release configurations while CV_DbgAssert is only retained in the Debug configuration.
宏CV_Assert检查调试和发布配置中的条件,而CV_DbgAssert只保留在调试配置中。
*/
/*
Mat.empty()
The method returns the identifier of the matrix element depth (the type of each individual channel).
有以下几种形式
CV_8U - 8-bit unsigned integers ( 0..255 )
CV_8S - 8-bit signed integers ( -128..127 )
CV_16U - 16-bit unsigned integers ( 0..65535 )
CV_16S - 16-bit signed integers ( -32768..32767 )
CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )
*/
namedWindow("LENA", CV_WINDOW_AUTOSIZE);//CV_WINDOW_AUTOSIZE要#include<opencv2/highgui/highgui_c.h>
imshow("LENA", myImage);
//方法一:
Mat resultImage;
myImage.copyTo(resultImage);//函数本身作用是复制,这里主要是为了给resultImage分配空间
int nchannels = myImage.channels();//Returns the number of matrix channels.返回矩阵通道数
int height = myImage.rows;//Mat的属性,像素矩阵的行数
int cols = myImage.cols;//Mat的属性,像素矩阵的列数
int width = myImage.cols * nchannels;//可以认为一个像素是由一个一维数组,数组长度为通道数,即实际矩阵长度要乘上矩阵
/*
模糊处理通常有一个模板,像素颜色是通过八邻位经过一系列运算得到的
由于图片边缘的像素没有八邻位(角落只有三个\边边只有五个)
所以在循环遍历的时候从第二行开始到倒数第二行结束
对应的角标就是1和height-1
*/
double t1 = (double)getTickCount();//获取开始时间信息
for (int row = 1;row < height - 1;row++) {
/*
char是有符号的,unsigned char是无符号的,里面全是正数。两者都作为字符用的话是没有区别的,但当整数用时有区别:
char 整数范围为-128到127( 0x80__0x7F),而unsigned char 整数范围为0到255( 0__0xFF )
有时候想把整数数值限在255范围内,也用unsigned char,这个类型在嵌入式用的多
为了书写方便所以大家都做了以下定义typedef unsigned char Uchar; typedef unsigned char uchar;
*/
//myImage.ptr<uchar>(int)是一个函数模板,看到<>应该想到模板,看到()应该想到函数.他有多达20个重载,这里暂时不展开说
//Returns a pointer to the specified matrix row.总的来说这就像一个指针,可以利用他来访问矩阵上任意一个地方
//下面每次循环都会声明这几个变量,在下一次循环之前会被销毁
const uchar* previous = myImage.ptr<uchar>(row - 1);//返回值是一个一级指针,或者说是一个一维数组
const uchar* current = myImage.ptr<uchar>(row);
const uchar* next = myImage.ptr<uchar>(row + 1);//原来的图像
uchar* output = resultImage.ptr<uchar>(row);//生成的图像
for (int col = nchannels;col < nchannels*(myImage.cols - 1);col++) {//前面int col=nchannels改为int col=nchannels*1会更好理解,即刚才提到的:遍历的时候掐头去尾
*output = saturate_cast<uchar>(5 * current[col] - previous[col] - next[col] - current[col - nchannels] - current[col + nchannels]);
/*
saturate_cast用来限定结果范围
saturate in the name means that when the input value v is out of the range of the target type,
当saturate_cast的函数参数值超出模板参数对于的范围的话,结果会被剪裁
the result is not formed just by taking low bits of the input, but instead the value is clipped.
For example: uchar a = saturate_cast<uchar>(-100); // a = 0 (UCHAR_MIN)
short b = saturate_cast<short>(33333.33333); // b = 32767 (SHORT_MAX)
*/
output++;
}
}
t1 = ((double)getTickCount() - t1) / getTickFrequency();//获取中间用时,记住要除以一个frequency
std::cout << "first time passed in seconds: " << t1 << std::endl;//注意命名域的使用
namedWindow("result", CV_WINDOW_AUTOSIZE);
imshow("result", resultImage);
//waitKey(0);可以不写
//方法二
Mat result2;
Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);//模板矩阵或者称为算子,核
double t2 = (double)getTickCount();//用于返回从操作系统启动到当前所经的计时周期数
filter2D(myImage, result2, myImage.depth(), kernel);//调用线性滤波器,参数为(原图,结果图,原图深度,核,[锚点,delta,borderType])
//锚点为每一次运算被赋值的点,默认为核的中心点 delta为每次运算和加的值,默认0 borderType为边缘无像素填充方式
/*
Convolves an image with the kernel.用kernel算子(核)对图片进行卷积运算
The function applies an arbitrary linear filter to an image.函数可以订制成对任意图片的任意线性滤波器
The function does actually compute correlation, not the convolution.函数不是真正的做卷积,而是通过公式运算(因此速度理论上比法一快??)
The function uses the DFT-based algorithm in case of sufficiently large kernels (~11 x 11 or larger) and the direct algorithm for small kernels.
对于不同大小的kernel会用不同的算法(适应各种需求的优化)(DFT->离散傅里叶变换)
*/
t2 = ((double)getTickCount() - t2) / getTickFrequency();//getTickFrequency()用于返回CPU的频率,周期差/频率=时间s
std::cout << "built-in filter2D time passed in seconds: " << t2 << std::endl;//注意命名域的使用
/*
在开头也可以加上这几句,推荐法一和法二
法二:
using std::cout;
using std::endl;
法三:
using namespace std;
*/
namedWindow("result2", CV_WINDOW_AUTOSIZE);//若不写这句imshow依旧可以生效,会自动创建,但是还是要养成良好的习惯
imshow("result2", result2);//若这里的窗口名换成result,之前的会被覆盖
waitKey(0);
return 0;
}
翻译笔记
correlation n. [数] 相关,关联;相互关系
arbitrary adj. [数] 任意的;武断的;专制的
convolution n. [数] 卷积;回旋;盘旋;卷绕
Debug and Release configurations 调试和发布配置