グレースケール化
カラー画像を、グレースケール画像に変換表示にします。
グレー画像にする方法としては、
1.NTSC係数を用いて、変換する。
2.三色の平均値を使用する。
3.グレーパレットを使用して、グレー表示にする。
の三つが代表的な方法です。
今時、画像を利用した、測定、判定等以外にグレーの画像を用いることはあまりないでしょう。
NTSC係数による場合は、視覚に合わせて、輝度を調整する方法で、アナログ方式で、カラー画像と、モノクロ画像を、同一の電波で送信する方法として考え出された方式です。
カラー画像信号は輝度(y)と色信号(Cb,Cr)からなっている事になり、輝度がモノクロ時の画像となります。
光りの三原色のうち、視感度はグリーンが一番高く、次が赤、一番感度が低いのはブルーなので、視感度に合わせて、係数で補正をしています。
輝度は、グレーに変換する場合の係数として、赤 0.298912
緑 0.586611 青 0.114478 となっています。
実際には、視覚なので、個人差があり、あまり精度よく計算する必要はありません。
NTSC係数による計算は、GDIのカラー変換マトリックスを使用して変換する方法と、変換テーブルを使用して変換する方法があります。
変換テーブルを使用しないと、各ピクセルごとに演算を行う為、整数演算を使用しても、時間を要します。
GDIプラスの Color Matrix の使用
変換マトリックスを使用した場合、浮動小数点演算があるため、変換に若干時間がかかります。
NTSC係数のマトリックス
ColorMatrix_GrayScale : TColorMatrix = (
( 0.298912, 0.298912, 0.298912, 0.0, 0.0),
( 0.586611, 0.586611, 0.586611, 0.0, 0.0),
( 0.114478, 0.114478, 0.114478, 0.0, 0.0),
( 0.0, 0.0, 0.0, 1.0, 0.0),
( 0.0, 0.0, 0.0, 0.0, 1.0));
何も変換しない場合のマトリックス
ColorMatrix_NormalScale : TColorMatrix = (
( 1.0, 0.0, 0.0, 0.0, 0.0),
( 0.0, 1.0, 0.0, 0.0, 0.0),
(
0.0, 0.0, 1.0, 0.0, 0.0),
( 0.0, 0.0, 0.0, 1.0, 0.0),
( 0.0, 0.0, 0.0, 0.0, 1.0));
値に、1を超えても設定しても良いのですが、計算した結果が 0を下回る場合は0に、255を超える場合は、255に制限されます。
TColorMatrix の値について詳細
R'=m11*R + m21*G + m31*B + m41 * α + dR
G'=m12*R + m22*G + m32*B +
m42 * α + dG
B'=m13*R + m23*G + m33*B + m43 * α + dB
α'=m14*R + m24*G + m34*B + m44 * α + dα
Wndows
の場合、4バイト目の"α"の値はフラグとなり、システム用デフォルトとか、透明色とかの指定用フラグとなるので
m14,m24,m34,
m41,m42,m43 には"0"を割り付け、m44には、フラグが変わらないように"1"を割り付けます。
5列目は、5×5のマトリックスにする為のDummyです。
dR,dB,dG,dα の値は、0~1の値が0~255の値に相当します。-1を与えると、その色が0に設定されます。
各色の計算結果は、255を超える場合は255に、0以下は0に設定されます。
次のマトリックスは
ColorMatrix_GrayScale : TColorMatrix = (
(-0.298912,-0.298912,-0.298912, 0.0, 0.0),
(-0.586611,-0.586611,-0.586611, 0.0, 0.0),
(-0.114478,-0.114478,-0.114478, 0.0, 0.0),
( 0.0, 0.0, 0.0, 1.0, 0.0),
(
1.0, 1.0,
1.0, 0.0, 1.0));
R'=-0.298912*R -0.586611*G -0.114478*B +0 * α + 255
= 255 - (0.298912*R + 0.586611*G + 0.114478*B)
G'=-0.298912*R
-0.586611*G -0.114478*B +0 * α + 255 = 255 -
(0.298912*R + 0.586611*G + 0.114478*B)
B'=-0.298912*R -0.586611*G -0.114478*B
+0 * α + 255 = 255 - (0.298912*R + 0.586611*G +
0.114478*B)
α'=0.0*R + 0.0*G +0.0*B + 1.0 * α + 0.0
= α
の計算となり、グレースケールの反転画像となります。
ガンマ補正値
ガンマ補正
V=(D/255)1/r× 255
信号の最大値を255とした場合で、アナログ信号の場合は、アナログアンプによる計算なので、
255の値をとるわけではありません。
NTSCにはガンマ補正もあり、ガンマ補正値2.2は、ブラウン管の発光特性を補正するもので、実際にこの値が採用されているわけではありません。
ブラウン管の特性によって、違う値が採用されています。
現在は、ブラウン管は日本からは全く消滅しています
又、モニター、TVカメラもカラー化が進み、モノクロの画像が使用されているのは、監視カメラと、工業用測定カメラ位でしか存在しません。
テレビの放送も、デジタル化された事により、NTSC方式は、監視用テレビカメラと、そのモニターと録画装置位しかなくなってしまいました。
デジタルカメラで撮影された画像は、RGB それぞれの値の最大値が255になっています。
三色の平均値をとる方法は、簡単なのですが、視覚とは少しずれてしまいます、しかし、測定器として使用する場合は、全く問題はありません。
又、測定用としデーターを使用する場合は、配列データーとしたほうが演算が早くなります。
測定用のカメラからパソコンにデーターを取り込むと、一般的には、メモリーへ配列データーとして取り込まれます。
写真用デジタルカメラの場合は、JPGファイルとする方法が一般的です。
グレーパレットの使用は、三色の平均値をとるのと変わりません。
変換はシステムに任せるので、一番早く変換が出来るものと思われます。
グレーパレットを書き込み先のTBitmapに適用すると、カラー画像のビットマップを、グレースケールのビットマップに画像を書き込む時、システムがパレットを使用して、グレー画像に変換します。
グレースケールのビットマップから、表示用イメージに書き出すと、グレースケールにより逆変換して、画像として表示されます。
この時は、変換元の値が、一つなので、グレースケール変換前のカラー画像にはなりません。単一色として表示されます。
元画像に対して、NTSC係数を使用すると、三食平均に比べ、グリーンの色の部分が明らかに、明るくなっています。
見た感覚も、カラーの時の画像に近いことがわかります。
グレースケールとして、8ビット、4ビット、1ビット(白黒)がありますが、プログラム上で画像を扱う場合、8ビット以外は扱い辛いので、4ビット、1ビット(白黒)のデーターを扱うことは無いでしょう。
一昔前は、パソコンのメモリー容量が小さかったため、メモリーの使用量を出来る限り少なくしていました。
現在は、大容量となり、アクセスも早くなりました。
プログラムとして、グレースケールパレットが使用できるようになっているので、参考としてプログラムを組んでみました。
本サンプルプログラムには、画像の保存はありません、保存処理のある画像処理プログラムもあるので、必要であれば、それを参照して追加してください。
保存処理は、アンシャープマスキング、画像の回転のプログラムに組み込んであります。
参考プログラム
unit GrayScaleMain; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, Vcl.ExtDlgs, system.Types, GDIPAPI, GDIPOBJ; type TForm1 = class(TForm) FileOpen: TButton; Image1: TImage; Image2: TImage; OpenPictureDialog1: TOpenPictureDialog; GrayScaleBtn: TButton; GrayScaleDBtn: TButton; GrayScaleABtn: TButton; GrayScale24Btn: TButton; GrayScale4btn: TButton; procedure FormCreate(Sender: TObject); procedure FileOpenClick(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure GrayScaleBtnClick(Sender: TObject); procedure GrayScaleDBtnClick(Sender: TObject); procedure GrayScaleABtnClick(Sender: TObject); procedure GrayScale24BtnClick(Sender: TObject); procedure GrayScale4btnClick(Sender: TObject); procedure GrayMatBtnClick(Sender: TObject); private { Private 宣言 } procedure TemplateMake; // Templateデーター作成 public { Public 宣言 } end; var Form1: TForm1; type TPrgbarry = array[0..0] of Trgbtriple; // 24ビットカラーレコード 32ビット用はTRGBQuadArray Prgbarray = ^TPrgbarry; // ポインター // 配列のポインターが必要なだけなので、長さは1 [0..0]で問題ありません。 // TMaxLogPaletteを使用しても良いのですが、4ビット用グレースケールを宣言してみました T16LogPalette = packed record // 4ビット用グレースケール パレット レコード Delphiにないので此処で宣言 palVersion: Word; // Delphi では record の可変長宣言できません palNumEntries: Word; palPalEntry: array [0..15] of TPaletteEntry; // TPaletteEntryは32 ビットカラー end; implementation {$R *.dfm} const OpenFileFilter = '画像ファイル|*.png;*.jpg;*.gif;*.bmp;*.tif;*.ico;*.wdp'+ '|*.png|*.png' + '|*.jpg|*.jpg' + '|*.gif|*.gif' + '|*.bmp|*.bmp' + '|*.tif|*.tif' + '|*.ico|*.ico' + '|*.wdp|*.wdp'; SaveFileFilter = '画像ファイル|*.png;*.jpg;*.gif;*.bmp;*.tif;*.wdp' + '|*.png|*.png' + '|*.jpg|*.jpg' + '|*.gif|*.gif' + '|*.bmp|*.bmp' + '|*.tif|*.tif' + '|*.wdp|*.wdp'; ImageHW = 384; // 表示枠サイズ RK = 0.298912; // グレースケール輝度係数 赤 GK = 0.586611; // グレースケール輝度係数 緑 BK = 0.114478; // グレースケール輝度係数 青 BISU = 1024; // 整数化倍数 RSISF = 10; // ビットシフト数 ColorMatrix_GrayScale : TColorMatrix = ( // GDI カラーコントロールマトリックス ( 0.298912, 0.298912, 0.298912, 0.0, 0.0), ( 0.586611, 0.586611, 0.586611, 0.0, 0.0), ( 0.114478, 0.114478, 0.114478, 0.0, 0.0), ( 0.0, 0.0, 0.0, 1.0, 0.0), ( 0.0, 0.0, 0.0, 0.0, 1.0)); var InBitmap : TBitmap; // ビットマップ OutBitmap : TBitmap; OutBitmap24 : TBitmap; OutBitmap4 : TBitmap; GHeight, GWidth : integer; // ソース画像サイズ Vrect : Trect; // 表示枠 IHeight, IWidth : Integer; // 表示サイズ RTemp : array[0..255] of Cardinal; // テンプレート用配列 赤 GTemp : array[0..255] of Cardinal; // テンプレート用配列 緑 BTemp : array[0..255] of Cardinal; // テンプレート用配列 青 // Tone に階調をセット 使用できるのは 256, 16, 2 です。 function CreateGrayScalePaletteS(Tone: WORD): HPALETTE; // Tone に階調をセット var Palette : ^TLogPalette; // TLogPalette のポインター i : Integer; BD : BYTE; begin GetMem(Palette, SizeOf(TLogPalette) + SizeOf(TPaletteEntry) * Tone); // TLogPaletteは固定長の配列なので必要なメモリ確保 Palette^.palNumEntries := Tone; // 階調セット Palette^.palVersion := $0300; // バージョンセット BD := 255 div (Tone - 1); // 階調差計算 for i := 0 to Tone - 1 do begin // 階調データーセット Palette^.palPalEntry[i].peRed := BD * i; Palette^.palPalEntry[i].peGreen := BD * i; Palette^.palPalEntry[i].peBlue := BD * i; Palette^.palPalEntry[i].peFlags := 0; // フラグは使用しない end; Result := CreatePalette(Palette^); FreeMem(Palette); end; // 16諧調 function Create16ScalePalette: HPALETTE; // グレー4ビットパレットに設定 const Tone = 16; // 16階調 var Palette: T16LogPalette; i: Integer; begin Palette.palNumEntries := Tone; // 16階調セット Palette.palVersion := $0300; // パレットバージョン for i := 0 to Tone - 1 do begin // パレットデーターの設定 Palette.palPalEntry[ i ].peRed := 17 * i; // 17 = 255 / 15 15 = Tone - 1 Palette.palPalEntry[ i ].peGreen := 17 * i; Palette.palPalEntry[ i ].peBlue := 17 * i; Palette.palPalEntry[ i ].peFlags := 0; // フラグは使用しない end; Result := CreatePalette(PLogPalette(@Palette)^); end; // 256諧調 function CreateGrayScalePalette: HPALETTE; // グレー8ビットパレットに設定 const Tone = 256; // 256階調 var Palette: TMaxLogPalette; i: Integer; begin Palette.palNumEntries := Tone; // 256階調セット Palette.palVersion := $0300; // パレットバージョン for i := 0 to Tone - 1 do begin // パレットデーターの設定 Palette.palPalEntry[ i ].peRed := i; Palette.palPalEntry[ i ].peGreen := i; Palette.palPalEntry[ i ].peBlue := i; Palette.palPalEntry[ i ].peFlags := 0; end; Result := CreatePalette(PLogPalette(@Palette)^); end; // NTSC 係数を1024倍してテンプレートデーター作成 procedure TForm1.TemplateMake; // Templateデーター作成 var II : Integer; begin for II := 0 to 255 do begin RTemp[II] := round(RK * BISU * II); // 赤テンプレート GTemp[II] := round(GK * BISU * II); // 緑テンプレート BTemp[II] := round(BK * BISU * II); // 青テンプレート end; end; // ファイルのオープン WIC がファイルの種類が多いので使用 procedure TForm1.FileOpenClick(Sender: TObject); var WIC : TWICImage; InFilename : String; begin GrayScaleBtn.Enabled := False; GrayScaleDBtn.Enabled := False; GrayScaleABtn.Enabled := False; GrayScale24Btn.Enabled := False; GrayScale4Btn.Enabled := False; GrayMatBtn.Enabled := False; VRect := Rect(0, 0, Image1.Width, Image1.Height); Image1.Canvas.Brush.Style := bsSolid; Image1.Canvas.Brush.Color := clBtnface; Image1.Canvas.FillRect(VRect); // Canvas 画像消去 Image2.Canvas.Brush.Style := bsSolid; Image2.Canvas.Brush.Color := clBtnface; Image2.Canvas.FillRect(VRect); // Canvas 画像消去 OpenPictureDialog1.Filter := OpenFileFilter; // ファイルオープンフィルターの設定 if OpenPictureDialog1.Execute then // ファイルが指定されたら begin WIC := TWICImage.Create; // TWICImageの生成 try InFilename := OpenPictureDialog1.FileName; // ファイル名の取得 WIC.LoadFromFile(InFilename); // 画像の読み込み GHeight := WIC.Height; // 画像高さ取得 GWidth := WIC.Width; // 画像幅 IWidth := ImageHW; // 出力先イメージ1の幅 IHeight := ImageHW; // 出力先イメージ1の高さ if GHeight <= GWidth then // 縦横比により出力サイズ設定 IHeight := Round(IWidth * GHeight / GWidth) else IWidth := Round(IHeight * GWidth / GHeight); Image1.Width := IWidth; Image1.Height:= IHeight; Image1.Picture.Bitmap.SetSize(IWidth, IHeight); // 表示用ビットマップサイズの設定 Image2.Width := IWidth; Image2.Height:= IHeight; Image2.Picture.Bitmap.SetSize(IWidth, IHeight); // 表示用ビットマップサイズの設定 VRect := Rect(0, 0, IWidth, IHeight); // 出力枠設定 Image1.Canvas.StretchDraw(VRect, WIC); // 出力枠に変倍出力 InBitmap.Width := GWidth; InBitmap.Height := GHeight; InBitmap.Canvas.Draw(0, 0, WIC); // DrawでInBitmapに入力画像設定フォーマット24ビットに変換されます OutBitmap.Width := GWidth; OutBitmap.Height := GHeight; OutBitmap24.Width := GWidth; OutBitmap24.Height:= GHeight; OutBitmap4.Width := GWidth; OutBitmap4.Height := GHeight; finally WIC.Free; // TWICImage 解放 end; end else Exit; GrayScaleBtn.Enabled := True; GrayScaleDBtn.Enabled := True; GrayScaleABtn.Enabled := True; GrayScale24Btn.Enabled := True; GrayScale4Btn.Enabled := True; GrayMatBtn.Enabled := True; end; // マトリックスを使用してグレー画像の生成 procedure TForm1.GrayMatBtnClick(Sender: TObject); var Gpgraph : TGPGraphics; GPImage : TGPImage; GPImageAttr : TGPImageAttributes; Astream : TMemoryStream; begin // GPImage := TGPImage.Create(InFilename); // TGPImageにファイル名関連付け // ファイルから読み込む代わりにメモリーへ出力しメモリーから読み込みます // メモリーストリームを作成し inbitmap を出力 Astream := TMemoryStream.Create; InBitmap.SaveToStream(Astream); // メモリーストリームから,TGPBitmapのオブジェクトに読み込み指定 GPImage := TGPBitmap.Create(TStreamAdapter.Create(AStream, soReference)); // GD+にTBitmapの描画ハンドル設定 Gpgraph := TGPGraphics.Create(OutBitmap24.Canvas.Handle); // TGPImageAttributesのオブジェクトを生成 GPImageAttr := TGPImageAttributes.Create; // 使用するマトリックスをアトリビュートに設定 GPImageAttr.SetColorMatrix(ColorMatrix_GrayScale); // TCanvasのStretchDrawに相当する // GpgraphのHandleがOutBitmap24.Canvasに指定されているのでOutBitmap24.Canvasに描画されます Gpgraph.DrawImage( // Gpgraph (OutBitmap24) に描画 GPImage, // 元画像 MakeRect(0, 0, GWidth, GHeight), // 出力位置 サイズ 0, 0, GPImage.GetWidth, GPImage.GetHeight, // 入力位置 サイズ UnitPixel, // ピクセル GPImageAttr); // アトリビュート Image2.Canvas.StretchDraw(Vrect, OutBitmap24); // Image1に Dbitmap変倍表示 //Createしたオブジェクトは解放処理します。 GPImageAttr.Free; GPImage.Free; Gpgraph.Free; Astream.Free; end; // パレットを使用せずに、24ビットカラーモード R,G,B に同じ値を書き込みます procedure TForm1.GrayScale24BtnClick(Sender: TObject); // パレットを使用せずRGB同じ値セット var II, JJ : Integer; InpPrgbarray : Prgbarray; OutParray : Prgbarray; OutInt : DWORD; OutByte : BYTE; begin for II := 0 to GHeight - 1 do begin InpPrgbarray := InBitmap.ScanLine[II]; // ラインポインタ設定 OutParray := OutBitmap24.ScanLine[II]; for JJ := 0 to GWidth - 1 do begin // 三色加算 OutInt := RTemp[InpPrgbarray[JJ].rgbtRed] // 赤テンプレート + GTemp[InpPrgbarray[JJ].rgbtGreen] // 緑テンプレート + BTemp[InpPrgbarray[JJ].rgbtBlue]; // 青テンプレート OutByte := OutInt shr RSISF; // 10ビット右シフト OutInt div 10 OutParray[JJ].rgbtBlue := OutByte; // 三色同じ値をセット OutParray[JJ].rgbtGreen := OutByte; OutParray[JJ].rgbtRed := OutByte; end; end; Image2.Canvas.StretchDraw(VRect, OutBitmap24); // 出力枠に変倍出力 end; // 16階調グレーパレットを使用して表示 procedure TForm1.GrayScale4btnClick(Sender: TObject); // 4ビット 16階調表示 var // NTSC係数使用 II, JJ : Integer; InpPrgbarray : Prgbarray; OutParray : Prgbarray; OutInt : DWORD; OutByte : BYTE; begin for II := 0 to GHeight - 1 do begin InpPrgbarray := InBitmap.ScanLine[II]; // ラインポインタ設定 OutParray := OutBitmap24.ScanLine[II]; for JJ := 0 to GWidth - 1 do begin // 三色加算 OutInt := RTemp[InpPrgbarray[JJ].rgbtRed] // 赤テンプレート + GTemp[InpPrgbarray[JJ].rgbtGreen] // 緑テンプレート + BTemp[InpPrgbarray[JJ].rgbtBlue]; // 青テンプレート OutByte := OutInt shr RSISF; // 10ビット右シフト OutInt div 1024 OutParray[JJ].rgbtBlue := OutByte; // 三色同じ値をセット OutParray[JJ].rgbtGreen := OutByte; OutParray[JJ].rgbtRed := OutByte; end; end; OutBitmap4.Canvas.Draw(0, 0, OutBitmap24); // 16階調パレットに描画 Image2.Canvas.StretchDraw(VRect, OutBitmap4); // 出力枠に変倍出力 end; // R,G,Bの平均値を使用して、表示 表示は256階調グレーパレット使用 procedure TForm1.GrayScaleABtnClick(Sender: TObject); // 平均値計算出力 var II, JJ : Integer; InpPrgbarray : Prgbarray; OutParray : Pbytearray; OutInt : WORD; begin for II := 0 to GHeight - 1 do begin InpPrgbarray := InBitmap.ScanLine[II]; // ラインポインタ設定 OutParray := OutBitmap.ScanLine[II]; for JJ := 0 to GWidth - 1 do begin // 三色加算 OutInt := InpPrgbarray[JJ].rgbtRed // 赤 + InpPrgbarray[JJ].rgbtGreen // 緑 + InpPrgbarray[JJ].rgbtBlue; // 青 OutParray[JJ] := OutInt div 3; // 平均値 OutInt div 3 end; end; Image2.Canvas.StretchDraw(VRect, OutBitmap); // 出力枠に変倍出力 end; // NTSC係数を使用して輝度計算 グレースケールパレット使用して表示 procedure TForm1.GrayScaleBtnClick(Sender: TObject); // グレースケール変換 var // NTSC係数使用 II, JJ : Integer; InpPrgbarray : Prgbarray; OutParray : Pbytearray; OutInt : DWORD; begin for II := 0 to GHeight - 1 do begin InpPrgbarray := InBitmap.ScanLine[II]; // ラインポインタ設定 OutParray := OutBitmap.ScanLine[II]; for JJ := 0 to GWidth - 1 do begin OutInt := RTemp[InpPrgbarray[JJ].rgbtRed] // 赤テンプレート + GTemp[InpPrgbarray[JJ].rgbtGreen] // 緑テンプレート + BTemp[InpPrgbarray[JJ].rgbtBlue]; // 青テンプレート OutParray[JJ] := OutInt shr RSISF; // 10ビット右シフト OutInt div 1024 end; end; Image2.Canvas.StretchDraw(VRect, OutBitmap); // 出力枠に変倍出力 end; // グレースケールパレットを使用して、直接変換表示 procedure TForm1.GrayScaleDBtnClick(Sender: TObject); // 直接出力 変換は システム任せ begin OutBitmap.Canvas.Draw(0, 0, InBitmap); // OutBitmapに描画、グレー変換はパレット任せ Image2.Canvas.StretchDraw(VRect, OutBitmap); // 出力枠に変倍出力 end; // 初期設定 procedure TForm1.FormCreate(Sender: TObject); begin GrayScaleBtn.Enabled := False; GrayScaleDBtn.Enabled := False; GrayScaleABtn.Enabled := False; GrayScale24Btn.Enabled := False; GrayScale4Btn.Enabled := False; Image1.Width := ImageHW; Image1.Height := ImageHW; Image2.Width := ImageHW; Image2.Height := ImageHW; InBitmap := TBitmap.Create; // 入力画像用 OutBitmap := TBitmap.Create; // 8ビット出力用 OutBitmap24 := TBitmap.Create; // 24ビット出力用 OutBitmap4 := TBitmap.Create; // 4ビット出力用 InBitmap.PixelFormat := pf24bit; // 24ビットカラーに設定 OutBitmap24.PixelFormat := pf24bit; // 24ビットカラーに設定 OutBitmap.PixelFormat := pf8bit; // 8ビットに設定 OutBitmap.Palette := CreateGrayScalePalette; // グレイ256階調に設定 OutBitmap4.PixelFormat := pf4bit; // 4ビットに設定 // OutBitmap4.Palette := Create16ScalePalette; OutBitmap4.Palette := CreateGrayScalePaletteS(16); // グレー16階調にパレット設定 TemplateMake; // Templateデーター作成 end; // ビットマップの解放 procedure TForm1.FormDestroy(Sender: TObject); begin InBitmap.Free; OutBitmap.Free; OutBitmap24.Free; OutBitmap4.Free; end; end.