目录
2、四大天王之Point_、Size_、Rect_、Scalar_
一、前言
上几篇文章,我们讲完了OpenCV中的常见基础结构。
从这篇博客开始,我们要踏上新的征程,我们要从像素的角度,来一起了解图像,一起了解OpenCV。今天这篇博客,我们讲最基本的图像像素操作,让我们一起,复习一下之前学的基本数据类型,然后一起走进今天的内容吧!
二、温故知新——基本数据类型
我想大家应该还记得我们之前学的数据类型,主要有如下:
1、最基本之Mat类
Mat类是我们学到的最基本的数据类型,从这节课开始往后的每节课,都会涉及到Mat类。Mat常用于声明或定义一个图像,偶尔也用于声明一个二维矩阵。
具体内容大家请看:
2、四大天王之Point_、Size_、Rect_、Scalar_
四大天王,这个名字也是很形象了,因为这个是除了Mat类之外最常用的四个类型,经常需要辅助Mat类做一些操作。他们分别是:
表示 点 的 Point_;
表示 尺寸 的 Size_;
表示 矩形 的 Rect_;
表示 颜色 的 Scalar_;
具体内容大家请看:
3、各大分堂主
其他数据类型虽然不如上面四个常用,但是他们在自己的领域不可替代,会协助Mat类和上面四个常用类型做更多的事,实现更加丰富的功能:
表示 复数 的 Complex;
表示 三维点 的 Point_;
表示 旋转矩形 的 RotatedRect;
表示 序列 的 Range;
表示 角点 的 KeyPoint;
表示 索引与描述符距离 的 DMatch;
表示 迭代算法终止条件 的 TermCriteria;
具体内容大家请看:
三、像素及相关概念
1、像素
首先我们先来了解一下像素
像素是计算机屏幕上所能显示的最小单位。用来表示图像的单位。每一个单位都会有其色彩数值。
2、灰度图与彩图
我们经常能够见到的是黑白图像和彩色图像,其中黑白图像只有一个通道,彩色图像有三个通道,这些通道中每个像素点上的取值范围都是0-255;
对于黑白图像,0表示纯黑色,255表示纯白色,介于0-255之间的图像是灰色,根据数值的不同,灰色的程度也不同。我们也把这样的图像称之为灰度图像。
一个10×10的灰度图像排布如下:
对于彩色图像,有三个通道,每个通道各表示一种颜色,分别是BGR,也就是蓝绿红。以红色通道为例,如果取值为0,表示黑色,如果取值为255表示红色。我们之前学习Scalar_也学到了光学三原色,我们可以通过三种颜色构造出不同的颜色来。当三个通道的数值都为255时,图像为纯白色。
一个10×10的彩色图像排布如下:
四、像素基本操作
1、获取像素指针
1.理论讲解
操作像素之前,肯定要做的就是获取指向像素的指针,然后才能对像素进行操作。所以第一个就是要获取像素的指针。
但是我们看上面的图, 我么发现,对于灰度图和彩图,他们是不一样的,而且对于彩图来说,它是怎么存放的呢?
在计算机中,Mat是以序列的形式存放,对于灰度图,是这样的:
对于彩色图,我们要先知道,我们是一个像素一个像素排列,在像素内,是先B再G最后R。所以一幅彩色图像的表示如下:
然后再按照上面的灰度图那样进行排列。
所以根据上面的,我们可以知道:
对于灰度图:
一幅尺寸为 m×n (m行n列)的灰度图像,存储为长为 m×n 的序列;
想要访问第 i 行,第 j 列的像素,就是访问序列中的第 (i-1)×n+j 个像素,因为第1个像素序号是0,那这个像素的序号就是 (i-1)×n+j-1 。
对于彩色图:
一幅尺寸为 m×n (m行n列)的彩色图像,存储为长为 m×n×3 的序列;
想要访问第 i 行,第 j 列的元素就没有那么简单了:
(1)首先上面就有 (i-1) 行像素,每行有 n×3 个元素,就是有 (i-1)×n×3 个元素。
(2)其次对于当前行,前面有 (j-1) 个像素,就是有 (j-1)×3 个元素。
(3)最后是对于当前位置的像素,我们知道顺序是BGR,所以B是0,G是1,R是2。
总的来说就是:访问第 i 行,第 j 列的像素,B是 (i-1)×n×3+(j-1)×3 ,G是 (i-1)×n×3+(j-1)×3+1 ,R是 (i-1)×n×3+(j-1)×3+2 。
对于彩色图像来说,如果真的按照上面讲的这样,就真的很麻烦了,所以在OpenCV里,对于彩色图像,我们提供了非常方便的获取像素指针的方式。
2.代码实战
1.灰度图像
我们知道获取像素指针其实就是提供了一个访问像素的途径,在这里,我们用到一个at方法:
如果我们想获取坐标为(x,y)的像素,我们可以使用如下方法:
src_gray.at<uchar>(y, x); //行在前,列在后,y表示行,x表示列
上面会返回一个uchar类型的数据,但是我们是需要一其数值,所以我们可以定义一个整型数据来获取,举个例子:
int sc1 = src_gray.at<uchar>(src_gray.rows/2, src_gray.cols/2);
当然我们也可以使用我们之前学习Scalar类型来获取,因为是单通道,所以后三个位置全为0:
Scalar sc2 = src_gray.at<uchar>(src_gray.rows / 2, src_gray.cols / 2);
上面我们是先使用y,再使用x,这是因为我们是按照行列访问,如果我们是按照点访问,就需要用到我们之前学的点结构了:
src_gray.at<uchar>(Point(x, y));
示例如下:
Scalar sc3 = src_gray.at<uchar>(Point(src_gray.cols / 2, src_gray.rows / 2));
输出代码及结果如下:
cout << "scalar of src_gray(Scalar) : " << sc1 << endl;
cout << "scalar of src_gray(int) : " << sc2 << endl;
cout << "scalar of src_gray(Point) : " << sc3 << endl;
2.彩色图像
我们依然使用at方法但是这个时候,我们就不能直接使用int类型了,而且我们也要用到一个新的类型Vec3b:
Vec3b sc4 = src.at<Vec3b>(src.rows / 2, src_gray.cols / 2);
int B = sc4.val[0];
int G = sc4.val[1];
int R = sc4.val[2];
Scalar sc5 = src.at<Vec3b>(Point(src.cols / 2, src_gray.rows / 2));
cout << "scalar of src(Vec3b) : [" << B << ", " << G << ", " << R << "] " << endl;
cout << "scalar of src(Scalar) : " << sc5 << endl;
跟上面一样使用at方法,但因为是三通道的,所以我们用到新的类型Vec3b,定义如下:
typedef Vec<uchar, 3> Vec3b;
我们可以这样理解:这是一个包含三个uchar类型数据的一个结构。
然后我们想要获取三个通道的值,我们就依次获取其值就好:
int B = sc4.val[0];
int G = sc4.val[1];
int R = sc4.val[2];
2、控制像素范围
1.理论讲解
我们获取到的值,它们是有范围的,我们在最开始也说了,对于灰度图来说,像素的取值范围是0-255,对于彩色图像来说,每个通道的取值范围也是0-255,那我们在操作像素的时候,一旦超过这个范围怎么办呢?
在OpenCV中,我们提供了一个方法来控制其取值范围:
/////////////// saturate_cast (used in image & signal processing) ///////////////////
/** @brief Template function for accurate conversion from one primitive type to another.
The function saturate_cast resembles the standard C++ cast operations, such as static_cast\<T\>()
and others. It perform an efficient and accurate conversion from one primitive type to another
(see the introduction chapter). saturate in the name means that when the input value v is out of the
range of the target type, the result is not formed just by taking low bits of the input, but instead
the value is clipped. For example:
@code
uchar a = saturate_cast<uchar>(-100); // a = 0 (UCHAR_MIN)
short b = saturate_cast<short>(33333.33333); // b = 32767 (SHRT_MAX)
@endcode
Such clipping is done when the target type is unsigned char , signed char , unsigned short or
signed short . For 32-bit integers, no clipping is done.
When the parameter is a floating-point value and the target type is an integer (8-, 16- or 32-bit),
the floating-point value is first rounded to the nearest integer and then clipped if needed (when
the target type is 8- or 16-bit).
@param v Function parameter.
@sa add, subtract, multiply, divide, Mat::convertTo
*/
template<typename _Tp> static inline _Tp saturate_cast(uchar v) { return _Tp(v); }
/** @overload */
template<typename _Tp> static inline _Tp saturate_cast(schar v) { return _Tp(v); }
/** @overload */
template<typename _Tp> static inline _Tp saturate_cast(ushort v) { return _Tp(v); }
/** @overload */
template<typename _Tp> static inline _Tp saturate_cast(short v) { return _Tp(v); }
/** @overload */
template<typename _Tp> static inline _Tp saturate_cast(unsigned v) { return _Tp(v); }
/** @overload */
template<typename _Tp> static inline _Tp saturate_cast(int v) { return _Tp(v); }
/** @overload */
template<typename _Tp> static inline _Tp saturate_cast(float v) { return _Tp(v); }
/** @overload */
template<typename _Tp> static inline _Tp saturate_cast(double v) { return _Tp(v); }
/** @overload */
template<typename _Tp> static inline _Tp saturate_cast(int64 v) { return _Tp(v); }
/** @overload */
template<typename _Tp> static inline _Tp saturate_cast(uint64 v) { return _Tp(v); }
template<> inline uchar saturate_cast<uchar>(schar v) { return (uchar)std::max((int)v, 0); }
template<> inline uchar saturate_cast<uchar>(ushort v) { return (uchar)std::min((unsigned)v, (unsigned)UCHAR_MAX); }
template<> inline uchar saturate_cast<uchar>(int v) { return (uchar)((unsigned)v <= UCHAR_MAX ? v : v > 0 ? UCHAR_MAX : 0); }
template<> inline uchar saturate_cast<uchar>(short v) { return saturate_cast<uchar>((int)v); }
template<> inline uchar saturate_cast<uchar>(unsigned v) { return (uchar)std::min(v, (unsigned)UCHAR_MAX); }
template<> inline uchar saturate_cast<uchar>(float v) { int iv = cvRound(v); return saturate_cast<uchar>(iv); }
template<> inline uchar saturate_cast<uchar>(double v) { int iv = cvRound(v); return saturate_cast<uchar>(iv); }
template<> inline uchar saturate_cast<uchar>(int64 v) { return (uchar)((uint64)v <= (uint64)UCHAR_MAX ? v : v > 0 ? UCHAR_MAX : 0); }
template<> inline uchar saturate_cast<uchar>(uint64 v) { return (uchar)std::min(v, (uint64)UCHAR_MAX); }
template<> inline schar saturate_cast<schar>(uchar v) { return (schar)std::min((int)v, SCHAR_MAX); }
template<> inline schar saturate_cast<schar>(ushort v) { return (schar)std::min((unsigned)v, (unsigned)SCHAR_MAX); }
template<> inline schar saturate_cast<schar>(int v) { return (schar)((unsigned)(v-SCHAR_MIN) <= (unsigned)UCHAR_MAX ? v : v > 0 ? SCHAR_MAX : SCHAR_MIN); }
template<> inline schar saturate_cast<schar>(short v) { return saturate_cast<schar>((int)v); }
template<> inline schar saturate_cast<schar>(unsigned v) { return (schar)std::min(v, (unsigned)SCHAR_MAX); }
template<> inline schar saturate_cast<schar>(float v) { int iv = cvRound(v); return saturate_cast<schar>(iv); }
template<> inline schar saturate_cast<schar>(double v) { int iv = cvRound(v); return saturate_cast<schar>(iv); }
template<> inline schar saturate_cast<schar>(int64 v) { return (schar)((uint64)((int64)v-SCHAR_MIN) <= (uint64)UCHAR_MAX ? v : v > 0 ? SCHAR_MAX : SCHAR_MIN); }
template<> inline schar saturate_cast<schar>(uint64 v) { return (schar)std::min(v, (uint64)SCHAR_MAX); }
template<> inline ushort saturate_cast<ushort>(schar v) { return (ushort)std::max((int)v, 0); }
template<> inline ushort saturate_cast<ushort>(short v) { return (ushort)std::max((int)v, 0); }
template<> inline ushort saturate_cast<ushort>(int v) { return (ushort)((unsigned)v <= (unsigned)USHRT_MAX ? v : v > 0 ? USHRT_MAX : 0); }
template<> inline ushort saturate_cast<ushort>(unsigned v) { return (ushort)std::min(v, (unsigned)USHRT_MAX); }
template<> inline ushort saturate_cast<ushort>(float v) { int iv = cvRound(v); return saturate_cast<ushort>(iv); }
template<> inline ushort saturate_cast<ushort>(double v) { int iv = cvRound(v); return saturate_cast<ushort>(iv); }
template<> inline ushort saturate_cast<ushort>(int64 v) { return (ushort)((uint64)v <= (uint64)USHRT_MAX ? v : v > 0 ? USHRT_MAX : 0); }
template<> inline ushort saturate_cast<ushort>(uint64 v) { return (ushort)std::min(v, (uint64)USHRT_MAX); }
template<> inline short saturate_cast<short>(ushort v) { return (short)std::min((int)v, SHRT_MAX); }
template<> inline short saturate_cast<short>(int v) { return (short)((unsigned)(v - SHRT_MIN) <= (unsigned)USHRT_MAX ? v : v > 0 ? SHRT_MAX : SHRT_MIN); }
template<> inline short saturate_cast<short>(unsigned v) { return (short)std::min(v, (unsigned)SHRT_MAX); }
template<> inline short saturate_cast<short>(float v) { int iv = cvRound(v); return saturate_cast<short>(iv); }
template<> inline short saturate_cast<short>(double v) { int iv = cvRound(v); return saturate_cast<short>(iv); }
template<> inline short saturate_cast<short>(int64 v) { return (short)((uint64)((int64)v - SHRT_MIN) <= (uint64)USHRT_MAX ? v : v > 0 ? SHRT_MAX : SHRT_MIN); }
template<> inline short saturate_cast<short>(uint64 v) { return (short)std::min(v, (uint64)SHRT_MAX); }
template<> inline int saturate_cast<int>(unsigned v) { return (int)std::min(v, (unsigned)INT_MAX); }
template<> inline int saturate_cast<int>(int64 v) { return (int)((uint64)(v - INT_MIN) <= (uint64)UINT_MAX ? v : v > 0 ? INT_MAX : INT_MIN); }
template<> inline int saturate_cast<int>(uint64 v) { return (int)std::min(v, (uint64)INT_MAX); }
template<> inline int saturate_cast<int>(float v) { return cvRound(v); }
template<> inline int saturate_cast<int>(double v) { return cvRound(v); }
template<> inline unsigned saturate_cast<unsigned>(schar v) { return (unsigned)std::max(v, (schar)0); }
template<> inline unsigned saturate_cast<unsigned>(short v) { return (unsigned)std::max(v, (short)0); }
template<> inline unsigned saturate_cast<unsigned>(int v) { return (unsigned)std::max(v, (int)0); }
template<> inline unsigned saturate_cast<unsigned>(int64 v) { return (unsigned)((uint64)v <= (uint64)UINT_MAX ? v : v > 0 ? UINT_MAX : 0); }
template<> inline unsigned saturate_cast<unsigned>(uint64 v) { return (unsigned)std::min(v, (uint64)UINT_MAX); }
// we intentionally do not clip negative numbers, to make -1 become 0xffffffff etc.
template<> inline unsigned saturate_cast<unsigned>(float v) { return static_cast<unsigned>(cvRound(v)); }
template<> inline unsigned saturate_cast<unsigned>(double v) { return static_cast<unsigned>(cvRound(v)); }
template<> inline uint64 saturate_cast<uint64>(schar v) { return (uint64)std::max(v, (schar)0); }
template<> inline uint64 saturate_cast<uint64>(short v) { return (uint64)std::max(v, (short)0); }
template<> inline uint64 saturate_cast<uint64>(int v) { return (uint64)std::max(v, (int)0); }
template<> inline uint64 saturate_cast<uint64>(int64 v) { return (uint64)std::max(v, (int64)0); }
template<> inline int64 saturate_cast<int64>(uint64 v) { return (int64)std::min(v, (uint64)LLONG_MAX); }
/** @overload */
template<typename _Tp> static inline _Tp saturate_cast(float16_t v) { return saturate_cast<_Tp>((float)v); }
// in theory, we could use a LUT for 8u/8s->16f conversion,
// but with hardware support for FP32->FP16 conversion the current approach is preferable
template<> inline float16_t saturate_cast<float16_t>(uchar v) { return float16_t((float)v); }
template<> inline float16_t saturate_cast<float16_t>(schar v) { return float16_t((float)v); }
template<> inline float16_t saturate_cast<float16_t>(ushort v) { return float16_t((float)v); }
template<> inline float16_t saturate_cast<float16_t>(short v) { return float16_t((float)v); }
template<> inline float16_t saturate_cast<float16_t>(unsigned v){ return float16_t((float)v); }
template<> inline float16_t saturate_cast<float16_t>(int v) { return float16_t((float)v); }
template<> inline float16_t saturate_cast<float16_t>(uint64 v) { return float16_t((float)v); }
template<> inline float16_t saturate_cast<float16_t>(int64 v) { return float16_t((float)v); }
template<> inline float16_t saturate_cast<float16_t>(float v) { return float16_t(v); }
template<> inline float16_t saturate_cast<float16_t>(double v) { return float16_t((float)v); }
//! @}
} // cv
这里有很多定义,其实这个定义的含义很简单:
大于255的值,变为255;
小于0的值,变为0;
其他的不改变;
这样我们就可以把范围控制在0-255之间。如果有学过激活函数的同学对这个形式就很清晰了,这个也可以算作图像像素的激活函数。
2.代码实战
我们讲完了功能,我们做个实验,我们定义三个数据,然后使用这个定义控制其范围:
int a = saturate_cast<uchar>(-100); //返回 0
cout <<"a = "<< a << endl;
int b = saturate_cast<uchar>(100); //返回 100
cout << "b = " << b << endl;
int c = saturate_cast<uchar>(1000); //返回 255
cout << "c = " << c << endl;
执行结果如下:
说在后面的话
从今天这节内容开始,我们就要走进像素的世界了。也从今天开始,我们就要进入Mat的世界,带领Mat一起征战OpenCV的世界。
这个世界我们会遇到很多困难,但是不要担心,因为我们从未畏惧。
讲真,写这个很心累,教别人和自己学完全不是一个档次。学东西的时候,自己明白了就好,但是讲东西的时候,要给别人彻底讲明白。我不知道我的讲解水平如何,但是我的每一篇博客都是认真去写的,希望大家能够提出宝贵意见。我们一起加油!