opencv 基于图像单像素点的处理

1 基于图像单像素点的处理

看过数字图像处理一书的都知道,图像处理中基于像素点的处理分为两种
灰度变换:本质就是基于单像素点的变化处理。
空间滤波:本质就是基于邻域像素点的变化处理。
今天要讲的是在OpenCv下基于基于单像素点的处理,其中会讲到OpenCv针对单像素处理封装好的一些ApI,即实质原理。

1.1 图像像素点的访问

要进行基于单像素点的处理,首先就必须知道,给你一幅图,你如何去访问图中的每一个像素点。无论是网上还是各种教程书上都给出了三种访问像素点的方式,分别是:
1、 指针访问。实质就是通过数据指针直接访问数据。
2、 迭代器访问。类似STL中容器的访问。
3、 动态地址计算。将每个像素转换为对应类型(uchar或Vec3b),再进行访问。
下面以为图像中每个像素点加1为例说明三种方法。

/************************************************/
/*访问图像像素点的三种方法
/************************************************/
	int rows = img.rows;
	int cols = img.cols;
	int channels = img.channels();
	uchar *pRow = 0;

// 方法一:指针法访问
	for (int i = 0; i < rows; ++i)
	{
		// 获取行指针
		pRow = img.ptr<uchar>(i);
		for (int j = 0; j < cols; ++j)
		{
			if (channels == 1) //单通道
			{
				pRow[j] = pRow[j] + 1;
			}
			else if (channels == 3) //三通道
			{
				pRow[j * 3 ] = pRow[j * 3 ] + 1;
				pRow[j * 3 + 1] = pRow[j * 3 + 1] + 1;
				pRow[j * 3 + 2] = pRow[j * 3 + 2] + 1;
			}
		}
	}

// 方法一:迭代器法访问、事先得知道图像通道数,这里假设为3通道
	Mat_<Vec3b>::iterator it = img.begin<Vec3b>(); //初始位置迭代器
	Mat_<Vec3b>::iterator end = img.end<Vec3b>(); //终止位置迭代器
	for (; it != end; ++it)
	{
		(*it)[0] = (*it)[0] + 1;
		(*it)[1] = (*it)[1] + 1;
		(*it)[2] = (*it)[2] + 1;
	}

// 方法一:动态地址访问、事先得知道图像通道数,这里假设为3通道
	for (int i = 0; i < rows; ++i)
	{
		for (int j = 0; j < cols; ++j)
		{
			if (channels == 1) //单通道
			{
				img.at<uchar>(i,j) = img.at<uchar>(i, j)+1;
			}
			else if (channels == 3) //三通道
			{
				img.at<Vec3b>(i, j)[0] = img.at<Vec3b>(i, j)[0] + 1;
				img.at<Vec3b>(i, j)[1] = img.at<Vec3b>(i, j)[1] + 1;
				img.at<Vec3b>(i, j)[2] = img.at<Vec3b>(i, j)[2] + 1;
			}
		}
	}

1.2 图像亮度和对比度调整

知道如何对图像单像素点进行操作后,其实就可以对图像进行很多操作了,其中最有用,最让人产生直观感受的就是亮度和对比度的调整了。
基于单像素点变换分为以下几种:r为源像素点值,s为变换后像素点值。这里的值有可能是灰度图像中的灰度值,RGB颜色模型中的红、绿、蓝分量值,或者是HSV颜色模型中的色调、饱和度、亮度值。
1、 翻转变换

s=L-1-r
2、 线性变换
s=a*r+b
作用:a控制对比度,b控制亮度。 3、 对数变化
s=clog(1+r)
作用:围较窄的低灰度值映射到范围较宽的灰度区间,同时将范围较宽的高灰度值区间映射为较窄的灰度区间,从而扩展了暗像素的值,压缩了高灰度的值,能够对图像中低灰度细节进行增强。 4、幂(伽马)变换
s=cr^γ
作用: 当γ=c=1时:恒等变换。 当c=1,γ<1时:扩展低灰度级范围,压缩高灰度级范围,灰度值整体增大,变亮。γ 的值越小,对图像低灰度值的扩展越明显。 当c=1,γ>1时:压缩低灰度级范围,扩展高灰度级范围,灰度值整体减小,变暗。γ的值越大,对图像高灰度值部分的扩展越明显。 伽马变换主要用于图像的校正,对灰度值过高(图像过亮)或者过低(图像过暗)的图像进行修正,增加图像的对比。 值得注意的是上述4个变换中,除了翻转变换结果不可能溢出,即值在范围0-255之间,其他的都有可能溢出,特别是线性变换还可能出现负值。下面就以线性变换为例写一个小例子。例子是基于QT的,但不妨碍理解。
主要源码:
void QtGuiApplication1::update()
{
	int rows = m_Sor.rows;
	int cols = m_Sor.cols;
	int channels = m_Sor.channels();
	uchar *pSorRow = 0;
	uchar *pDstRow = 0;

	float contrast = m_Value1 / 10.f;
	// 方法一:指针法访问
	for (int i = 0; i < rows; ++i)
	{
		// 获取行指针
		pSorRow = m_Sor.ptr<uchar>(i);
		pDstRow = m_Dst.ptr<uchar>(i);
		for (int j = 0; j < cols; ++j)
		{
			if (channels == 1) //单通道
			{
				pDstRow[j] = pSorRow[j] + 1;
			}
			else if (channels == 3) //三通道
			{
				pDstRow[j * 3] = saturate_cast<uchar>(pSorRow[j * 3]* contrast + m_Value2);
				pDstRow[j * 3 + 1] = saturate_cast<uchar>(pSorRow[j * 3 + 1] * contrast + m_Value2);
				pDstRow[j * 3 + 2] = saturate_cast<uchar>(pSorRow[j * 3 + 2] * contrast + m_Value2);
			}
		}
	}

	//normalize(m_Dst, m_Dst, 0, 255,NORM_MINMAX);
	imshow("Dst", m_Dst);
}
使用到的opencv API:
saturate_cast(v)

作用:将输入值V限制在给定类型T范围内。

normalize()

void normalize( InputArray src, InputOutputArray dst, double alpha = 1, double beta = 0,
int norm_type = NORM_L2, int dtype = -1, InputArray mask = noArray());
参数:src:输出数组。
dst:输出数组。
alpha:参数。
beta:参数。
norm_type:类型。
dtype:输出数组的元素类型。默认和src一致。
mask:是否对src中的指定元素变换,默认全部元素变换。
norm_type取值:
opencv 基于图像单像素点的处理
作用:归一化函数,将数值限制在指定范围内,值得注意的是该函数支持输入值为负数哟。对数变换和指数变换后都需要使用该函数进行范围限制。

1.3 颜色空间缩减

什么是颜色空间缩减,目前我们接触到的图像都是256个灰度值的图像,对于显示来说自然是灰度值等级越多,图像越细腻,但是进行图像处理的时候可不是灰度值等级越多越好,而是适当就行,什么是适合呢?是100个灰度等级还是10个灰度等级,这得要具体情况具体对待了。
颜色空间缩减非常简单,一个公式就能明白,例如我们要把256个灰度级缩减到26个灰度级,那么
s=r/10*10
即对每个像素点都进行上述变换,那么一幅256个灰度级的图像最终就变为26个灰度级的图像。
为了提高提高变换的效率,这里可使用查表法,就是预先把每个灰度值将要变换为的灰度值体现算好,放在表中,变换时直接查表。Opencv提供了一个现成的API专门处理的这种查表式变换。

源码例子:
#include "QtGuiApplication1.h"

//#define Ui
#ifndef Ui

#include <QFileInfo>
#include <QDir>
#include <opencv2\opencv.hpp>
using namespace cv;

#else

#include <QtWidgets/QApplication>

#endif

int main(int argc, char *argv[])
{
	QApplication a(argc, argv);

#ifdef Ui
	QtGuiApplication1 w;
	w.show();
	return a.exec();
#else
	QString path = QFileInfo(QCoreApplication::applicationDirPath(), "../../../../../").absoluteDir().absolutePath() + "/imgs/";
	Mat src = imread(path.toStdString() + "sor1.jpg", IMREAD_GRAYSCALE);
	imshow("src", src);

	int divideWith = 50;
	Mat table(1,256,CV_8U);
	for (int i = 0; i < 256; ++i)
	{
		table.data[i] = (i / divideWith) * divideWith;
	}

	Mat dst;
	LUT(src, table, dst);
	imshow("dst", dst);
	waitKey();

	return 0;
#endif
	
}

效果图:明显灰度级较少之后,图片变得很粗糙了。
opencv 基于图像单像素点的处理