画像ヒストグラム及び等化方法まとめ(二)適応ヒストグラム等化AHE,CLARHE
17256 ワード
にあるhttp://blog.csdn.net/piaoxuezhong/article/details/78269439本文では画像ヒストグラムとヒストグラム等化をまとめ,編幅の原因により,後の適応ヒストグラム等化部分を単独で本編でまとめた.
アダプティブヒストグラム等化(Adaptive histgram equalization/AHE)
ウィキペディアによれば、“It differs from ordinary histogram equalization in the respect that the adaptive method computes several histograms, each corresponding to a distinct section of the image, and uses them to redistribute the lightness values of the image. It is therefore suitable for improving the local contrast and enhancing the definitions of edges in each region of an image.”
すなわち、AHEアルゴリズムと古典的アルゴリズムとの相違点は、画像の複数の局所領域のヒストグラムを計算し、輝度を再分布することによって画像コントラストを変化させることである.従って,このアルゴリズムは画像の局所コントラストと詳細部分を向上させるのにより適している.しかし、AHEは画像中の相対的に均一な領域のノイズを過度に増幅する問題がある.
ヒストグラム等化古典アルゴリズムは,画像全体の画素に対して同じ変換を用い,画素値分布が比較的等化した画像に対して古典アルゴリズムの効果は良好である.しかし、画像に明らかに明るい領域または暗い領域が含まれている場合、これらの部分のコントラストは増強されません.
AHEアルゴリズムは,局所領域のヒストグラム等化により上記問題を解決する.最も単純な形式は,各画素がその周辺の矩形範囲内の画素のヒストグラムを等化することにより,等化の方式は通常の等化アルゴリズムを用い,変換関数は画素周辺の累積分布関数(CDF)に比例する.もちろん、ローカルヒストグラムは他の方法を採用することができ、ローカルヒストグラム処理には大体3つの実現方法がある:1)元のピクチャを重ならないサブブロックに分割し、各サブブロック内でヒストグラム処理を行い、この方法は簡単だが出力画像にはブロック効果がある.2)テンプレートの畳み込みのように、処理待ちの点を中心とし、その近傍をサブブロックとし、サブブロック内でヒストグラム処理を行い、処理結果をこの点にマッピングするだけでブロック効果を除去できるが、各点に対して1回のヒストグラム処理を計算する必要があり、効率が低い.3)前の2つの方法の組み合わせ版は、画素ごとに移動することなく、ステップ長がサブブロック幅よりも小さく、2つの隣接するサブブロックが重複していることを確保する.各サブブロックがヒストグラムマッピングされた結果は、サブブロック内のすべての点に値を付与し、各点に複数回の値が付与され、最終的な値はこれらの値の平均値となる.
AHEアルゴリズムには増幅ノイズなどの問題があるため,後の研究者らはコントラストを制限する適応ヒストグラム等化アルゴリズム(CLARHE)を発明したが,CLARHEについての説明や使用は多く見られるが,ここではできるだけ全面的にまとめる.
制限コントラスト適応ヒストグラム等化(Contrast Limited Adaptive histgram equalization/CLaHE)
CLARHEは,局所ヒストグラム等化(適応ヒストグラム等化AHEとも呼ばれる)に基づいて,サブブロック毎のヒストグラムを制限し,AHEによるノイズを良好に制御した.(後にウィキペディアの説明でレンガを運ぶ...)
CLARHEとAHEの違いは主にコントラストに対するリミットであり、CLARHEでは小さな領域ごとにコントラストリミットを使用してAHEの過度な増幅ノイズの問題を克服しなければならない.
アルゴリズムはAHEのコントラスト増強度を制限することによって効果を達成する.1つの画素値について,その周辺のコントラスト増幅は主に変換関数のスロープによって決定され,このスロープは領域蓄積分布関数のスロープに比例する.CDFを計算する前に、CLARHEは、あらかじめ定義されたしきい値でヒストグラムを切り取ることで、拡大倍数を制限する目的を達成する.このアルゴリズムの利点は,制限幅を超えた部分を直接無視するのではなく,これらの剪断された部分をヒストグラムの他の部分に均一に分布させることである.
計算速度の向上やブロック分割処理によるブロックエッジ遷移のアンバランスの除去などの問題のために,補間法を用いることができる.アルゴリズムの説明と説明については、記事を参照してください.アルゴリズム実装では、次のオプションを選択できます.
(1)WikipediaでCLAHEのC言語バージョンの実装が示されています.http://www.realtimerendering.com/resources/GraphicsGems/gemsiv/clahe.c
(2)matlabが与える関数adapthisteqは,ソースコードを見たい場合はmatlabでctrl+Dを見ることができ,ここでは添付しない.例を示します.
もちろん、Lab空間で操作することをお勧めしますが、具体的な実装は書かないで、空間を変換すればいいです.
(3)opencvにもCLAHEの実装があり、ディレクトリは:..\opencv\sources\modules\imgproc\src\clahe.cpp
ソースコードを添付します.http://blog.csdn.net/panda1234lee/article/details/52852765
step1. 画像境界を拡張して、いくつかのサブブロックに分割できるようにし、各サブブロック面積がtileSizeTotal、サブブロック係数lutScale=255.0/tileSizeTotalと仮定し、予め設定されたlimit=MAX(1,limit*tileSizeTotal/256)を処理する.step2. サブブロックごとにヒストグラムを計算します.step3. 各サブブロックヒストグラムの各階調レベルについて、予め設定されたlimit値を用いて限定し、同時にヒストグラム全体がlimitを超える画素数を統計する.step4. 各サブブロックのlut累積ヒストグラムを計算するtileLut,tileLut[i]=sum[i]*lutScale,sum[i]は累積ヒストグラムであり、lutScaleはtileLutの値が[0,255]であることを保証する.step5. 元の画像の各点を巡り、その点があるサブブロックと右、下、右下の4つのサブブロックのtileLutを考慮し、元の階調値をインデックスとして4つの値を得、その後、この点変換後の階調値に二重線形を挿入する.次にopencvを使用してカラー画像のCLARHEを実装し、プログラムでRGB空間からLab空間に画像を変換することに注意します.
CLARHEアルゴリズムの応用
現在,CLARHEアルゴリズムは画像処理の分野で多くの応用があり,典型的には画像デミスト,水中画像処理,低照度画像処理などがある.もちろん実際の応用に具体的には,アルゴリズムはパラメータ調整と最適化が必要である.
リファレンス
https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE
http://blog.csdn.net/u010839382/article/details/49584181
http://blog.csdn.net/fightingforcv/article/details/47661463
http://blog.csdn.net/linear_luo/article/details/52527563
http://blog.csdn.net/baimafujinji/article/details/50660189
http://www.cnblogs.com/Imageshop/archive/2013/04/07/3006334.html
アダプティブヒストグラム等化(Adaptive histgram equalization/AHE)
ウィキペディアによれば、“It differs from ordinary histogram equalization in the respect that the adaptive method computes several histograms, each corresponding to a distinct section of the image, and uses them to redistribute the lightness values of the image. It is therefore suitable for improving the local contrast and enhancing the definitions of edges in each region of an image.”
すなわち、AHEアルゴリズムと古典的アルゴリズムとの相違点は、画像の複数の局所領域のヒストグラムを計算し、輝度を再分布することによって画像コントラストを変化させることである.従って,このアルゴリズムは画像の局所コントラストと詳細部分を向上させるのにより適している.しかし、AHEは画像中の相対的に均一な領域のノイズを過度に増幅する問題がある.
ヒストグラム等化古典アルゴリズムは,画像全体の画素に対して同じ変換を用い,画素値分布が比較的等化した画像に対して古典アルゴリズムの効果は良好である.しかし、画像に明らかに明るい領域または暗い領域が含まれている場合、これらの部分のコントラストは増強されません.
AHEアルゴリズムは,局所領域のヒストグラム等化により上記問題を解決する.最も単純な形式は,各画素がその周辺の矩形範囲内の画素のヒストグラムを等化することにより,等化の方式は通常の等化アルゴリズムを用い,変換関数は画素周辺の累積分布関数(CDF)に比例する.もちろん、ローカルヒストグラムは他の方法を採用することができ、ローカルヒストグラム処理には大体3つの実現方法がある:1)元のピクチャを重ならないサブブロックに分割し、各サブブロック内でヒストグラム処理を行い、この方法は簡単だが出力画像にはブロック効果がある.2)テンプレートの畳み込みのように、処理待ちの点を中心とし、その近傍をサブブロックとし、サブブロック内でヒストグラム処理を行い、処理結果をこの点にマッピングするだけでブロック効果を除去できるが、各点に対して1回のヒストグラム処理を計算する必要があり、効率が低い.3)前の2つの方法の組み合わせ版は、画素ごとに移動することなく、ステップ長がサブブロック幅よりも小さく、2つの隣接するサブブロックが重複していることを確保する.各サブブロックがヒストグラムマッピングされた結果は、サブブロック内のすべての点に値を付与し、各点に複数回の値が付与され、最終的な値はこれらの値の平均値となる.
AHEアルゴリズムには増幅ノイズなどの問題があるため,後の研究者らはコントラストを制限する適応ヒストグラム等化アルゴリズム(CLARHE)を発明したが,CLARHEについての説明や使用は多く見られるが,ここではできるだけ全面的にまとめる.
制限コントラスト適応ヒストグラム等化(Contrast Limited Adaptive histgram equalization/CLaHE)
CLARHEは,局所ヒストグラム等化(適応ヒストグラム等化AHEとも呼ばれる)に基づいて,サブブロック毎のヒストグラムを制限し,AHEによるノイズを良好に制御した.(後にウィキペディアの説明でレンガを運ぶ...)
CLARHEとAHEの違いは主にコントラストに対するリミットであり、CLARHEでは小さな領域ごとにコントラストリミットを使用してAHEの過度な増幅ノイズの問題を克服しなければならない.
アルゴリズムはAHEのコントラスト増強度を制限することによって効果を達成する.1つの画素値について,その周辺のコントラスト増幅は主に変換関数のスロープによって決定され,このスロープは領域蓄積分布関数のスロープに比例する.CDFを計算する前に、CLARHEは、あらかじめ定義されたしきい値でヒストグラムを切り取ることで、拡大倍数を制限する目的を達成する.このアルゴリズムの利点は,制限幅を超えた部分を直接無視するのではなく,これらの剪断された部分をヒストグラムの他の部分に均一に分布させることである.
計算速度の向上やブロック分割処理によるブロックエッジ遷移のアンバランスの除去などの問題のために,補間法を用いることができる.アルゴリズムの説明と説明については、記事を参照してください.アルゴリズム実装では、次のオプションを選択できます.
(1)WikipediaでCLAHEのC言語バージョンの実装が示されています.http://www.realtimerendering.com/resources/GraphicsGems/gemsiv/clahe.c
(2)matlabが与える関数adapthisteqは,ソースコードを見たい場合はmatlabでctrl+Dを見ることができ,ここでは添付しない.例を示します.
clear all,close all,clc;
img = imread('../image/7.jpg');
figure,
subplot(1,2,1),imshow(img);title(' ');
if length(size(img))>2
rimg = img(:,:,1);
gimg = img(:,:,2);
bimg = img(:,:,3);
resultr = adapthisteq(rimg);
resultg = adapthisteq(gimg);
resultb = adapthisteq(bimg);
result = cat(3, resultr, resultg, resultb);
subplot(1,2,2),imshow(result);title('CLAHE ');
end
もちろん、Lab空間で操作することをお勧めしますが、具体的な実装は書かないで、空間を変換すればいいです.
(3)opencvにもCLAHEの実装があり、ディレクトリは:..\opencv\sources\modules\imgproc\src\clahe.cpp
ソースコードを添付します.http://blog.csdn.net/panda1234lee/article/details/52852765
// CLAHE
namespace
{
class CLAHE_CalcLut_Body : public cv::ParallelLoopBody
{
public:
CLAHE_CalcLut_Body(const cv::Mat& src, cv::Mat& lut, cv::Size tileSize, int tilesX, int clipLimit, float lutScale) :
src_(src), lut_(lut), tileSize_(tileSize), tilesX_(tilesX), clipLimit_(clipLimit), lutScale_(lutScale)
{
}
void operator ()(const cv::Range& range) const;
private:
cv::Mat src_;
mutable cv::Mat lut_;
cv::Size tileSize_;
int tilesX_;
int clipLimit_;
float lutScale_;
};
//
void CLAHE_CalcLut_Body::operator ()(const cv::Range& range) const
{
const int histSize = 256;
uchar* tileLut = lut_.ptr(range.start);
const size_t lut_step = lut_.step; // size = tilesX_*tilesY_ * lut_step
// Range(0, tilesX_ * tilesY_), tilesX_*tiles_Y
for (int k = range.start; k < range.end; ++k, tileLut += lut_step)
{
// (tx, ty)
// (0, 0) (1, 0)...(tilesX_-1, 0)
// (0, 1) (1, 1)...(tilesX_-1, 1)
// ...
// (0, tilesY_-1)... (tilesX_-1, tilesY_-1)
const int ty = k / tilesX_;
const int tx = k % tilesX_;
// retrieve tile submatrix
// :tileSize.width ,tileSize.height
cv::Rect tileROI;
tileROI.x = tx * tileSize_.width; //
tileROI.y = ty * tileSize_.height;
tileROI.width = tileSize_.width;
tileROI.height = tileSize_.height;
const cv::Mat tile = src_(tileROI);
// calc histogram
int tileHist[histSize] = { 0, };
// ROI
int height = tileROI.height;
const size_t sstep = tile.step;
for (const uchar* ptr = tile.ptr(0); height--; ptr += sstep)
{
int x = 0;
for (; x <= tileROI.width - 4; x += 4)
{
int t0 = ptr[x], t1 = ptr[x + 1];
tileHist[t0]++; tileHist[t1]++;
t0 = ptr[x + 2]; t1 = ptr[x + 3];
tileHist[t0]++; tileHist[t1]++;
}
for (; x < tileROI.width; ++x)
tileHist[ptr[x]]++;
}
// clip histogram
if (clipLimit_ > 0)
{
// how many pixels were clipped
int clipped = 0;
for (int i = 0; i < histSize; ++i)
{
//
if (tileHist[i] > clipLimit_)
{
clipped += tileHist[i] - clipLimit_;
tileHist[i] = clipLimit_;
}
}
// redistribute clipped pixels
int redistBatch = clipped / histSize;
int residual = clipped - redistBatch * histSize;
//
for (int i = 0; i < histSize; ++i)
tileHist[i] += redistBatch;
//
for (int i = 0; i < residual; ++i)
tileHist[i]++;
}
// calc Lut
int sum = 0;
for (int i = 0; i < histSize; ++i)
{
//
sum += tileHist[i];
tileLut[i] = cv::saturate_cast(sum * lutScale_); // static_cast(histSize - 1) / tileSizeTotal
}
}
}
class CLAHE_Interpolation_Body : public cv::ParallelLoopBody
{
public:
CLAHE_Interpolation_Body(const cv::Mat& src, cv::Mat& dst, const cv::Mat& lut, cv::Size tileSize, int tilesX, int tilesY) :
src_(src), dst_(dst), lut_(lut), tileSize_(tileSize), tilesX_(tilesX), tilesY_(tilesY)
{
}
void operator ()(const cv::Range& range) const;
private:
cv::Mat src_;
mutable cv::Mat dst_;
cv::Mat lut_;
cv::Size tileSize_;
int tilesX_;
int tilesY_;
};
// 4
void CLAHE_Interpolation_Body::operator ()(const cv::Range& range) const
{
const size_t lut_step = lut_.step;
// Range(0, src.rows)
for (int y = range.start; y < range.end; ++y)
{
const uchar* srcRow = src_.ptr(y);
uchar* dstRow = dst_.ptr(y);
const float tyf = (static_cast(y) / tileSize_.height) - 0.5f;
int ty1 = cvFloor(tyf);
int ty2 = ty1 + 1;
//
const float ya = tyf - ty1;
ty1 = std::max(ty1, 0);
ty2 = std::min(ty2, tilesY_ - 1);
const uchar* lutPlane1 = lut_.ptr(ty1 * tilesX_); //
const uchar* lutPlane2 = lut_.ptr(ty2 * tilesX_); //
for (int x = 0; x < src_.cols; ++x)
{
const float txf = (static_cast(x) / tileSize_.width) - 0.5f;
int tx1 = cvFloor(txf);
int tx2 = tx1 + 1;
//
const float xa = txf - tx1;
tx1 = std::max(tx1, 0);
tx2 = std::min(tx2, tilesX_ - 1);
// src_.ptr(y)[x]
const int srcVal = srcRow[x];
// LUT
const size_t ind1 = tx1 * lut_step + srcVal;
const size_t ind2 = tx2 * lut_step + srcVal; //
float res = 0;
//
// lut_.ptr(ty1 * tilesX_)[tx1 * lut_step + srcVa] => lut_[ty1][tx1][srcVal]
res += lutPlane1[ind1] * ((1.0f - xa) * (1.0f - ya));
res += lutPlane1[ind2] * ((xa) * (1.0f - ya));
res += lutPlane2[ind1] * ((1.0f - xa) * (ya));
res += lutPlane2[ind2] * ((xa) * (ya));
dstRow[x] = cv::saturate_cast(res);
}
}
}
class CLAHE_Impl : public cv::CLAHE
{
public:
CLAHE_Impl(double clipLimit = 40.0, int tilesX = 8, int tilesY = 8);
cv::AlgorithmInfo* info() const; // Algorithm
void apply(cv::InputArray src, cv::OutputArray dst);
void setClipLimit(double clipLimit);
double getClipLimit() const;
void setTilesGridSize(cv::Size tileGridSize);
cv::Size getTilesGridSize() const;
void collectGarbage();
private:
double clipLimit_;
int tilesX_;
int tilesY_;
cv::Mat srcExt_;
cv::Mat lut_;
};
CLAHE_Impl::CLAHE_Impl(double clipLimit, int tilesX, int tilesY) :
clipLimit_(clipLimit), tilesX_(tilesX), tilesY_(tilesY)
{
}
// Algorithm
//CV_INIT_ALGORITHM(CLAHE_Impl, "CLAHE",
// obj.info()->addParam(obj, "clipLimit", obj.clipLimit_);
//obj.info()->addParam(obj, "tilesX", obj.tilesX_);
//obj.info()->addParam(obj, "tilesY", obj.tilesY_))
void CLAHE_Impl::apply(cv::InputArray _src, cv::OutputArray _dst)
{
cv::Mat src = _src.getMat();
CV_Assert(src.type() == CV_8UC1);
_dst.create(src.size(), src.type());
cv::Mat dst = _dst.getMat();
const int histSize = 256;
// LUT,tilesX_*tilesY_ , 256
lut_.create(tilesX_ * tilesY_, histSize, CV_8UC1);
cv::Size tileSize;
cv::Mat srcForLut;
// ( )
if (src.cols % tilesX_ == 0 && src.rows % tilesY_ == 0)
{
tileSize = cv::Size(src.cols / tilesX_, src.rows / tilesY_);
srcForLut = src;
}
//
else
{
cv::copyMakeBorder(src, srcExt_, 0, tilesY_ - (src.rows % tilesY_), 0, tilesX_ - (src.cols % tilesX_), cv::BORDER_REFLECT_101);
tileSize = cv::Size(srcExt_.cols / tilesX_, srcExt_.rows / tilesY_);
srcForLut = srcExt_;
}
const int tileSizeTotal = tileSize.area();
const float lutScale = static_cast(histSize - 1) / tileSizeTotal; // △
// clipLimit
int clipLimit = 0;
if (clipLimit_ > 0.0)
{
clipLimit = static_cast(clipLimit_ * tileSizeTotal / histSize);
clipLimit = std::max(clipLimit, 1);
}
// : LUT
CLAHE_CalcLut_Body calcLutBody(srcForLut, lut_, tileSize, tilesX_, clipLimit, lutScale);
cv::parallel_for_(cv::Range(0, tilesX_ * tilesY_), calcLutBody);
// :
CLAHE_Interpolation_Body interpolationBody(src, dst, lut_, tileSize, tilesX_, tilesY_);
cv::parallel_for_(cv::Range(0, src.rows), interpolationBody);
}
void CLAHE_Impl::setClipLimit(double clipLimit)
{
clipLimit_ = clipLimit;
}
double CLAHE_Impl::getClipLimit() const
{
return clipLimit_;
}
void CLAHE_Impl::setTilesGridSize(cv::Size tileGridSize)
{
tilesX_ = tileGridSize.width;
tilesY_ = tileGridSize.height;
}
cv::Size CLAHE_Impl::getTilesGridSize() const
{
return cv::Size(tilesX_, tilesY_);
}
void CLAHE_Impl::collectGarbage()
{
srcExt_.release();
lut_.release();
}
}
cv::Ptr<:clahe> cv::createCLAHE(double clipLimit, cv::Size tileGridSize)
{
return new CLAHE_Impl(clipLimit, tileGridSize.width, tileGridSize.height);
}
opencv CLARHEを実装する主な手順は、次のように分けられます.step1. 画像境界を拡張して、いくつかのサブブロックに分割できるようにし、各サブブロック面積がtileSizeTotal、サブブロック係数lutScale=255.0/tileSizeTotalと仮定し、予め設定されたlimit=MAX(1,limit*tileSizeTotal/256)を処理する.step2. サブブロックごとにヒストグラムを計算します.step3. 各サブブロックヒストグラムの各階調レベルについて、予め設定されたlimit値を用いて限定し、同時にヒストグラム全体がlimitを超える画素数を統計する.step4. 各サブブロックのlut累積ヒストグラムを計算するtileLut,tileLut[i]=sum[i]*lutScale,sum[i]は累積ヒストグラムであり、lutScaleはtileLutの値が[0,255]であることを保証する.step5. 元の画像の各点を巡り、その点があるサブブロックと右、下、右下の4つのサブブロックのtileLutを考慮し、元の階調値をインデックスとして4つの値を得、その後、この点変換後の階調値に二重線形を挿入する.次にopencvを使用してカラー画像のCLARHEを実装し、プログラムでRGB空間からLab空間に画像を変換することに注意します.
#include "opencv2/opencv.hpp"
#include
using namespace cv;
using namespace std;
void main()
{
Mat inp_img = cv::imread("D:\\proMatlab\\vessel_edge_extration\\image\\7.jpg");
if (!inp_img.data) {
cout << "Something Wrong";
}
namedWindow("Input Image", CV_WINDOW_AUTOSIZE);
imshow("Input Image", inp_img);
Mat clahe_img;
cvtColor(inp_img, clahe_img, CV_BGR2Lab);
vector<:mat> channels(3);
split(clahe_img, channels);
Ptr<:clahe> clahe = createCLAHE();
// ClipLimit ,
clahe->setClipLimit(4.); // (int)(4.*(8*8)/256)
//clahe->setTilesGridSize(Size(8, 8)); // 8*8
Mat dst;
clahe->apply(channels[0], dst);
dst.copyTo(channels[0]);
//
clahe->apply(channels[1], dst);
dst.copyTo(channels[1]);
clahe->apply(channels[2], dst);
dst.copyTo(channels[2]);
merge(channels, clahe_img);
Mat image_clahe;
cvtColor(clahe_img, image_clahe, CV_Lab2BGR);
namedWindow("CLAHE Image", CV_WINDOW_AUTOSIZE);
imshow("CLAHE Image", image_clahe);
imwrite("out.jpg", image_clahe);
waitKey();
system("pause");
}
元の図はmatlabで処理されたものと同じで、ここでは処理後の結果のみが添付されています.CLARHEアルゴリズムの応用
現在,CLARHEアルゴリズムは画像処理の分野で多くの応用があり,典型的には画像デミスト,水中画像処理,低照度画像処理などがある.もちろん実際の応用に具体的には,アルゴリズムはパラメータ調整と最適化が必要である.
リファレンス
https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE
http://blog.csdn.net/u010839382/article/details/49584181
http://blog.csdn.net/fightingforcv/article/details/47661463
http://blog.csdn.net/linear_luo/article/details/52527563
http://blog.csdn.net/baimafujinji/article/details/50660189
http://www.cnblogs.com/Imageshop/archive/2013/04/07/3006334.html