OpenCV2马拉松第9圈——再谈对比度(对比度拉伸,直方图均衡化)
来源:互联网 发布:无人机pos数据怎么确定 编辑:程序博客网 时间:2024/06/02 22:12
收入囊中
- lookup table
- 对比度拉伸
- 直方图均衡化
葵花宝典
lookup table是什么东西呢?
举个例子,假设你想把图像颠倒一下,f[i] = 255-f[i],你会怎么做?
for( int i = 0; i < I.rows; ++i) for( int j = 0; j < I.cols; ++j ) I.at<uchar>(i,j) = 255 - I.at<uchar>(i,j);大部分人应该都会这么做.或者:
for( i = 0; i < nRows; ++i){ p = I.ptr<uchar>(i); for ( j = 0; j < nCols; ++j){ p[j] = 255 - p[j]; }}或者使用迭代器
MatIterator_<uchar> it, end; for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it) *it = 255 - *it;
OpenCV提供了一个更快的方法,如下代码
LUT函数接收src,table和output
table是一个1*256的mat,将对应关系已经map好了
#include "opencv2/highgui/highgui.hpp"#include "opencv2/imgproc/imgproc.hpp"using namespace cv;Mat applyLookUp(const cv::Mat& image,const cv::Mat& lookup) { Mat result;cv::LUT(image,lookup,result);return result;}int main( int, char** argv ){ Mat image,gray; image = imread( argv[1], 1 ); if( !image.data ) return -1; cvtColor(image, gray, CV_BGR2GRAY); Mat lut(1,256,CV_8U);for (int i=0; i<256; i++) {lut.at<uchar>(i)= 255-i;}Mat out = applyLookUp(gray,lut);namedWindow("sample");imshow("sample",out); waitKey(0); return 0;}
第二种:Efficient Way79.4717 milliseconds
第三种:Iterator83.7201 milliseconds
第四种:LUT function32.5759 milliseconds
对比度拉伸又是什么?先来直观地看张图片。
右边是原始图,可以发现,低灰度没有像素,但是左边就比较好,低灰度也有,这就是对比度的拉伸。
公式非常简单:f[i] = 255.0*(i-imin)/(imax-imin)+0.5);
当灰度 < imin , f[i] = 0;
当灰度 > imax, f[i] = 255;
imin < f[i] < imax,就线性映射.
有人就会问了,imin和imax要怎么确定呢?imin取10还是20还是30呢?
我们可以确定一个阀值minvalue,当灰度的个数>这个阀值minvalue时,就确定下来了。看下面这个确定的过程:
histSize[0]就是256
Mat hist= getHistogram(image);int imin= 0;for( ; imin < histSize[0]; imin++ ) if (hist.at<float>(imin) > minValue) break;int imax= histSize[0]-1;for( ; imax >= 0; imax-- ) if (hist.at<float>(imax) > minValue) break;
Mat lookup(1, 256, CV_8U);for (int i=0; i<256; i++) { if (i < imin) lookup.at<uchar>(i)= 0; else if (i > imax) lookup.at<uchar>(i)= 255; else lookup.at<uchar>(i)= static_cast<uchar>(255.0*(i-imin)/(imax-imin)+0.5);}
这也是一个老生长谈的问题了
我在http://blog.csdn.net/abcd1992719g/article/details/24357797#t4这里写过直方图均衡化
我再赘述一下
直方图均衡化的思想就是这样的。假设我有灰度级255的图像,但是都是属于[100,110]的灰度,图像对比度就很低,我应该尽可能拉到整个[0,255]
下面是直方图均衡化的代码,有个累积函数的概念,其实很简单。
我先计算出每个灰度级g(0),g(1)......g(255)点的个数,sum为图像width*height
那么累计函数c(0) = g(0)/sum
c(1) = (g(0)+g(1))/sum
......
c(255) = 1
下面是JAVA代码,改C++非常容易的
public int[][] Histogram_Equalization(int[][] oldmat) { int[][] new_mat = new int[height][width]; int[] tmp = new int[256]; for(int i = 0;i < width;i++){ for(int j = 0;j < height;j++){ //System.out.println(oldmat[j][i]); int index = oldmat[j][i]; tmp[index]++; } } float[] C = new float[256]; int total = width*height; //计算累积函数 for(int i = 0;i < 256 ; i++){ if(i == 0) C[i] = 1.0f * tmp[i] / total; else C[i] = C[i-1] + 1.0f * tmp[i] / total; } for(int i = 0;i < width;i++){ for(int j = 0;j < height;j++){ new_mat[j][i] = (int)(C[oldmat[j][i]] * 255); new_mat[j][i] = new_mat[j][i] + (new_mat[j][i] << 8) + (new_mat[j][i] << 16); //System.out.println(new_mat[j][i]); } } return new_mat; }
这是效果图,可以看到原来的图像被拉伸了
自适应直方图均衡化
AHE算法通过计算图像的局部直方图,然后重新分布亮度来来改变图像对比度。因此,该算法更适合于改进图像的局部对比度以及获得更多的图像细节。
想像以下一幅图像,左上角是黑乎乎的一团,但是其他区域很正常,如果只用HE,那么黑乎乎的那团是没法有多大改进的。
于是,你可以把那黑乎乎的一团当作一张图片,对那一部分进行HE,其实这就是AHE了,就是把图片分片处理,8*8是常用的选择。
然后,你就可以写一个循环来操作,算法和HE是一模一样的,当然可以工作,只是速度比较慢。
正如我下面代码所写的,利用双线性插值。
我以前写CLAHE时候看的博客找不到了T_T http://m.blog.csdn.net/blog/gududeyhc/8997009这里有但是远远没我以前看的那篇讲的清楚,如果你去看Pizer的论文估计要花很多的时间。下面是我用Java写的CLAHE.
CLAHE比AHE多了裁剪补偿的操作
/* * CLAHE * 自适应直方图均衡化 */ public int[][] AHE(int[][] oldmat,int pblock) { int block = pblock; //将图像均匀分成等矩形大小,8行8列64个块是常用的选择 int width_block = width/block; int height_block = height/block; //存储各个直方图 int[][] tmp = new int[block*block][256]; //存储累积函数 float[][] C = new float[block*block][256]; //计算累积函数 for(int i = 0 ; i < block ; i ++) { for(int j = 0 ; j < block ; j++) { int start_x = i * width_block; int end_x = start_x + width_block; int start_y = j * height_block; int end_y = start_y + height_block; int num = i+block*j; int total = width_block * height_block; for(int ii = start_x ; ii < end_x ; ii++) { for(int jj = start_y ; jj < end_y ; jj++) { int index = oldmat[jj][ii]; tmp[num][index]++; } } //裁剪操作 int average = width_block * height_block / 255; int LIMIT = 4 * average; int steal = 0; for(int k = 0 ; k < 256 ; k++) { if(tmp[num][k] > LIMIT){ steal += tmp[num][k] - LIMIT; tmp[num][k] = LIMIT; } } int bonus = steal/256; //hand out the steals averagely for(int k = 0 ; k < 256 ; k++) { tmp[num][k] += bonus; } //计算累积分布直方图 for(int k = 0 ; k < 256 ; k++) { if( k == 0) C[num][k] = 1.0f * tmp[num][k] / total; else C[num][k] = C[num][k-1] + 1.0f * tmp[num][k] / total; } } } int[][] new_mat = new int[height][width]; //计算变换后的像素值 //根据像素点的位置,选择不同的计算方法 for(int i = 0 ; i < width; i++) { for(int j = 0 ; j < height; j++) { //four coners if(i <= width_block/2 && j <= height_block/2) { int num = 0; new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255); }else if(i <= width_block/2 && j >= ((block-1)*height_block + height_block/2)){ int num = block*(block-1); new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255); }else if(i >= ((block-1)*width_block+width_block/2) && j <= height_block/2){ int num = block-1; new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255); }else if(i >= ((block-1)*width_block+width_block/2) && j >= ((block-1)*height_block + height_block/2)){ int num = block*block-1; new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255); } //four edges except coners else if( i <= width_block/2 ) { //线性插值 int num_i = 0; int num_j = (j - height_block/2)/height_block; int num1 = num_j*block + num_i; int num2 = num1 + block; float p = (j - (num_j*height_block+height_block/2))/(1.0f*height_block); float q = 1-p; new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255); }else if( i >= ((block-1)*width_block+width_block/2)){ //线性插值 int num_i = block-1; int num_j = (j - height_block/2)/height_block; int num1 = num_j*block + num_i; int num2 = num1 + block; float p = (j - (num_j*height_block+height_block/2))/(1.0f*height_block); float q = 1-p; new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255); }else if( j <= height_block/2 ){ //线性插值 int num_i = (i - width_block/2)/width_block; int num_j = 0; int num1 = num_j*block + num_i; int num2 = num1 + 1; float p = (i - (num_i*width_block+width_block/2))/(1.0f*width_block); float q = 1-p; new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255); }else if( j >= ((block-1)*height_block + height_block/2) ){ //线性插值 int num_i = (i - width_block/2)/width_block; int num_j = block-1; int num1 = num_j*block + num_i; int num2 = num1 + 1; float p = (i - (num_i*width_block+width_block/2))/(1.0f*width_block); float q = 1-p; new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255); } //inner area else{ int num_i = (i - width_block/2)/width_block; int num_j = (j - height_block/2)/height_block; int num1 = num_j*block + num_i; int num2 = num1 + 1; int num3 = num1 + block; int num4 = num2 + block; float u = (i - (num_i*width_block+width_block/2))/(1.0f*width_block); float v = (j - (num_j*height_block+height_block/2))/(1.0f*height_block); new_mat[j][i] = (int)((u*v*C[num4][oldmat[j][i]] + (1-v)*(1-u)*C[num1][oldmat[j][i]] + u*(1-v)*C[num2][oldmat[j][i]] + v*(1-u)*C[num3][oldmat[j][i]]) * 255); } new_mat[j][i] = new_mat[j][i] + (new_mat[j][i] << 8) + (new_mat[j][i] << 16); } } return new_mat; }
难道直方图均衡化的代码要让我们自己写?当然不是,下面就是API
初识API
- C++: void equalizeHist(InputArray src, OutputArray dst)
- src – Source 8-bit single channel image.
- dst – Destination image of the same size and type as src .
内部好像不是用自适应直方图均衡化来做
荷枪实弹
先给出对比度拉伸的源代码
有一个我们上次用过的直方图类,加了一个拉伸的方法
#include "opencv2/highgui/highgui.hpp"#include "opencv2/imgproc/imgproc.hpp"using namespace cv;Mat applyLookUp(const cv::Mat& image,const cv::Mat& lookup) { Mat result;cv::LUT(image,lookup,result);return result;}class Histogram1D {private:int histSize[1]; // number of binsfloat hranges[2]; // min and max pixel valueconst float* ranges[1];int channels[1];public:Histogram1D() {histSize[0]= 256;hranges[0]= 0.0;hranges[1]= 255.0;ranges[0]= hranges;channels[0]= 0; // by default, we look at channel 0}Mat getHistogram(const cv::Mat &image) {Mat hist;calcHist(&image,1,channels,Mat(),hist,1,histSize,ranges);return hist;}Mat getHistogramImage(const cv::Mat &image){Mat hist= getHistogram(image);double maxVal=0;double minVal=0;minMaxLoc(hist, &minVal, &maxVal, 0, 0);Mat histImg(histSize[0], histSize[0],CV_8U,Scalar(255));int hpt = static_cast<int>(0.9*histSize[0]);for( int h = 0; h < histSize[0]; h++ ) {float binVal = hist.at<float>(h);int intensity = static_cast<int>(binVal*hpt/maxVal);line(histImg,Point(h,histSize[0]),Point(h,histSize[0]-intensity),Scalar::all(0));}return histImg;}Mat stretch(const cv::Mat &image, int minValue=0) {Mat hist= getHistogram(image);int imin= 0;for( ; imin < histSize[0]; imin++ )if (hist.at<float>(imin) > minValue)break;int imax= histSize[0]-1;for( ; imax >= 0; imax-- )if (hist.at<float>(imax) > minValue)break;Mat lookup(1, 256, CV_8U);for (int i=0; i<256; i++) {if (i < imin) lookup.at<uchar>(i)= 0;else if (i > imax) lookup.at<uchar>(i)= 255;else lookup.at<uchar>(i)= static_cast<uchar>(255.0*(i-imin)/(imax-imin)+0.5);}Mat result;result= applyLookUp(image,lookup);return result;}};int main( int, char** argv ){ Mat image,gray; image = imread( argv[1], 1 ); if( !image.data ) return -1; cvtColor(image, gray, CV_BGR2GRAY); namedWindow("original");imshow("original",gray); Histogram1D h;Mat streteched = h.stretch(gray,100);namedWindow("sample");imshow("sample",streteched);namedWindow("histogram1");imshow("histogram1",h.getHistogramImage(gray));namedWindow("histogram2");imshow("histogram2",h.getHistogramImage(streteched)); waitKey(0); return 0;}
#include "opencv2/highgui/highgui.hpp"#include "opencv2/imgproc/imgproc.hpp"#include <iostream>#include <stdio.h>using namespace cv;using namespace std;int main( int, char** argv ){ Mat src, dst; const char* source_window = "Source image"; const char* equalized_window = "Equalized Image"; /// Load image src = imread( argv[1], 1 ); if( !src.data ) { cout<<"Usage: ./Histogram_Demo <path_to_image>"<<endl; return -1; } /// Convert to grayscale cvtColor( src, src, CV_BGR2GRAY ); /// Apply Histogram Equalization equalizeHist( src, dst ); /// Display results namedWindow( source_window, CV_WINDOW_AUTOSIZE ); namedWindow( equalized_window, CV_WINDOW_AUTOSIZE ); imshow( source_window, src ); imshow( equalized_window, dst ); /// Wait until user exits the program waitKey(0); return 0;
举一反三
我在上面给出了CLAHE的JAVA代码
这是一个很好的学习材料,双线性插值加速,附带剪裁补偿
如果你有时间,应该认真去看看,这是当初花了一天的时间写的TAT
计算机视觉讨论群:162501053
转载请注明:http://blog.csdn.net/abcd1992719g
11 1
- OpenCV2马拉松第9圈——再谈对比度(对比度拉伸,直方图均衡化)
- OpenCV2马拉松第3圈——改变对比度和亮度
- OpenCV2马拉松第8圈——绘制直方图
- OpenCV2马拉松第12圈——直方图比较
- 图像增强对比度的方法——直方图均衡化
- 关于图像对比度【2】——直方图均衡化
- 图像增强—限制对比度自适应直方图均衡化
- 亮度、对比度、直方图均衡化
- OpenCV2马拉松第10圈——直方图反向投影(back project)
- OpenCV2马拉松第11圈——meanshift与直方图反向投影
- 限制对比度自适应直方图均衡化
- OpenCV2.4.4中利用直方图均衡化增强图像对比度(Mat结构实现)
- OpenCV2.4.4中利用直方图均衡化增强图像对比度(Mat结构实现)
- 关于图像对比度【3】——直方图均衡化的改进
- 限制对比度自适应直方图均衡
- OpenCV2马拉松第4圈——图片混合
- OpenCV2马拉松第1圈——纵观全局
- OpenCV2马拉松第2圈——读写图片
- android Listview的getView和convertview原理解析以及使用不同的item布局(二)
- WebBrowser实现Javascript和Winform代码之间实现双向通信
- hibernate映射(二) 之Hibernate单项一对多映射
- 一、启动4个线程,在控制台中输出数字,保证数字是按照顺序递增,并且没有重复数字,每隔1秒输出一个数字。
- TCP协议三次握手过程分析
- OpenCV2马拉松第9圈——再谈对比度(对比度拉伸,直方图均衡化)
- 黑马程序员_求任意字符串及其所有子串的全排列
- 数列题解
- 利用java发射 生产对象的sql插入语句
- cocos2dx 3.0 final版本 mac 创建工程
- org.apache.log4j.Logger 详解
- sed命令的使用
- OCP-1Z0-051 第121题 NATURAL JOIN自然连接
- 寄人篱下,却也不卑不亢,为了自己心中的那点情愫付出无数痴心