【OpenCV:从零到一】04:图像操作

前言
这是我《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 在……之前

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