背景
OpenCVには、画像の色空間を変換するcvtColorという関数があります。そして、cvtColor関数にはカラー画像をグレースケールに変換する機能があります。そのグレースケール変換処理に関して、実際にどういう計算式なのか調べてみました。この記事は、そのメモです。
やったこと
8bit3チャンネル(Mat3bクラス)のRGB画像からグレースケールに変換する処理を抜粋してみた。
実装されている計算式と、ドキュメントに記載されている式を浮動小数で実装した場合との違いを見てみた。
実行環境
- windows 8.1(64ビット)
- Microsoft Visual Studio Community 2015
- OpenCV 3.2.0
ドキュメントに記載されている式
OpenCV 3.2.0のウェブページ( http://docs.opencv.org/3.2.0/de/d25/imgproc_color_conversions.html )を見るとRGBからグレースケールへの変換式は以下のように記述されています。
Y ← 0.299 \times R + 0.587 \times G + 0.114 \times B
Yはグレースケールの輝度値です。
実装されている式
実際には、固定小数として計算していて式は以下です。
Y ← ( 4899 \times R + 9617 \times G + 1868 \times B + 8192 ) >> 14
14ビット右シフトしているのは、16384で割り算するのと同じです。
+8192しているのは、+0.5していることと同じで、四捨五入の効果だと思います。
実装の抜粋
グレースケール値の算出式を抜粋してみました。
OpenCVの実装では、parallel_forなどの高速化がなされていますが、
高速化部分は無視して、単純に算出している式だけ取り出してみました。
Mat1b cvtColor2(const Mat3b& src)
{
int db = 1868;
int dg = 9617;
int dr = 4899;
const int yuv_shift = 14;
int b = 0, g = 0, r = (1 << (yuv_shift - 1));
int tab[256 * 3];
for (int i = 0; i < 256; i++, b += db, g += dg, r += dr)
{
tab[i] = b;
tab[i + 256] = g;
tab[i + 512] = r;
}
Mat1b dst = Mat1b::zeros(src.size());
for (int j = 0; j < dst.rows; ++j)
{
for (int i = 0; i < dst.cols; ++i)
{
dst(j, i) = uchar((tab[src(j, i)[0]] + tab[src(j, i)[1] + 256] + tab[src(j, i)[2] + 512]) >> 14);
}
}
return dst;
}
一応、上記の関数を使ってグレースケール化してみます。
#include "opencv2\opencv.hpp"
using namespace cv;
int main(int argc, char* argv[])
{
Mat3b src = imread("fruits.jpg");
Mat1b dst = cvtColor2(src);
imwrite("gray.png", dst);
}
下の画像が入力画像 fruits.jpgです。
そして、下の画像がグレースケール結果です。
公式と実装されている式との差について
一応、公式を浮動小数で実装した場合と数値が一致するのか調べてみました。
とりあえず入力値(256 * 256 * 256通りのRGB値)を全部試してみて差の絶対値の平均を算出してみました。算出する関数は以下です。
void calcDif()
{
int db = 1868;
int dg = 9617;
int dr = 4899;
const int yuv_shift = 14;
int b = 0, g = 0, r = (1 << (yuv_shift - 1));
int tab[256 * 3];
for (int i = 0; i < 256; i++, b += db, g += dg, r += dr)
{
tab[i] = b;
tab[i + 256] = g;
tab[i + 512] = r;
}
int sum = 0;
for (int rid = 0; rid < 256; ++rid) {
for (int gid = 0; gid < 256; ++gid) {
for (int bid = 0; bid < 256; ++bid) {
int val1 = (tab[bid] + tab[gid + 256] + tab[rid + 512]) >> 14;
int val2 = int(0.299 * (double)rid + 0.587 * (double)gid + 0.114 * (double)bid + 0.5);
sum += abs(val1 - val2);
}
}
}
double num = 256 * 256 * 256;
cout << "mean error : " << sum / num << endl;
}
結果は、mean_error : 0.00183302 となりました。
差はかなり小さいように思いました。