写在前面
先交代下代码实现的技术细节吧。
1、在最后的代码里,图像反转,对数变换方法1,对比度拉伸,比特平面分层的实现均采用指针访问像素的方式,以前总是用Opencv的at模板,但是指针最为高效,我还是用一下指针吧。当然,还有迭代器访问的方式,也是最为安全的方式,以后试一下。访问元素的方式常用的有四种,详情可以参考:https://www.cnblogs.com/ronny/p/opencv_road_2.html
2、对数变换,用了两种写法,对数变换方法1上面说了是用指针,方法2则使用了OPencv的log函数,可以进行数组运算,也可以说是矩阵运算吧,没有遍历像素,也是一种高效的方法。
一、图像反转(负片变换)
灰度变换时所有图像处理技术中最简单的技术,负片变换又是灰度变换中最简单的技术,它可用于观察过暗的图像,特别是适用于增强嵌入图像暗色区域中白色或灰色细节。变换如下:
二、对数变换
对数变换主要完成图像灰度级的扩展或压缩,也就是将图像的低灰度值部分扩展,将其高灰度值部分压缩,以达到强调图像低灰度部分的目的,而反对数变换则相反。值得说明的是,相比对数变换,Gamma(幂律)变换更适用完成图像灰度级的扩展或压缩。对数变换的通用形式:
原图: c=10:
三、对比度拉伸
灰度拉伸也用于强调图像的某个部分,与伽马变换与对数变换不同的是,灰度拉升可以改善图像的动态范围。可以将原来低对比度的图像拉伸为高对比度图像。实现灰度拉升的方法很多,其中最简单的一种就是线性拉伸,常用的是三段线性变换,形式如下:
四、比特平面分层
灰度图是一个由像素组成的矩阵,目前大部分的灰度图中的像素用1B存储即8bit,比特平面分层的意思是将所有像素的相同比特拿出来单独组成一个矩阵,这样就会有八个矩阵。根据冈萨雷斯的《数字图像处理》,高比特部分的图像所含信息量大,而低比特部分所含信息量少。比特平面分层对于以后指导图像压缩或者计算机视觉具有重要意义。
由于每个像素值是十进制数,由8bit组成,所有首先需要将每个像素值转换为对应的二进制数。例如,像素值128,对应的二进制数为1000 0000,那么从左至右,依次为该像素的第8bit、第7bit、第6bit......,对于一幅图像构成的矩阵也就对应着有8个bit矩阵,分别为第8bit平面、第7bit平面、第6bit平面......。
比特平面分层的目的就是将这8个bit平面分开,并显示观察每个平面所包含的信息。
五、代码
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
/*图像反转*(用指针访问像素)*/
void Image_inversion(cv::Mat& src, cv::Mat& dst){
int nr = src.rows;
int nc = src.cols*src.channels();
src.copyTo(dst);
if (src.isContinuous() && dst.isContinuous()){ //判断图像连续性
nr = 1;
nc = src.rows*src.cols*src.channels(); //行数*列数 * 通道数=一维数组的个数
}
for (int i = 0; i < nr; i++){
const uchar* srcdata = src.ptr <uchar>(i); //采用指针访问像素,获取第i行的首地址
uchar* dstdata = dst.ptr <uchar>(i);
for (int j = 0; j < nc; j++){
dstdata[j] = 255 - srcdata[j]; //开始处理每个像素
}
}
}
/*对数变换方法1*(灰度图像和彩色图像都适用)*/
void LogTransform1(cv::Mat& src, cv::Mat& dst, double c){
int nr = src.rows;
int nc = src.cols*src.channels();
src.copyTo(dst);
dst.convertTo(dst, CV_64F);
if (src.isContinuous() && dst.isContinuous()){ //判断图像连续性
nr = 1;
nc = src.rows*src.cols*src.channels(); //行数*列数 * 通道数= 一维数组的个数
}
for (int i = 0; i < nr; i++){
const uchar* srcdata = src.ptr <uchar>(i); //采用指针访问像素,获取第i行的首地址
double* dstdata = dst.ptr <double>(i);
for (int j = 0; j < nc; j++){
dstdata[j] = c*log(double(1.0 + srcdata[j])); //开始处理每个像素
}
}
cv::normalize(dst, dst, 0, 255, cv::NORM_MINMAX); //经过对比拉升(将像素值归一化到0-255)得到最终的图像
dst.convertTo(dst, CV_8U); //转回无符号8位图像
}
/*对数变换方法2*(适用于灰度图像)*/
cv::Mat LogTransform2(cv::Mat& src , double c){
if (src.channels()>1)
cv::cvtColor(src, src, CV_RGB2GRAY);
cv::Mat dst;
src.copyTo(dst);
dst.convertTo(dst,CV_64F);
dst = dst + 1.0;
cv::log(dst,dst);
dst =c*dst;
cv::normalize(dst, dst, 0, 255, cv::NORM_MINMAX); //经过对比拉升(将像素值归一化到0-255)得到最终的图像
dst.convertTo(dst, CV_8U); //转回无符号8位图像
return dst;
}
/*分段线性变换——对比度拉伸*/
/*****************
三段线性变换
a<=b,c<=d
*****************/
void contrast_stretching(cv::Mat& src, cv::Mat& dst, double a, double b, double c, double d){
src.copyTo(dst);
dst.convertTo(dst, CV_64F);
double min = 0, max = 0;
cv::minMaxLoc(dst, &min, &max, 0, 0);
int nr = dst.rows;
int nc = dst.cols *dst.channels();
if (dst.isContinuous ()){
int nr = 1;
int nc = dst.cols * dst.rows * dst.channels();
}
for (int i = 0; i < nr; i++){
double* ptr_dst = dst.ptr<double>(i);
for (int j = 0; j < nc; j++){
if (min <= ptr_dst[j] < a)
ptr_dst[j] = (c / a)*ptr_dst[j];
else if(a <= ptr_dst[j] < b)
ptr_dst[j] = ((d-c)/(b-a))*ptr_dst[j];
else if (b <= ptr_dst[j] < max )
ptr_dst[j] = ((max - d) / (max - b))*ptr_dst[j];
}
}
dst.convertTo(dst, CV_8U); //转回无符号8位图像
}
/*bit平面分层*/
/*十进制数转二进制数*/
void num2Binary(int num, int b[8]){
int i;
for ( i = 0; i < 8; i++){
b[i] = 0;
}
i = 0;
while (num!=0){
b[i] = num % 2;
num = num / 2;
i++;
}
}
/***************************
num_bit - 指定bit平面
num_bit = 1~8
num_bit=1,即输出第1个Bit平面
****************************/
void Bitplane_stratification(cv::Mat& src,cv::Mat& B , int num_Bit){
int b[8];//8个二进制bit位
if (src.channels()>1)
cv::cvtColor(src, src, CV_RGB2GRAY);
B.create(src.size(), src.type());
for (int i = 0; i < src.rows; i++){
const uchar* ptr_src = src.ptr<uchar>(i);
uchar* ptr_B = B.ptr<uchar>(i);
for (int j = 0; j < src.cols; j++){
num2Binary(ptr_src[j], b);
ptr_B[j] = b[num_Bit - 1]*255; //0和1灰度差别太小,乘255便于视觉观察
}
}
}
int main(){
cv::Mat src =cv::imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\(2nd_from_top).tif");
if (src.empty()){
return -1;
}
cv::Mat dst;
//Image_inversion(src, dst); //图像反转
//LogTransform1(src, dst, 10); //对数变换1
//dst = LogTransform2(src,10); //对数变换2
//contrast_stretching(src, dst, 20, 100, 30, 200); //分段函数变换
Bitplane_stratification(src, dst, 8); //Bit平面分层
cv::namedWindow("src");
cv::imshow("src", src);
cv::namedWindow("dst");
cv::imshow("dst", dst);
cv::waitKey(0);
}
小武~~
关注
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_40647819/article/details/88014295
Gamma校正原理及c++实现(查找表法)
写在前面
如果直接按公式编程的话 ,需要执行大量浮点数的乘法、除法和指数运算。效率太低,在应用时是肯定不行的。
针对上述情况,有人提出了一种快速算法,如果能够确知图像的像素取值范围 , 例如 , 0 ~ 255 之间的整数 , 则图像中任何一个像素值只能 是 0 到 255 这 256 个整数中的某一个 ; 在 gamma 值 已知的情况下 ,0 ~ 255 之间的任一整数 , 经过“归一 化、预补偿、反归一化”操作后 , 所对应的结果是唯一的 , 并且也落在 0 ~ 255 这个范围内。
具体参考原文:Gamma校正原理及实现_gamma校正证明-CSDN博客
原图:
gamma=3:
代码
#include <iostream>
#include <opencv2\core.hpp>
#include <opencv2\highgui.hpp>
#include <opencv2\imgproc.hpp>
void gamma_correction(cv::Mat& src, cv::Mat& dst, float K){
uchar LUT[256];
src.copyTo(dst);
for (int i = 0; i < 256; i++){
//float f = (i + 0.5f) / 255;
float f = i / 255.0;
f = pow(f, K);
//LUT[i] = cv::saturate_cast<uchar>(f*255.0f-0.5f);
LUT[i] = cv::saturate_cast<uchar>(f*255.0);
}
if (dst.channels() == 1){
cv::MatIterator_<uchar> it = dst.begin<uchar>();
cv::MatIterator_<uchar> it_end = dst.end<uchar>();
for (; it != it_end; ++it){
*it = LUT[(*it)];
}
}
else{
cv::MatIterator_<cv::Vec3b> it = dst.begin<cv::Vec3b>();
cv::MatIterator_<cv::Vec3b> it_end = dst.end<cv::Vec3b>();
for (; it != it_end; ++it){
(*it)[0] = LUT[(*it)[0]];
(*it)[1] = LUT[(*it)[1]];
(*it)[2] = LUT[(*it)[2]];
}
}
}
int main(){
cv::Mat src = cv::imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\(washed_out_aerial_image).tif");
if (src.empty()){
return -1;
}
cv::Mat dst;
gamma_correction(src, dst, 3); //gamma变换
cv::namedWindow("src");
cv::imshow("src", src);
cv::namedWindow("dst");
cv::imshow("dst", dst);
cv::waitKey(0);
}