記事ディレクトリ
ヒストグラムとは
ヒストグラムは、一般的に使用されるデータの統計グラフです。特定の物理量または特徴量の最大値と最小値を求め、すべての測定データを含める区間を決定し、その区間をいくつかの小さな区間に分割し、それぞれの小さな区間に現れる測定結果の頻度または割合をカウントします。 , 測定データを横軸、頻度または割合を縦軸にとり、各小領域の間隔とそれに対応する頻度または割合の高さを描くと、長方形の図、つまり統計的ヒストグラムが得られます。
ヒストグラム統計
ヒストグラムに分割された小さな間隔の数はグループの数と呼ばれ、ビンで表され、各グループの 2 つの端点間の横軸の差はグループ距離と呼ばれます。ヒストグラムが表すデータの最小値と最大値の範囲をヒストグラムの範囲と呼びます。
すべてのグループのグループ距離が同じ場合、ヒストグラムは均一ヒストグラムと呼ばれ、そうでない場合は不均一ヒストグラムと呼ばれます。
物理量や物体の特性値に基づくヒストグラムは 1 次元のヒストグラムですが、実際には複数の物理量や特性値に基づいて多次元のヒストグラムを作成することもできます。たとえば、人口データでは、人口分布ヒストグラムが作成されます。年齢 + 教育年数に基づくグラフは 2 次元ヒストグラムです。物理量または特徴量の数はヒストグラムの次元と呼ばれ、ディムで表されます。
画像ヒストグラム
画像処理において、ヒストグラムの横軸は画像の特定チャンネルの画素値データからなる設定値、縦軸は各値の個数または比率をとり、その結果得られるヒストグラムが画像ヒストグラム(画像ヒストグラム)となります。ヒストグラム)。
実際には、画像のピクセル値に基づいてヒストグラムが作成されるだけでなく、画像の勾配や各ピクセルの角度など、すべての画像属性値に対してヒストグラムを作成することもできます。ただし、画像処理では画像のピクセル値に基づくヒストグラムが最も一般的です。一般に、画像のヒストグラムは 1 次元のヒストグラムです。
画像のヒストグラムは、移動、回転、拡大縮小に対して不変です。平行移動画像は画像角度を回転させても画像操作前後のヒストグラムの分布が変化せず、ズーム画像も画像操作前後のヒストグラムの分布が基本的に変化しないため、画像が暗いかどうかは、対応する画像のヒストグラムによって判断されます。明るいまたは通常の光では、画像ヒストグラムの横軸の左側は真っ黒で暗い領域になり、右側はより明るく真っ白な領域になります。したがって、暗い画像のヒストグラムのデータは左側と中央の部分に集中しますが、影がほとんどない全体的に明るい画像はその逆になります。以下に示すように:
OpenCVのヒストグラム描画
一般的な8bit画像については、opencv関数を直接利用して処理することができますので、詳しくはこのブログを参照してください。以下にコードを直接貼り付けます。
3 チャネル画像のヒストグラム プロット:
Mat histogram_demo(Mat &image)
{
/*图像直方图是图像像素值的统计学特征,计算代价较小,具有图像的平移、旋转、缩放不变性的优点。
Bins是指直方图的大小范围
*/
//三通道分离
std::vector<Mat>bgr_plane;
split(image, bgr_plane);
//定义参数变量
const int channels[1] = {
0 };
const int bins[1] = {
256 };//一共有256个灰度级别
float hranges[2] = {
0,255 };//每个通道的灰度级别是0-255
const float* ranges[1] = {
hranges };
Mat b_hist;
Mat g_hist;
Mat r_hist;
//计算Blue、Green、Red通道的直方图,1表示只有一张图,因为可以支持多张图多个通道;0表示只有1个通道;raanges就是直方图的取值范围0-25
calcHist(&bgr_plane[0], 1, 0, Mat(), b_hist, 1, bins, ranges);
calcHist(&bgr_plane[1], 1, 0, Mat(), g_hist, 1, bins, ranges);
calcHist(&bgr_plane[2], 1, 0, Mat(), r_hist, 1, bins, ranges);
//显示直方图
int hist_w = 512;
int hist_h = 400;
int bin_w = cvRound((double)hist_w / bins[0]);
Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
//归一化直方图数据
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
//绘制直方图曲线
for (int i = 1; i < bins[0]; i++) {
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
Point(bin_w * (i), hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0);
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
Point(bin_w * (i), hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, 8, 0);
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
Point(bin_w * (i), hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, 8, 0);
}
//显示直方图
//namedWindow("Histogram Demo", WINDOW_AUTOSIZE);
//imshow("Histogram Demo", histImage);
return histImage.clone();
}
加工効果は以下の通りです。
グレースケール画像のヒストグラムプロット:
Mat histogram_grayImage(const Mat& image)
{
//定义求直方图的通道数目,从0开始索引
int channels[] = {
0 };
//定义直方图的在每一维上的大小,例如灰度图直方图的横坐标是图像的灰度值,就一维,bin的个数
//如果直方图图像横坐标bin个数为x,纵坐标bin个数为y,则channels[]={1,2}其直方图应该为三维的,Z轴是每个bin上统计的数目
const int histSize[] = {
256 };
//每一维bin的变化范围
float range[] = {
0,256 };
//所有bin的变化范围,个数跟channels应该跟channels一致
const float* ranges[] = {
range };
//定义直方图,这里求的是直方图数据
Mat hist;
//opencv中计算直方图的函数,hist大小为256*1,每行存储的统计的该行对应的灰度值的个数
calcHist(&image, 1, channels, Mat(), hist, 1, histSize, ranges, true, false);
//找出直方图统计的个数的最大值,用来作为直方图纵坐标的高
double maxValue = 0;
//找矩阵中最大最小值及对应索引的函数
minMaxLoc(hist, 0, &maxValue, 0, 0);
//最大值取整
int rows = cvRound(maxValue);
//定义直方图图像,直方图纵坐标的高作为行数,列数为256(灰度值的个数)
//因为是直方图的图像,所以以黑白两色为区分,白色为直方图的图像
Mat histImage = Mat::zeros(rows, 256, CV_8UC1);
//直方图图像表示
for (int i = 0; i < 256; i++)
{
//取每个bin的数目
int temp = (int)(hist.at<float>(i, 0));
//如果bin数目为0,则说明图像上没有该灰度值,则整列为黑色
//如果图像上有该灰度值,则将该列对应个数的像素设为白色
if (temp)
{
//由于图像坐标是以左上角为原点,所以要进行变换,使直方图图像以左下角为坐标原点
histImage.col(i).rowRange(Range(rows - temp, rows)) = 255;
}
}
//由于直方图图像列高可能很高,因此进行图像对列要进行对应的缩减,使直方图图像更直观
Mat resizeImage;
resize(histImage, resizeImage, Size(256, 256));
return resizeImage;
}
グレースケール化後の同じ画像のヒストグラム統計:
カスタム描画ヒストグラム
void DrawHist(void* srcData, int *m_dim)
{
int iWidth = m_dim[0];
int iHeight = m_dim[1];
//histgram
int min = 0;
int max = 8000;
int bin = 200;
int internal = (max - min) / bin;
MatND dstHist;
Mat bins = GetBins(internal, 0, bin);
dstHist = CalcGrayHist(bin, internal, (signed short*)srcData, iWidth, iHeight, 1);
//get soft value
Mat histMaxValue = GetMaxBin(dstHist, bins, bin, 500);
int softValue = histMaxValue.at<int>(0, 0);
int softIndex = histMaxValue.at<int>(0, 1);
//设置直方图画布的参数,直方图要花在一个“幕布”上,这个幕布也要设置参数
int hist_w = 1200;//画布的宽
int hist_h = 800; //画布的高
int bin_w = cvRound((double)hist_w / bin); //设置直方 图中每一点的步长,直方图有256个横坐标,每个坐标在画布中占多长,通过hist_w/bin计算得出。cvRound()函数是“四舍五入”的作用。
Mat hist_canvas(hist_h, hist_w, CV_8UC3, cv::Scalar(255, 255, 255));//通过我们设置的参数创建出一个黑色的画布
//对三个通道的直方图数据进行归一化处理,这是一个必要环节
normalize(dstHist, dstHist, 0, hist_h - 100, NORM_MINMAX, -1, Mat());
int isGrid = 1;
cv::Scalar scalarGrid = Scalar(255, 206, 135);
if (isGrid)
{
//添加网格
for (int i = 0; i < hist_w; )
{
cv::line(hist_canvas, Point(bin_w * i, hist_h - cvRound(0)),
Point(bin_w * i, hist_h - cvRound(hist_h)), scalarGrid, 1, 8, 0);
i += bin_w;
}
for (int i = 0; i < hist_h; )
{
cv::line(hist_canvas, Point(0, hist_w - bin_w * i),
Point(hist_w, hist_w - bin_w * i), scalarGrid, 1, 8, 0);
i += bin_w;
}
}
//以折线统计图的方式绘制三个通道的直方图在画布上
for (int i = 1; i < bin; i++)
{
cv::line(hist_canvas, Point(bin_w * (i - 1), hist_h - cvRound(dstHist.at<int>(i - 1))),
Point(bin_w * (i), hist_h - cvRound(dstHist.at<int>(i))), Scalar(0, 0, 255), 2, CV_AA, 0);
}
cv::imshow("hist", hist_canvas);
cv::waitKey(0);
cv::destroyAllWindows();
}
//获取直方图bins
Mat GetBins(int internal, int min, int bin)
{
Mat bins = Mat::zeros(Size(bin, 1), CV_32SC1);
for (int i = 0; i < bin; i++)
{
bins.at<int>(i) = min + i*internal;
}
return bins;
}
//直方图统计
Mat CalcGrayHist(int bin, int internal, signed short* srcImage, int width, int height, int counts)
{
Mat histogram = Mat::zeros(Size(bin, 1), CV_32SC1); //注意,Size对应的是x和y,也就是第一个元数是矩阵的列数
for (int k = 0; k < counts; k++)
{
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int index = k*width*height + i*width + j; //获取每个点的像素值
if (srcImage[index] > 3)
{
int value = srcImage[index] / internal;
if (value < (bin - 1))
{
histogram.at<int>(0, value) += 1; //获取了一个像素值,在相应的位置上加1
}
else
{
histogram.at<int>(0, bin - 1) += 1;
}
}
}
}
}
return histogram;
}
参考文献:
[1] C++OpenCV画像処理の基本操作(5) - ヒストグラム
[2] RGBカラーテーブル