[w5d5] Histogram Stretch, Filtering


(Ubuntu 18.04.6 LTS)
2022.03.18.
C++、VSコードを使用
プログラマー自主走行Defcos 3期

histogram stretch


従来の対照は,特定の値を基準として分布する形で与えられた.その値の多くは、平均値または輝度の中間値128である.
dst = src + (src - m) * alpha
dst = src + (128 - m)
ヒストグラムストレッチのヒストグラム上の分布は、最大値と最小値を用いて輝度を調節する.

適用される式は次のとおりです.
dst = 255/(G_max - G_min) * (src - G_min)
OpenCVで最大最小値を求める関数はcv::minmaxLocである.
void cv::minMaxLoc 	( 	InputArray  	src,
		double *  	minVal,
		double *  	maxVal = 0,
		Point *  	minLoc = 0,
		Point *  	maxLoc = 0,
		InputArray  	mask = noArray() 
	) 		
minVal、maxValでは、各値を含む2変数のアドレスを入力するだけです.
ポイントを追加すると、その位置をポイントとして取得できます.
cv::normalizeを使用してヒストグラムストレッチを実行することもできます.
void cv::normalize 	( 	InputArray  	src,
		InputOutputArray  	dst,
		double  	alpha = 1,
		double  	beta = 0,
		int  	norm_type = NORM_L2,
		int  	dtype = -1,
		InputArray  	mask = noArray() 
	) 		
norm typeをNORM MINMAXに設定すると、alphaを最小値、betaを最大値として使用できます.
// 예시 코드
#include <iostream>
#include "opencv2/opencv.hpp"

int main()
{
    cv::Mat src = cv::imread("./resources/lenna.bmp",cv::IMREAD_COLOR);

    if (src.empty()){
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }
    double gmin,gmax;
    cv::minMaxLoc(src,&gmin,&gmax);
    cv::Mat dst = (src-gmin)*255/(gmax-gmin);

    cv::Mat dst2,src2;
    cv::cvtColor(src,src2,cv::COLOR_BGR2GRAY);
    cv::normalize(src2,dst2,0,255,cv::NORM_MINMAX);

    cv::imshow("image",src);
    cv::imshow("dst",dst);
    cv::imshow("image2",src2);
    cv::imshow("dst2",dst2);

    cv::waitKey();
    cv::destroyAllWindows();
    return 0;
}
しかし、最大値、最小値周辺の画素数が少ない場合やノイズが発生する場合、単純な最大最小値は実現しにくい.一定の割合を超えると、その値に基づいてヒストグラムstretchを実行できます.コードは以下の通りです.
#include "opencv2/opencv.hpp"
#include <iostream>

void histogram_stretching(const cv::Mat& src, cv::Mat& dst);
void histogram_stretching_mod(const cv::Mat& src, cv::Mat& dst);

int main()
{
	cv::Mat src = cv::imread("./resources/lenna.bmp", cv::IMREAD_GRAYSCALE);

	if (src.empty()) {
		std::cerr << "Image load failed!" << std::endl;
		return -1;
	}

	cv::Mat dst1, dst2;
	histogram_stretching(src, dst1);
	histogram_stretching_mod(src, dst2);

	cv::imshow("src", src);
	cv::imshow("dst1", dst1);
	cv::imshow("dst2", dst2);
	cv::waitKey();
}

void histogram_stretching(const cv::Mat& src, cv::Mat& dst)
{
	double gmin, gmax;
	cv::minMaxLoc(src, &gmin, &gmax);

	dst = (src - gmin) * 255 / (gmax - gmin);
}

void histogram_stretching_mod(const cv::Mat& src, cv::Mat& dst)
{
	int hist[256] = {0,};

	for(int y=0;y<src.rows;y++){
		for(int x=0;x<src.cols;x++){
			hist[src.at<uchar>(cv::Point(y,x))]+=1;
		}
	}

	int gmin, gmax;
	int ratio = int(src.cols * src.rows * 0.01);
	
	for (int i = 0, s = 0; i < 255; i++) {
		s += hist[i];
		if (s>=ratio){
			gmin = i;
			break;
		}
	}

	for (int i = 255, s = 0; i >= 0; i--) {
		s += hist[i];
		if (s>=ratio){
			gmax = i;
			break;
		}
	}

	dst = (src-gmin)/(gmax-gmin)*255;
}

一番左は既存の写真で、2番目は普通のヒストグラムで、3番目は1%のヒストグラムで、よりはっきりした画像が見えます.

histogram equalization


ヒストグラム等化は、区間全体にわたって均一な分布の形で現れる技術であり、ヒストグラムを求め、正規化した後に累積分布関数を求め、最大値に四捨五入を乗じる.以下のウィキサイトの内容を確認し、記入しました.
https://en.wikipedia.org/wiki/Histogram_equalization

画像のコントラスト値をテキストファイルに挿入します.その後Pythonを利用して個数を求めます.
# python 3
f = open("counti.txt",'r')
hist = [0]*256
while True:
    line = f.readline()
    if not line: break
    hist[int(line)]+=1
hist_sum = float(sum(hist))
cdf = 0
for idx, count in enumerate(hist):
    if (count!=0):
        norm_hist=count/hist_sum
        cdf += norm_hist
        dist_val = int(round(cdf*255,0))
        print(f'{idx:3}: count: {count}, norm_hist = {norm_hist:.3f}, cdf = {cdf:.3f}, dst_val = {dist_val}')
f.close
結果値は以下のとおりです.countは個数であり,norm histは総数で割った値であり,cdfは累積分布関数である.255の範囲内で滑らかにするために、cdfを255に乗じて四捨五入する.
 52: count: 1, norm_hist = 0.016, cdf = 0.016, dst_val = 4
 55: count: 3, norm_hist = 0.047, cdf = 0.062, dst_val = 16
 58: count: 2, norm_hist = 0.031, cdf = 0.094, dst_val = 24
 59: count: 3, norm_hist = 0.047, cdf = 0.141, dst_val = 36
 60: count: 1, norm_hist = 0.016, cdf = 0.156, dst_val = 40
 61: count: 4, norm_hist = 0.062, cdf = 0.219, dst_val = 56
 62: count: 1, norm_hist = 0.016, cdf = 0.234, dst_val = 60
 63: count: 2, norm_hist = 0.031, cdf = 0.266, dst_val = 68
 64: count: 2, norm_hist = 0.031, cdf = 0.297, dst_val = 76
 65: count: 3, norm_hist = 0.047, cdf = 0.344, dst_val = 88
 66: count: 2, norm_hist = 0.031, cdf = 0.375, dst_val = 96
 67: count: 1, norm_hist = 0.016, cdf = 0.391, dst_val = 100
 68: count: 5, norm_hist = 0.078, cdf = 0.469, dst_val = 120
 69: count: 3, norm_hist = 0.047, cdf = 0.516, dst_val = 131
 70: count: 4, norm_hist = 0.062, cdf = 0.578, dst_val = 147
 71: count: 2, norm_hist = 0.031, cdf = 0.609, dst_val = 155
 72: count: 1, norm_hist = 0.016, cdf = 0.625, dst_val = 159
 73: count: 2, norm_hist = 0.031, cdf = 0.656, dst_val = 167
 75: count: 1, norm_hist = 0.016, cdf = 0.672, dst_val = 171
 76: count: 1, norm_hist = 0.016, cdf = 0.688, dst_val = 175
 77: count: 1, norm_hist = 0.016, cdf = 0.703, dst_val = 179
 78: count: 1, norm_hist = 0.016, cdf = 0.719, dst_val = 183
 79: count: 2, norm_hist = 0.031, cdf = 0.750, dst_val = 191
 83: count: 1, norm_hist = 0.016, cdf = 0.766, dst_val = 195
 85: count: 2, norm_hist = 0.031, cdf = 0.797, dst_val = 203
 87: count: 1, norm_hist = 0.016, cdf = 0.812, dst_val = 207
 88: count: 1, norm_hist = 0.016, cdf = 0.828, dst_val = 211
 90: count: 1, norm_hist = 0.016, cdf = 0.844, dst_val = 215
 94: count: 1, norm_hist = 0.016, cdf = 0.859, dst_val = 219
104: count: 2, norm_hist = 0.031, cdf = 0.891, dst_val = 227
106: count: 1, norm_hist = 0.016, cdf = 0.906, dst_val = 231
109: count: 1, norm_hist = 0.016, cdf = 0.922, dst_val = 235
113: count: 1, norm_hist = 0.016, cdf = 0.938, dst_val = 239
122: count: 1, norm_hist = 0.016, cdf = 0.953, dst_val = 243
126: count: 1, norm_hist = 0.016, cdf = 0.969, dst_val = 247
144: count: 1, norm_hist = 0.016, cdf = 0.984, dst_val = 251
154: count: 1, norm_hist = 0.016, cdf = 1.000, dst_val = 255
各値に基づいてテーブルが作成されたと解釈することができ、例えば、元の144値は、平滑化後に251を付与することができる.変更すると輝度値が均等に分布します.


filtering


フィルタリングは、必要な情報のみを用いる方法であり、空間フィルタリングを処理する画像処理において、mask、kernelと呼ばれるマトリクスを用いてぼかし、エッジ検出などのフィルタリングを行う.OpenCVでは、cv::filter 2 D関数を使用できます.
void cv::filter2D 	( 	InputArray  	src,
		OutputArray  	dst,
		int  	ddepth,
		InputArray  	kernel,
		Point  	anchor = Point(-1,-1),
		double  	delta = 0,
		int  	borderType = BORDER_DEFAULT 
	) 	
ddepthはデータの深さを表し、-1は処理がソースと同じであることを示す.種類は下記のリンクでご覧いただけます.
https://docs.opencv.org/3.4/d4/d86/group__imgproc__filter.html#filter_depths
kernelはフィルタリング用のマトリクスです.
アンカーはアンカー位置であり、ポイント(-1,-1)の場合はフィルタの中心をアンカーとして使用します.
deltaは追加する値で、borderTypeはエッジの処理方法を決定します.borderタイプに関する情報は、以下でさらに参照できます.
https://docs.opencv.org/3.4/d2/de8/group__core__array.html#gga209f2f4869e304c82d07739337eae7c5a697c1b011884a7c2bdc0e5caf7955661
// 예제 코드: 엠보싱.
#include <iostream>
#include "opencv2/opencv.hpp"

int main()
{
    cv::Mat src = cv::imread("./resources/lenna.bmp",cv::IMREAD_GRAYSCALE);
    float data[] = {-1,-1,0,-1,0,1,1,1,1};
    cv::Mat kernel(3,3,CV_32FC1,data);
    cv::Mat dst;
    if (src.empty()){
        std::cerr << "Image load failed!" << std::endl;
        return -1;
    }
    cv::filter2D(src,dst,-1,kernel);

    cv::imshow("image",src);
    cv::imshow("dst",dst);
    cv::waitKey();
    cv::destroyAllWindows();
    return 0;
}
サンプルコードは、エンボスを実装するために作成されたフィルタマスクであり、エンボスに関する情報は次のリンクで参照できます.
https://en.wikipedia.org/wiki/Image_embossing
コード実行結果は以下の通りです.