【opencv4.3.0教程】07之像素基本操作 1 获取像素指针及控制像素范围

目录

一、前言

二、温故知新——基本数据类型

1、最基本之Mat类

2、四大天王之Point_、Size_、Rect_、Scalar_

3、各大分堂主

三、像素及相关概念

1、像素

2、灰度图与彩图

四、像素基本操作

1、获取像素指针

扫描二维码关注公众号,回复: 11237987 查看本文章

1.理论讲解

2.代码实战

2、控制像素范围

1.理论讲解

2.代码实战

说在后面的话


一、前言

上几篇文章,我们讲完了OpenCV中的常见基础结构。

从这篇博客开始,我们要踏上新的征程,我们要从像素的角度,来一起了解图像,一起了解OpenCV。今天这篇博客,我们讲最基本的图像像素操作,让我们一起,复习一下之前学的基本数据类型,然后一起走进今天的内容吧!

二、温故知新——基本数据类型

我想大家应该还记得我们之前学的数据类型,主要有如下:

1、最基本之Mat类

Mat类是我们学到的最基本的数据类型,从这节课开始往后的每节课,都会涉及到Mat类。Mat常用于声明或定义一个图像,偶尔也用于声明一个二维矩阵。

具体内容大家请看:

Mat类详解

2、四大天王之Point_、Size_、Rect_、Scalar_

四大天王,这个名字也是很形象了,因为这个是除了Mat类之外最常用的四个类型,经常需要辅助Mat类做一些操作。他们分别是:

表示 的 Point_;

表示 尺寸 的 Size_;

表示 矩形 的 Rect_;

表示 颜色 的 Scalar_;

具体内容大家请看:

Point_与Size_

Rect_

Scalar_

3、各大分堂主

其他数据类型虽然不如上面四个常用,但是他们在自己的领域不可替代,会协助Mat类和上面四个常用类型做更多的事,实现更加丰富的功能:

表示 复数 的 Complex;

表示 三维点 的 Point_;

表示 旋转矩形 的 RotatedRect;

表示 序列 的 Range;

表示 角点 的 KeyPoint;

表示 索引与描述符距离 的 DMatch;

表示 迭代算法终止条件 的 TermCriteria;

具体内容大家请看:

OpenCV基本结构

三、像素及相关概念

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的世界。

这个世界我们会遇到很多困难,但是不要担心,因为我们从未畏惧。

讲真,写这个很心累,教别人和自己学完全不是一个档次。学东西的时候,自己明白了就好,但是讲东西的时候,要给别人彻底讲明白。我不知道我的讲解水平如何,但是我的每一篇博客都是认真去写的,希望大家能够提出宝贵意见。我们一起加油!

 

猜你喜欢

转载自blog.csdn.net/shuiyixin/article/details/106182880