- 直方图规范化原理简介
- 07.24.0代码版本
1.直方图的规范化
直方图的规范化主要完成的任务是提高图像的对比度,实现过程主要如下,将灰度图像的的直方图计算出来,然后通过将不均匀的直方图进行拉伸(即对原图像中的灰度图进行调整)使得整个直方图灰度分布较为平均,这样得到的图像的对比度较高。是预处理阶段较为常见的而有效的图像处理手段。
最简单的直方图是线性变换通过对密集区域的拉伸实现灰度的平均分布。但有时难以达到理想的效果,所以一种改进的的方法可以对原图像的灰度直方图进行分段,分为拉伸端和压缩段已达到更好的效果。另一种改进的方法是先设计一种灰度拉伸的结果然建立原图像的灰度直方图和拉升后的灰度直方图的映射,可以不同于原图像分布的可以订制的直方图分布。
本文参考一篇论文的形式将进行原直方图进行分段拉伸,主要的难点在于选取原直方图的分段阈值点。这里采用最小误差法的方式。
最小误差法将原图像的灰度直方图分成对象和背景两个部分,并且将两个部分认为是两个高斯分布的拟合。用一个评价函数来确定阈值的值,当评价函数最小的时候说明设定的阈值越好,拟合效果越好,本文通过两次最小误差法的计算,第一次阈值用于将图像分成对象和非两个部分,第二次用阈值非对象分成背景和过度部分。然后对对象部分进行拉伸,对过度部分保持不变,对背景进行压缩。用将原图像分成三段并进行不同处理的方式完成提高对比度的任务。
PS:本人度这套算法有两点不同意见。
首先,我认为第二次阈值计算的方式有些不太合理,因为虽然可以将对象和背景视为两个高斯分布的拟合,但是将过度部分和背景也视为两个高斯部分的拟合有些不太合理,而且实际的效果也说明只能针对特定的图像完成两侧阈值的设置,有些图像在进行第二次阈值判断时会出现将阈值设置在整个灰度范围的两端,也就是说评价函数认为这个带判断的区域为可以视为一个高斯函数的拟合。
其次,对于第二次阈值判断时,是建立在第一次阈值的基础上的,但是如果是对阈值之前的灰度进行第二次阈值判断还是对阈值之后的灰度直方图进行第二次阈值判断,文章中采用的是根据图像的直方图直观感觉,这样的方式难以实现自动化的阈值判断实用性有待提高。
2.07.24.0版本
07.24.0目前完成编程实现,但是依然还是C语言的风格,接下来的任务把程序改写成类的形式。目前代码如下:
// thread1.cpp : 定义控制台应用程序的入口点。
//
#include <stdio.h>
#include <tchar.h>
#include <opencv2\opencv.hpp>
#include <highgui.h>
#include <cv.h>
#include <math.h>
using namespace std;
using namespace cv;
//定义一个直方图的类
class Histogram1D{
private:
int histSize[1]; //项的数目
float hranges[2]; //像素的最小值和最大值
const float * ranges[1];
int channels[1]; //仅用到一个通道
public:
Histogram1D(){
//准备1D直方图的参数
histSize[0] = 256;
hranges[0] = 0.0;
hranges[1] = 255.0;
ranges[0] = hranges;
channels[0] = 0; //默认情况下我们考察0通道。
}
//计算一个图像的直方图,并返回一个列表
MatND getHistogram(const Mat &image){
MatND hist;
calcHist(&image,
1, //计算单张图片的直方图
channels, //通道数量
Mat(), //不使用图像作为掩码
hist, //返回直方图
1, //这是1D的直方图
histSize, //项的数量
ranges //像素值的范围
);
return hist;
}
//计算1D直方图,并返回一幅图像
Mat getHistogramImage(const Mat &image, int minValue = 0){
//首先计算直方图
MatND 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));
//设置最高点为nbing的90%
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;
}
};
//matlab联调接口函数
//计算各灰度值出现的概率
void ComProArr(float probabilityGray[], //输出的各灰度值概率的数组
const MatND histout_arr, //直方图各灰度值出现概率
const int number, //计算灰度值范围
const int rows, //行数
const int cols, //列数
const int channels //通道数
){
float TotalPointNumber = rows*cols*channels;
float testsum = 0;
for (int i = 0; i < 256; i++)
{
probabilityGray[i] = histout_arr.at<float>(i) / TotalPointNumber;
// cout <<"proability: "<<i<<" "<< probabilityGray[i] << endl;
}
}
//根据输入阈值来计算两个前景和背景的概率
void ComProSum(float probabilityphase[],//输出概率存放数组地址
const int thread, //阈值相对位置
const float probabilityGray[], //各灰度级的概率
const int number //灰度级总数
){
if (thread > number)
{
cout << "阈值设置错误";
}
float total = 0.0; //用于计算实际灰度分布区域用灰度分割的概率的
float sumPro = 0.0; //用于计算实际灰度分布区域的总概率
for (int i =