ベタ塗り画素カウント（イラスト編）

#はじめに

* [＠omakeworldさんの呟き](https://twitter.com/omakeworld/status/602800065397166080)がきっかけ
* 「イラストと写真の区別はできるのか？」という問題
* もうあるのでは？と検索したら[面白い記事](http://ultraist.hatenablog.com/entry/20071231/1199138033)が
 
>
2.ベタ塗りエリアを構成するピクセルの割合による判定
3x3マスを構成する全てのピクセルが同色のブロックをベタ塗りエリアと定義して、そのエリアが全体に占める割合によって判定する。イラストはベタ塗りエリアが多いのではないかという予想。

* とても面白そうなので自分もこれをヒントに<br>
**イラストor写真判定システム**<br>
を作ってみたくなったのでその第一歩として表題へ
* まずはイラストと写真、ベタ塗り画素の割合はそれぞれどのような値になるのか調査です
* ※ベタ塗りの定義は上記の記事と同じにしました

#環境
* 言語：C++
* ライブラリ：OpenCV（バージョン忘れた… 2.ふんたらくらい）

#プログラム
```cpp:main.cpp
/**
 * @file main.cpp
 * @brief ベタ塗り画素を数えるプログラム
 * @author Ponyo1127
 * @date 2015/05/30
 */

#include <iostream>
#include <string>
#include <stdio.h>
#include <sys/stat.h>
#include "opencv/cv.h"       //cv.hを読み込む
#include "opencv/highgui.h"  //highgui.hを読み込む<br /><br />int main(int argc, const char * argv[])
#include "CImageData.h"

// 画像の総数
// あまり（全く）賢くない
#define ILLUST_COUNT 1063
// ベタ塗り判定範囲
// 3以上の奇数
#define SEARH_RANGE 3
// 中心の画素の位置
#define SEARH_RANGE_HALF SEARH_RANGE / 2

// 2つの画素の色が同じか判定
bool JudgeSolidFill(CvScalar csCenterRGB, CvScalar csSearchRGB);
// 対象画素の周りの画素の色を取得してJudgeSolidFillに渡す
bool SearchSolidFill(IplImage *iplInputImage, int y, int x, CvScalar csCenterRGB);

int main(int argc, const char * argv[]) {
	// 画像ファイルのディレクトリ
	string strDir = "/Users/Ponyo1127/Pictures/Image/";
	
	// ファイルが存在するか判定するのに使う構造体
	struct stat st;
	
	// ファイルが存在するか判定した結果を格納する変数
	// -1なら存在しない
	int nFileJudge = 0;
	
	// 読み込む画像ファイルのパス
	string strLoadImagePath;
	
	// 読み込む画像の名前
	string strLoadImageName;
	
	// 読み込む画像（IplImage）
	IplImage *iplLoadImage;

	// 読み込んだRGB値を格納
	CvScalar csCenterRGB;
	
	// 探索した画像の情報を管理するクラス
	CImageData ImageData;
	
	// 結果を出力するcsvファイルを読み込む
	FILE *fResultFile;
	fResultFile = fopen("/Users/Ponyo1127/Documents/result.csv", "w+");
	
	// 読み込めなかったら終了
	if(fResultFile == NULL)
	{
		printf("エラー");
		return -1;
	}
	
	// csvの見出しを設定
	fprintf(fResultFile, "画像名, 探索画素数, ベタ塗り画素数, ベタ塗り画素割合(%%)\n");
	
    // フォルダ内の画像を1枚ずつ読み込み
	for(int nIllustCount = 1; nIllustCount <= ILLUST_COUNT; nIllustCount++)
	{
		// 画像名
		strLoadImageName = "Illust" + to_string(nIllustCount);
		// 画像のパス
		strLoadImagePath = strDir + strLoadImageName + ".jpg";
		
		// jpgファイルが存在するか確認
		nFileJudge = stat(strLoadImagePath.c_str(), &st);
		// 存在しなかったらスキップ（pngファイルが読み込めない）
		if(nFileJudge == -1)
		{
			continue;
		}
		
		// 画像を読み込み
		iplLoadImage = cvLoadImage(strLoadImagePath.c_str());
		
		// ImageDataのメンバ変数の初期化
		ImageData.Init();
		
		// ImageDataクラスのオブジェクトに画像名をセット
		ImageData.SetImageName(strLoadImageName);
		
		// 左上から画素を指定してベタ塗りかどうか探索
		// スタートと終了位置はベタ塗り判定範囲によって調整
		for(int x = SEARH_RANGE_HALF; x < iplLoadImage->width - SEARH_RANGE_HALF; x++)
		{
			for(int y = SEARH_RANGE_HALF; y < iplLoadImage->height - SEARH_RANGE_HALF; y++)
			{
				// 探索画素を増やす
				ImageData.CountPixel();
				
				// RGB値を取得
				csCenterRGB = cvGet2D(iplLoadImage, y, x);
				
				// 周りの画素と色が同じかどうか
				if(SearchSolidFill(iplLoadImage, y, x, csCenterRGB) == true)
				{
					// 同じだったらベタ塗り画素数を増やす
					ImageData.MatchPixel();
				}
			}
		}
		
		// ベタ塗り画素数の割合計算
		ImageData.CalcSolidFillRatio();
		
		// csvファイルに書き込み
		fprintf(fResultFile, "%s\n", (ImageData.GetCsvText()).c_str());
		
		// コンソールに結果出力
		// 合ってもなくてもどっちでも
		printf("%d枚目終了\n", nIllustCount);
		
		// iplLoadImageを破棄
		cvReleaseImage(&iplLoadImage);
	}
	
	// csvファイルを閉じる
	fclose(fResultFile);

    return 0;
}

/**
 * @breaf 2つの画素の色が同じか判定
 * @param [csCenterRGB] 入力画素1のRGB値
 * @param [csSearchRGB] 入力画素2のRGB値
 * @retval true  2つの画素の色が同じなのでベタ塗り
 * @retval false 2つの画素の色が違う色なのでベタ塗りでない
 */
bool JudgeSolidFill(CvScalar csCenterRGB, CvScalar csSearchRGB)
{
	// RGBのそれぞれ比較
	for(int nCount = 0; nCount < 3; nCount++)
	{
		// RGBの内1つでも値が異なったらfalseを返す
		if(csCenterRGB.val[nCount] != csSearchRGB.val[nCount])
		{
			return false;
		}
	}
	
	// 全て一致したらtrueを返す
	return true;
}

/**
 * @breaf 対象画素の周りの画素の色を取得してJudgeSolidFillに渡す
 * @param [iplInputImage] 探索画像
 * @param [y] 対象画素のy座標
 * @param [x] 対象画素のx座標
 * @param [csCenterRGB] 対象画素のRGB値
 * @retval true  周りと色が同じなのでベタ塗り
 * @retval false 周りと少なくとも1つが違う色なのでベタ塗りでない
 */
bool SearchSolidFill(IplImage *iplInputImage, int y, int x, CvScalar csCenterRGB)
{
	// 読み込んだRGB値を格納
	CvScalar csSearchRGB;
	
	// 周りの画素を順番に指定
	for(int nSx = x - SEARH_RANGE_HALF; nSx < x + SEARH_RANGE_HALF; nSx++)
	{
		for(int nSy = y - SEARH_RANGE_HALF; nSy < y + SEARH_RANGE_HALF; nSy++)
		{
			// 周りの画素のRGB値を取得
			csSearchRGB = cvGet2D(iplInputImage, nSy, nSx);
			
			// 周りの画素で1つでも値が異なったらfalseを返す
			if(JudgeSolidFill(csCenterRGB, csSearchRGB) == false)
			{
				return false;
			}
		}
	}
	
	// 周りの画素と全て値が一致したらtrueを返す
	return true;
}
```


```cpp:CImageData.h
/**
 * @file CImageData.h
 * @brief 探索した画像の情報を管理するクラスの宣言
 * @author Ponyo1127
 * @date 2015/05/30
 */

#include <stdio.h>
#include <string>

using namespace std;


class CImageData
{
private:
    string m_strImageName;					// 画像の名前
    int    m_nSearchPixelCount;				// 画素数
    int    m_nSolidFillCount;				// ベタ塗り画素数
	double m_dSolidFillRatio;				// ベタ塗り画素の割合
    
public:
	void   SetImageName(string strSetName);	// 画像の名前のsetter
	void   Init(void);						// メンバ変数の初期化
	void   CountPixel(void);				// 探索画素数
	void   MatchPixel(void);				// ベタ塗り見つけた
	void   CalcSolidFillRatio(void);		// ベタ塗り割合計算
	string GetCsvText(void);				// csvファイル書き込み用の文字列生成
};
```

```cpp:CImageData.cpp
/**
 * @file CImageData.cpp
 * @brief 調査した画像の情報を管理するクラスのメンバ関数の定義
 * @author Ponyo1127
 * @date 2015/05/30
 */

#include "CImageData.h"

#include <stdio.h>
#include <string>

using namespace std;

// 画像の名前のsetter
void CImageData::SetImageName(string strSetName)
{
	m_strImageName = strSetName;
}

// メンバ変数の初期化
void CImageData::Init(void)
{
	m_strImageName    = "";
	m_nSearchPixelCount  = 0;
	m_nSolidFillCount = 0;
	m_dSolidFillRatio = 0.0;
}

// 探索画素数のカウント
void CImageData::CountPixel(void)
{
	m_nSearchPixelCount++;
}

// ベタ塗り画素のカウント
void CImageData::MatchPixel(void)
{
	m_nSolidFillCount++;
}

// ベタ塗り画素の割合計算
void CImageData::CalcSolidFillRatio(void)
{
	m_dSolidFillRatio = static_cast<double>(m_nSolidFillCount) / m_nSearchPixelCount;
}

// csvファイル書き込み用文字列生成
string CImageData::GetCsvText(void)
{
	string strCsvText;

	strCsvText = m_strImageName;
	strCsvText += ", ";
	strCsvText += to_string(m_nSearchPixelCount);
	strCsvText += ", ";
	strCsvText += to_string(m_nSolidFillCount);
	strCsvText += ", ";
	strCsvText += to_string(m_dSolidFillRatio * 100);
	
	return strCsvText;
}
```

#実験結果
* 入力は全部アニメ関連のイラスト画像（1000枚弱）


ざっくりこんな感じ
（※結果画像間違えて消しちゃった）

もんのすごい画像によって差がでた

一番ベタ塗り画素の割合が小さかった画像がこれ（リサイズしてます）
![Illust839.jpeg](https://qiita-image-store.s3.amazonaws.com/0/68592/c6615fce-7c45-e478-e8c5-7271917bc9b2.jpeg)
ピンクの矢印のところがまだら模様（？）みたいになってるので全然ベタ塗りになってない
ベタ塗り画素の割合は0%だった

まどマギのQべえとかは白画素が多くてベタ塗り画素率は高かった
あとは灰色の背景にちょびっとキャラがいるみたいな奴も当然高い

#考察
ベタ塗り画素の割合は0％〜94％と幅がいくらなんでも開きすぎてしまいました
どんな感じで分布してるかはcsvファイル置いとくので、誰かヒストグラムかなんか描いてください
[result.csv(utf-8)](https://dl.dropboxusercontent.com/u/11090008/result.csv)
[result_s.csv(Shift-JIS)](https://dl.dropboxusercontent.com/u/11090008/result_s.csv)
まだ写真のベタ塗りは調べてないですけどなんとなーくベタ塗りだけじゃ判定は無理っぽい気が…

2015/06/04 続き書きました!
[ベタ塗り画素カウント（写真編）](http://qiita.com/Ponyo1127/items/c87ffb62ce4067dce3bc)