
ゆうき( @BASEBALLY15 )です^^
今日はどうしたんじゃ?
C言語を使って,画像内の特定のHSV値をヒストグラムで表示したいのですが,どのようにコードを書けばよいのでしょうか?
そうじゃのう.
これから紹介するコードを書けばいいぞい!
(※Visual Studio 2017 を使用)
分かりました!

目次
HSV値って何?
HSVとは,色相(Hue),彩度(Saturation・Chroma),明度(Value・Brightness)の3つの成分から成る色空間のことを言います.
- 色相(H)→具体的な色を定義する要素.0°~360°で表現される.
- 彩度(S)→色の鮮やかさ・濃さを表す要素.0%~100%で表現される.
- 明度(V)→色の明るさ・暗さを表す要素.0%~100%で表現される.
といったように,色を定義するだけでなく,
どのような特徴を持った色なのか
ということまで,表現することができます.
具体的に何が出来るの?

今回ご紹介するコードを書くことで,カラー画像の指定した箇所のHSV値をヒストグラムで表示することが出来ます.
上の画像の『YProg』と書かれた箇所のHSV値をヒストグラムで表示したものが上の3つのブラフです.
それでは,画像内の特定のHSV値をヒストグラムで表示する方法を見てきましょう⤵
C言語で画像内の特定のHSV値をヒストグラムで表示しよう!
最初に手順を載せておきます.
- 『Paint.NET』というソフトでモノクロ画像を作る.
- カラー画像とモノクロ画像を『RAW形式』に変換する.(XnViewを使用)
- 『gnuplot』をダウンロード
- コードを書いていく
- 実際に画像内のHSV値をヒストグラムで表示する.
1. 『Paint.net』でモノクロ画像を作ろう!
まずは,『Paint.NET』というソフトを使って,モノクロ画像を作ります.
Paint.NETを開き,HSV値を調べたい画像を読み込ませます.
今回は,画像内の『YProg』と書かれている個所のHSV値をヒストグラムで表示します.

『ツール』の『鉛筆』を使って(上図),YProgという文字の外枠をなぞります(下図).

そして,『ペイント缶』を使って,YProgの文字の外側を黒色で塗りつぶします.

『ペイント缶』で上手く塗りつぶせない箇所は,『ペイントブラシ』を使うことで,綺麗に塗りつぶすことが出来ます.
(※『ツール』の横の『ブラシ幅』を変更することで,ブラシの大きさを変えることが出来ます.)
同じ要領で『YProg』という文字は白色で塗りつぶします.

2. 画像をRAW形式に変換しよう!(Xn.Viewを使用)
画像の形式と言えば,『.jpeg』や『.png』を思い浮かべるのではないでしょうか?
実はこの形式のままでは,画像処理を行うことが出来ません.
画像処理を行うためには,画像をRAW形式に変換しておく必要があります.
以下のサイトで,RAW形式に変換する方法をご紹介しているので,先にそちらを見て頂いた方がいいかもしれません^^
標準入力関数を使えるようにしよう!
『Visual Studio 2012以降』を使っている方は,標準入力を行うための関数を使うために,少し処理を行わなければいけません.
今から,その方法をご紹介します.
Visual Studioのヘッダーファイルを開いてください.

そして,『pch.h』をダブルクリックして,以下のコードを記述してください.
#define _CRT_SECURE_NO_WARNINGS
これで,標準入力を行うための関数(scanfやfopenなど)を使うことが出来ます^^
3. gnuplotをダウンロードしよう!
ヒストグラムで表示する際に,『gnuplot』というグラフ作成ソフトを使います.
以下の記事で,gnuplotのダウンロード方法を紹介しているので,先にそちらを確認して頂くことをオススメします!
4. C言語でコードを書いていこう!
#include "pch.h"
#include "stdio.h"
#include "stdlib.h"
//--------------------------------------------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------------------------------------------
//画像サイズを決める
#define X_SIZE 640
#define Y_SIZE 480
//1枚目の画像(カラー画像)を読み込むファイルの配列数を決める
char filepath[500];
//2枚目の画像(Paint.NETで作成)を読み込むファイルの配列数を決める
char second_filepath[500];
//rgb値の配列を宣言する
unsigned char r[Y_SIZE][X_SIZE], g[Y_SIZE][X_SIZE], b[Y_SIZE][X_SIZE];
//paint.netで作成した画像のrgb値の配列を宣言する
unsigned char r_in_paint[Y_SIZE][X_SIZE], g_in_paint[Y_SIZE][X_SIZE], b_in_paint[Y_SIZE][X_SIZE];
//ヒストグラムの結果用の配列を宣言する
long Hist[400];
//--------------------------------------------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------------------------------------------
//プロトタイプ宣言(作成した関数を始めに読み込む)
//rgb画像を読み込むための関数(RGB値それぞれを取得)
void Read_rgbimage(char *Read_filepath, unsigned char r[Y_SIZE][X_SIZE], unsigned char g[Y_SIZE][X_SIZE], unsigned char b[Y_SIZE][X_SIZE]);
//画像を切り出して,その範囲のHSV値をヒストグラムに表す(Paint.NETを使用)
void paint_dot_NET(unsigned char r_in[Y_SIZE][X_SIZE], unsigned char g_in[Y_SIZE][X_SIZE], unsigned char b_in[Y_SIZE][X_SIZE], unsigned char r_paint[Y_SIZE][X_SIZE], unsigned char g_paint[Y_SIZE][X_SIZE], unsigned char b_paint[Y_SIZE][X_SIZE], long hist[400]);
//gnuplotを表示
void gnuplot(long hist[400]);
//--------------------------------------------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------------------------------------------
//メインとなる関数
void main(void)
{
//1枚目の画像を読み込む
printf("読み込む画像ファイル1枚目=");
scanf("%s", filepath);
//1枚目の画像のrgb画像を読み込むための関数を呼び出す
Read_rgbimage(filepath, r, g, b);
//2枚目の画像を読み込む
printf("読み込む画像ファイル2枚目(paint.net)=");
scanf("%s", second_filepath);
//2枚目の画像のrgb画像を読み込むための関数を呼び出す
Read_rgbimage(second_filepath, r_in_paint, g_in_paint, b_in_paint);
//ヒストグラムに表す
paint_dot_NET(r, g, b, r_in_paint, g_in_paint, b_in_paint, Hist);
//gnuplotを表示
gnuplot(Hist);
}
//--------------------------------------------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------------------------------------------
//RGBカラー画像を読み込むための関数(RGB値それぞれを取得)
void Read_rgbimage(char *Read_filepath, unsigned char r[Y_SIZE][X_SIZE], unsigned char g[Y_SIZE][X_SIZE], unsigned char b[Y_SIZE][X_SIZE])
{
//変数を定義
int i, j;
//画像を読み込むための準備
FILE *r_filepath;
r_filepath = fopen(Read_filepath, "rb");
//もし,r_filepathが無かったら,「存在しません」と表示する
if (r_filepath == NULL)
{
printf("%sは存在しません!", Read_filepath);
exit(-1);
}
//rgb値を読み込む
for (j = 0; j < Y_SIZE; j++)
{
for (i = 0; i < X_SIZE; i++)
{
//画像のRGB値を取得
r[j][i] = fgetc(r_filepath);
g[j][i] = fgetc(r_filepath);
b[j][i] = fgetc(r_filepath);
}
}
//画像ファイルを閉じる
fclose(r_filepath);
}
//--------------------------------------------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------------------------------------------
//画像を切り出して,その範囲のHSV値をヒストグラムに表す(Paint.NETを使用)(2枚の画像を読み込む)
void paint_dot_NET(unsigned char r_in[Y_SIZE][X_SIZE], unsigned char g_in[Y_SIZE][X_SIZE], unsigned char b_in[Y_SIZE][X_SIZE], unsigned char r_paint[Y_SIZE][X_SIZE], unsigned char g_paint[Y_SIZE][X_SIZE], unsigned char b_paint[Y_SIZE][X_SIZE],long hist[400])
{
int i, j, n;
//RGB値からHSV値に変換する
double max, min;
double R, G, B;
//ヒストグラムの値が入る箱内を初期化
for (n = 0; n < 400; n++)
{
hist[n] = 0;
}
for (j = 0; j < Y_SIZE; j++)
{
for (i = 0; i < X_SIZE; i++)
{
//1枚目の出力用のrgb値を用意
r_out[j][i] = r_in[j][i];
g_out[j][i] = g_in[j][i];
b_out[j][i] = b_in[j][i];
//2枚目(Paint.NETで作成した画像)
//出力用のrgb値を用意
r_out_paint[j][i] = r_in_paint[j][i];
g_out_paint[j][i] = g_in_paint[j][i];
b_out_paint[j][i] = b_in_paint[j][i];
//カラー画像のRGB値をHSV値に変換する(131行~210行)
R = (double)r_out[j][i] / 255.0;
G = (double)g_out[j][i] / 255.0;
B = (double)b_out[j][i] / 255.0;
//最大値・最小値
if (R >= G && R >= B)
{
max = R;
if (G < B)
{
min = G;
}
else
{
min = B;
}
}
else if (G >= R && G >= B)
{
max = G;
if (R < B)
{
min = R;
}
else
{
min = B;
}
}
else
{
max = B;
if (R < G)
{
min = R;
}
else
{
min = G;
}
}
//Hue(色相)
if (max == min)
{
h_out = 0.0;
}
else if (max == R)
{
h_out = 60.0 * (G - B) / (max - min);
}
else if (max == G)
{
h_out = 60.0 * (B - R) / (max - min) + 120.0;
}
else if (max == B)
{
h_out = 60.0 * (R - G) / (max - min) + 240.0;
}
if (h_out < 0.0)
{
h_out += 360.0;
}
//Saturation(彩度)
if (max == 0.0)
{
s_out = 0.0;
}
else
{
s_out = ((max - min)*100.0) / max;
}
v_out = max * 100.0;
//もし,paint.netの画像のRGB値が全て127以上(白と黒の確実な分かれ目)なら・・・
if ((r_out_paint[j][i] >= 127) && (g_out_paint[j][i] >= 127) && (b_out_paint[j][i] >= 127))
{
//hsv値をヒストグラムに代入していく(h_out,s_out,v_outを都度書き替える)
n = int(h_out);
//n = int(s_out);
//n = int(v_out);
hist[n]++;
}
}
}
}
//--------------------------------------------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------------------------------------------
//gnuplot用の関数
void gnuplot(long hist[400])
{
int c;
//gnuplotを呼びだすために記述
FILE *gid;
//gnuplotを呼びだす
gid = _popen("c:/ooooooooo/xxxxxxxxx/bin/gnuplot.exe", "w");
//x軸の範囲を指定
fprintf(gid, "set xrange[0:100]\n");
//ラベル名(H,S,Vで都度変更する)
fprintf(gid, "set xlabel \"H\n");
//fprintf(gid, "set xlabel \"S\n");
//fprintf(gid, "set xlabel \"V\n");
fprintf(gid, "set ylabel\"Number of pixels\n");
//凡例を非表示
fprintf(gid, "unset key\n");
fprintf(gid, "plot'-' with boxes \n");
//H値の時・・・
for (c = 1; c < 360; c++)
{
//gnuplotに書き込んでいく
fprintf(gid, "%d\t%d\n", c, hist[c]);
}
//SとV値の時・・・
/*
for (c = 1; c < 100; c++)
{
//gnuplotに書き込んでいく
fprintf(gid, "%d\t%d\n", c, hist[c]);
}
*/
//guplotの後処理
fprintf(gid, "e\n");
fflush(gid);
fprintf(gid, "pause mouse\n");
fflush(gid);
_pclose(gid);
}
5. 実際にヒストグラムを表示してみよう!
コードを書き終えたら,『Ctrl+F5』で実行します.
そして,1枚目の画像ファイルにはカラー画像を,2枚目の画像ファイルにはモノクロ画像を読み込ませます.

すると,下の画像の様に,HSV値をヒストグラムで表示することが出来ます⤵

終わりに
今回は,C言語を使って画像内の特定のHSV値をヒストグラムで表示する方法についてご紹介しました.
今回のコードを少し書き換えると,RGB値をヒストグラムで表示することも出来ます.
今後は,ヒストグラムで表示したHSV値から画像内の特定の箇所を抽出する方法をご紹介します.
それでは・・・