前言
这是我《OpenCV:从零到一》专栏的第四篇博客,想看跟多请戳这。
本文概要
Sobel算子
minMaxLoc函数
Mat对象的成员函数 at
vec类及其常用的类
加强上一篇提到的API的使用
案例代码
大概内容:sobel边缘检测,阻塞图片的蓝色和绿色通道。
#include <opencv2/core/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace cv;
using std::endl;
using std::cout;
int main(int argc, char** args) {
//读取图像并灰度化
Mat src = imread("D:\\86186\\Documents\\opencv\\lena.jpg", IMREAD_COLOR);
if (src.empty()) {
cout << "could not find the image resource..." << endl;
return -1;
}
Mat grayImg;
cvtColor(src, grayImg, COLOR_BGR2GRAY);
namedWindow("grayImg");
imshow("grayImg", grayImg);
//使用sobel滤波器,x方向一阶导数
Mat sobelx;
Sobel(grayImg, sobelx, CV_32F, 1, 0);
imshow("sobelx", sobelx);
double minVal, maxVal;
minMaxLoc(sobelx, &minVal, &maxVal); //找最大和最小的强度(返回指针)
Mat draw;
sobelx.convertTo(draw, CV_8U, 255.0 / (maxVal - minVal), -minVal * 255.0 / (maxVal - minVal));
imshow("draw", draw);
//通过遍历数组可以达到把某个通道置0,从而达到只让某个通道显示,或者阻止某个通道显示
Mat image = src.clone();
int height = image.rows;
int width = image.cols;
int channels = image.channels();
printf("height=%d width=%d channels=%d", height, width, channels);
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
if (channels == 3) {//检测图片的通道数,防止出现野指针(访问越界),提升程序的鲁棒性
image.at<Vec3b>(row, col)[0] = 0; // blue
image.at<Vec3b>(row, col)[1] = 0; // green
//image.at<Vec3b>(row, col)[2] = 0; // red
}//只让红色通道显示
}
}
imshow("image", image);
waitKey(0);
return 0;
}
运行效果:
解析及注意事项
- 当前版本(opencv4)的namedWindow的参数CV_WINDOW_AUTOSIZE需要加#include<opencv2/highgui/highgui_c.h>,因为namedWindow第二个参数本身就是默认CV_WINDOW_AUTOSIZE的,所以这样就显得有些麻烦了,干脆不写第二个参数,或者更懒一点的话直接连namedWindow都不写了。
- cvtColor用于转换色彩空间,convertTo用于将矩阵的值转化,通常用于归一化(同一色彩空间下的值的转换),同时后者是Mat的成员函数
- (多行注释快捷键ctrl+k+c 取消ctrl+k+u)
- Sobel是一个离散性差分算子,用来运算图像亮度函数的梯度之近似值,主要用于获得数字图像的一阶梯度。索贝尔算子不但产生较好的检测效果,而且对噪声具有平滑抑制作用,但是得到的边缘较粗,且可能出现伪边缘。(离散的差分对应的是连续的微分)
- Sobel算子是一个线性滤波器(算子 包括 滤波器 包括 卷积核),即通常要进行卷积操作。所以别看它的参数那么多,大多数掩膜用的算子的前两个参数是固定的分别是
src(原图像)
dst(目标图像/结果图像)
而后面四个也是固定的,并且通常是有默认值的他们分别是
ksize = 3(卷积核的大小)、
scale = 1(缩放比例)、
delta = 0(整体增加的值)(有时候叫shift)、
borderType = BORDER_DEFAULT(边界处理类型)
连计算公式都是一样的dst(i)=src(i) x scale + shift scale和delta在其他处理图像的值的函数中也很常见,所以这样看下来那些看似很长的参数列表其实也不过是纸老虎罢了。 - minMaxLoc用于寻找一个矩阵的最大最小值,并且返回它的指针,需要用参数去接收,需要地址的话也可以增加参数去接受,然而这个函数只能够接受单通道的图片,多通道的图片需要先转换。
- at和上一篇讲到的ptr都是用于访问图片像素的方法之一,都是成员函数,相比之下,ptr在debug模式下比at快,但是在release模式下差异不明显,是因为at在release模式下不进行索引范围检查以此来提高效率。除此之外这两个还有一个不同的就是at返回引用而ptr返回的是指针。除此之外,还可以使用迭代器(Mat_::iterator配合Mat.begin()和Mat.end())来访问图片,熟悉stl的读者一定不陌生,这里暂时不展开写。
- vec是一个向量类,通常配合at来访问通道(把一个像素当成一个向量),需要传两个参数,一个是元素类型另一个是元素数量,值得注意的是他还有一些填好长度的宏,比较常用的有如下几个
typedef Vec<uchar,3> Vec3b;
typedef Vec<int,2> Vec2i;
typedef Vec<float,4> Vec4f;
typedef Vec<double,3> Vec3d;
全注释代码
#include <opencv2/core/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
//#include<opencv2/highgui/highgui_c.h>//CV_WINDOW_AUTOSIZE
#include <iostream>
using namespace cv;
using std::endl;
using std::cout;
int main(int argc, char** args) {
Mat src = imread("D:\\86186\\Documents\\opencv\\lena.jpg", IMREAD_COLOR);
if (src.empty()) {
cout << "could not find the image resource..." << endl;
return -1;
}
Mat grayImg;
cvtColor(src, grayImg, COLOR_BGR2GRAY);
//将彩色图像转换为灰度图像,2(two)和to同音,完整是 BGR to GRAY 在opencv里通道顺序是BRG
namedWindow("grayImg");//namedWindow("grayImg", CV_WINDOW_AUTOSIZE);//要加#include<opencv2/highgui/highgui_c.h>
//namedWindow第二个参数默认的就是CV_WINDOW_AUTOSIZE,不写也可以,因此也不用加#include<opencv2/highgui/highgui_c.h>
imshow("grayImg", grayImg);
Mat sobelx;
Sobel(grayImg, sobelx, CV_32F, 1, 0);//使用sobel滤波器,x方向一阶导数
/*
参数
InputArray src,
OutputArray dst,
int ddepth,//output image depth 输出图像的深度
int dx, //order of the derivative x. x的导数的阶数
int dy, //order of the derivative y. y的导数的阶数
int ksize = 3,//size of the extended Sobel kernel; it must be 1, 3, 5, or 7.
double scale = 1,//optional scale factor for the computed derivative values
double delta = 0,//optional delta value that is added to the results prior to storing them in dst.
int borderType = BORDER_DEFAULT //这个老生常谈了,绝大多数情况下都不用理(卷积的时候就会有这个参数)
*/
imshow("sobelx", sobelx);//甚至为了省事连namedWindow都可以不写,自动生成
//多行注释快捷键ctrl+k+c 取消ctrl+k+u
double minVal, maxVal;
minMaxLoc(sobelx, &minVal, &maxVal); //找最大和最小的强度(返回指针)
/*
The extremums are searched across the whole array or, if mask is not an empty array, in the specified array region.
The function do not work with multi-channel arrays. 只能处理单通道的图像
参数
InputArray src, //input single-channel array.
double * minVal, //pointer to the returned minimum value; NULL is used if not required.
double * maxVal = 0, //pointer to the returned maximum value; NULL is used if not required.
Point * minLoc = 0, //pointer to the returned minimum location (in 2D case); NULL is used if not required.
Point * maxLoc = 0, //pointer to the returned maximum location (in 2D case); NULL is used if not required.
InputArray mask = noArray() //optional mask used to select a sub-array.(寻找src数组中的局部的极值)
*/
Mat draw;
sobelx.convertTo(draw, CV_8U, 255.0 / (maxVal - minVal), -minVal * 255.0 / (maxVal - minVal));
//convertTo(dst,rtype,scale,shift) dst(i)=src(i)*scale+shift eg: 255归一化则scale=1/255.0
//这里是将原本不是255的图扩大到255并减去最小值(变白)
imshow("draw", draw);
//通过遍历数组可以达到把某个通道置0,从而达到只让某个通道显示,或者阻止某个通道显示
Mat image = src.clone();//这个克隆是连矩阵一起复制的
int height = image.rows;
int width = image.cols;
int channels = image.channels();
printf("height=%d width=%d channels=%d", height, width, channels);
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
if (channels == 3) {//检测图片的通道数,防止出现野指针(访问越界),提升程序的鲁棒性
image.at<Vec3b>(row, col)[0] = 0; // blue
image.at<Vec3b>(row, col)[1] = 0; // green
//image.at<Vec3b>(row, col)[2] = 0; // red
}//只让红色通道显示
/*
The template methods return a reference to the specified array element.
at是一个 返回特定数组元素的引用 的模版(ptr是返回地址/指针的模版)有12个重载
For the sake of higher performance, the index range checks are only performed in the Debug configuration.
为了获得更高的性能,仅在调试配置中执行索引范围检查。
If matrix is of type CV_8U then use Mat.at<uchar>(y,x).
If matrix is of type CV_8S then use Mat.at<schar>(y,x).
If matrix is of type CV_16U then use Mat.at<ushort>(y,x).
If matrix is of type CV_16S then use Mat.at<short>(y,x).
If matrix is of type CV_32S then use Mat.at<int>(y,x).
If matrix is of type CV_32F then use Mat.at<float>(y,x).
If matrix is of type CV_64F then use Mat.at<double>(y,x).
参数一般为行和列,也可以传一个指针
*/
/*
vec是一个向量类,通常配合at来访问通道(把一个像素当成一个向量)
template<typename _Tp, int cn>
_Tp element type 类型
cn the number of elements 元素数量(长度)
还有几个宏,一目了然,望文生义
typedef Vec<uchar,3> Vec3b;
typedef Vec<int,2> Vec2i;
typedef Vec<float,4> Vec4f;
typedef Vec<double,3> Vec3d;
*/
}
}
imshow("image", image);
waitKey(0);
return 0;
}
翻译笔记
derivative n. 派生物;导数
For the sake of higher performance, the index range checks are only performed in the Debug configuration.
为了获得更高的性能,仅在debug模式中执行索引范围检查。
sake n. 目的;利益;理由
extremums n. [数] 极值,极端值
optional delta value that is added to the results prior to storing them in dst
可选的增量值,在将结果存储到dst之前添加到结果中
prior to 在……之前