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、 翻转变换
主要源码:
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取值:
作用:归一化函数,将数值限制在指定范围内,值得注意的是该函数支持输入值为负数哟。对数变换和指数变换后都需要使用该函数进行范围限制。
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
}
效果图:明显灰度级较少之后,图片变得很粗糙了。