背景差分法
背景と前景画像の差分をとり、差分の大きい部分の前景画像部を取り出す方法です。
特に固定カメラ(監視カメラ)の画像処理で、画面に変化があった時のみ、画像を録画することにより、録画時間を長くすることが出来ます。
移動物体の検出などにも有効です。
上図は背景差分法の実行例です。
車の部分を取り出そうとしましたか゛、カメラの僅かなブレ、風による草や木の揺れ、jpeg圧縮された画像データー等の為、あまりきれいに取り出すことは出来ていません。
画像は、640×480=307200 です。
図中にある画素数は、差分として検出した画素数ですので、この値を使用して、移動体を検出したかどうかの判定に使用することが出来ます。
ここで使用したのは、カラー画像ですが、監視カメラのモノクロ画像の場合は、単に一色の値の差になりまし、HSLによる差分計算はありません。
プログラム
unit Main; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, system.Types, Vcl.ExtDlgs, Vcl.ExtCtrls; type TForm1 = class(TForm) Button1: TButton; OpenPictureDialog1: TOpenPictureDialog; Button2: TButton; Button3: TButton; LabeledEdit1: TLabeledEdit; LabeledEdit2: TLabeledEdit; LabeledEdit3: TLabeledEdit; Image1: TImage; LabeledEdit4: TLabeledEdit; LabeledEdit5: TLabeledEdit; LabeledEdit6: TLabeledEdit; LabeledEdit7: TLabeledEdit; LabeledEdit8: TLabeledEdit; RadioButton1: TRadioButton; RadioButton2: TRadioButton; Label1: TLabel; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure Button2Click(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Button3Click(Sender: TObject); private { Private 宣言 } procedure FileOpen(InF : Boolean); public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} uses system.UITypes, system.UIConsts, Vcl.Imaging.GIFImg; var BaseBM, DrawBM, NewPic: TBitmap; InF : boolean; inpf : byte; const // ファイル拡張子設定 OpenFileFilter = '画像ファイル|*.png;*.jpg;*.gif;*.bmp;*.tif;*.ico;*.wdp'+ '|*.png|*.png' + '|*.jpg|*.jpg' + '|*.gif|*.gif' + '|*.bmp|*.bmp' + '|*.tif|*.tif' + '|*.ico|*.ico' + '|*.wdp|*.wdp'; //---------------------------------------------------------------- // difference_Bitmap 2枚のビットマップの差分による画像取り出し // BaseBM 背景のビットマップ // uperBM BaseBMの上の前景ビットマップ // X, Y uperBMを描画するBaseBM上の座標 // R, G, B 赤、緑、青 差分閾値 // H, S, L 色相、彩度、輝度 // HF false 色信号RGB true 色相 H S L //---------------------------------------------------------------- function difference_Bitmap(BackBM, UperBM: TBitmap; X, Y: integer; R, G, B: smallint; H, S, L: single; HF: Boolean): TBitmap; type TAlphaarry = array[0..0] of TAlphaColor; // TAlphaColor配列 4byte PAlphaarray = ^TAlphaarry; // 配列のポインター var PBase, PDraw: PRGBQuadArray; // スキャンラインのキャッシュ用ポインタ Hbase, HDraw: PAlphaarray; // スキャンラインのキャッシュ用ポインタ CutBase, CutDraw: TBitmap; // 編集用ビットマップ Row, Col : integer; // 配列スキャン用 Rb, Gb, Bb: Smallint; Ru, Gu, Bu: Smallint; Ri, Gi, Bi: Smallint; Rect1, Rect2: TRect; // 四角領域 Xi, Yi: integer; // 前景画像範囲先頭位置計算用 BColor, UColor : TAlphaColor; Hb, Sb, Lb : single; Hu, Su, Lu : single; Hi, Si, Li : single; Pcount : integer; // 検出ピクセルの数 begin Result := BackBM; // 戻り値背景画像 // 背景画像の四角形領域 Rect1 := Rect(0, 0, BackBM.Width, BackBM.Height); // 前景画像の領域 画像範囲外あり Rect2 := Rect(X, Y, UperBM.Width + X, UperBM.Height + Y); // 背景画像の画像の重なる範囲計算 Rect1 if not IntersectRect(Rect1, Rect1, Rect2) then begin ShowMessage('二つの画像が重なる部分がありません'); Exit; end; // NewPic0にBackBMビットマップイメージコピー NewPic.Assign(BackBM); // 前景画像の重なる範囲計算 Rect2 Xi := -X; // 左範囲外符号反転 if X >= 0 then Xi := 0; // Xがプラスの場合左側0 Yi := -Y; // 上範囲外符号反転 if Y >= 0 then Yi := 0; // Yがプラスの場合上側0 Rect2 := Rect(Xi, Yi, Rect1.Right - X, Rect1.Bottom - Y); // 編集用ビットマップ生成 CutBase := TBitmap.Create; CutDraw := TBitmap.Create; // 画像の合成 try CutBase.Width := Rect1.Right - Rect1.Left; // 画像の重なる部分の背景画像幅 CutBase.Height := Rect1.Bottom - Rect1.top; // 画像の重なる部分の背景画像高さ CutBase.PixelFormat := pf32bit; CutDraw.Width := Rect1.Right - Rect1.Left; // 画像の重なる部分の前景画像幅 CutDraw.Height := Rect1.Bottom - Rect1.top; // 画像の重なる部分の前景画像高さ CutDraw.PixelFormat := pf32bit; // 背景画像から重なる部分の画像コピー CutBase.Canvas.CopyRect(Rect(0, 0, CutBase.Width, CutBase.Height), Newpic.Canvas, Rect1); // 前景画像から重なる部分の画コピー CutDraw.Canvas.CopyRect(Rect(0, 0, CutDraw.Width, CutDraw.Height), UperBM.Canvas, Rect2); // 重なる部分の差分によるコピー // RGBによる設定 Pcount := 0; if not HF then begin for Col :=0 to CutBase.Height -1 do begin PBase := CutBase.ScanLine[Col]; PDraw := CutDraw.ScanLine[Col]; for Row := 0 to CutBase.Width -1 do begin // 各色の値取り出し Rb := PBase[Row].rgbRed; Gb := PBase[Row].rgbGreen; Bb := PBase[Row].rgbBlue; Ru := PDraw[Row].rgbRed; Gu := PDraw[Row].rgbGreen; Bu := PDraw[Row].rgbBlue; // 差分計算 Ri := ABS(Rb - Ru); Gi := ABS(Gb - Gu); Bi := ABS(Bb - Bu); // 差分による色の設定 指定値より差が大きかったら各色のコピー if (Ri > R) or (Gi > G) or (Bi > b) then begin PBase[Row].rgbRed := PDraw[Row].rgbRed; PBase[Row].rgbGreen := PDraw[Row].rgbGreen; PBase[Row].rgbBlue := PDraw[Row].rgbBlue; inc(Pcount); end // 差が指定値より小さかったら白に設定 else begin PBase[Row].rgbRed := 255; PBase[Row].rgbGreen := 255; PBase[Row].rgbBlue := 255; end; PBase[Row].rgbReserved := 0; end; end; end // HSLによる設定 else begin for Col := 0 to CutBase.Height -1 do begin HBase := CutBase.ScanLine[Col]; HDraw := CutDraw.ScanLine[Col]; for Row := 0 to CutBase.Width -1 do begin // 色の値4byte取り出し BColor := HBase[Row]; UColor := HDraw[Row]; // HSL変換 RGBtoHSL(BColor, Hb, Sb, Lb); RGBtoHSL(UColor, Hu, Su, Lu); // 差分計算 hi := abs(hb - hu); // 色相の差分 if hi > 0.5 then hi := 1 - Hi; // 色相の差分最大値は0.5 色相は 0~1 リングデーターです Si := abs(Sb - Su); // 彩度の差分 Li := abs(Lb - Lu); // 輝度の差分 // 差分による色設定 if (Hi > H) or (Si > S) or (Li > L) then begin HBase[Row] := HDraw[Row] and $00FFFFFF; inc(Pcount); end else begin // R G B HBase[Row] := $00FFFFFF; // 不透明白に設定 end; end; end; end; // 重なる部分の画像を裏画像にコピー NewPic.Canvas.CopyRect(Rect1, CutBase.Canvas, rect(0, 0, CutBase.Width, CutBase.Height)); Result := NewPic; // 戻り値合成画像 Form1.label1.Caption := '検出数' + intTostr(Pcount); finally // 編集用ビットマップの解放 CutBase.Free; CutDraw.Free; end; end; procedure TForm1.FileOpen(InF : Boolean); var WIC : TWICImage; begin OpenPictureDialog1.Filter := OpenFileFilter; // ファイルオープンフィルターの設定 if OpenPictureDialog1.Execute then // ファイルが指定されたら begin WIC := TWICImage.Create; // TWICImageの生成 try WIC.LoadFromFile(OpenPictureDialog1.FileName);// 画像の読み込み // 前景画像読み込み if InF then begin DrawBM.Width := WIC.Width; // 画像幅 DrawBM.Height := WIC.Height; // 画像高さ DrawBM.Canvas.Draw(0, 0, WIC); // DrawでInBitmapに入力画像設定フォーマット24ビットに変換されます inpf := inpf or 1; // 前景画像読み込み済み設定 end // 背景画像読み込み else Begin BaseBM.Width := WIC.Width; // 画像幅 BaseBM.Height := WIC.Height; // 画像高さ BaseBM.Canvas.Draw(0, 0, WIC); // DrawでInBitmapに入力画像設定フォーマット24ビットに変換されます image1.Width := WIC.Width; // 描画Image枠設定 image1.Height := WIC.Height; // 背景画像読み込み済み設定 inpf := inpf or 2; end; finally WIC.Free; // TWICImage 解放 end; end; if inpf and 3 = 3 then Button1.Enabled := true; // 背景画像と前景画像が読み込まれたら合成ボタンイネーブル end; procedure TForm1.Button1Click(Sender: TObject); var X, Y, Ch: integer; R, G, B: smallint; H, S, L: single; HF : boolean; begin val(LabeledEdit1.Text, X, Ch); if ch <> 0 then begin application.MessageBox('横位置の値に間違いがあります。','入力ミス', 0); exit; end; val(LabeledEdit2.Text, Y, Ch); if ch <> 0 then begin application.MessageBox('縦位置の値に間違いがあります。','入力ミス', 0); exit; end; HF := False; R := 0; G := 0; B := 0; H := 0; S := 0; L := 0; if RadioButton1.Checked then begin val(LabeledEdit3.Text, R, Ch); if ch <> 0 then begin application.MessageBox('赤差分値に間違いがあります。','入力ミス', 0); exit; end; if (R < 0) or (R > 255) then begin application.MessageBox('赤差分値が範囲外です。','入力ミス', 0); exit; end; val(LabeledEdit4.Text, G, Ch); if ch <> 0 then begin application.MessageBox('緑差分値に間違いがあります。','入力ミス', 0); exit; end; if (G < 0) or (G > 255) then begin application.MessageBox('緑差分値が範囲外です。','入力ミス', 0); exit; end; val(LabeledEdit5.Text, B, Ch); if ch <> 0 then begin application.MessageBox('青差分値に間違いがあります。','入力ミス', 0); exit; end; if (B < 0) or (B > 255) then begin application.MessageBox('青差分値が範囲外です。','入力ミス', 0); exit; end; end; if RadioButton2.Checked then begin val(LabeledEdit6.Text, H, Ch); if ch <> 0 then begin application.MessageBox('色相差分値に間違いがあります。','入力ミス', 0); exit; end; if (H < 0) or (H > 360) then begin application.MessageBox('色相差分が範囲外です。','入力ミス', 0); exit; end; H := H / 360; val(LabeledEdit7.Text, S, Ch); if ch <> 0 then begin application.MessageBox('彩度差分値に間違いがあります。','入力ミス', 0); exit; end; if (S < 0) or (S > 1) then begin application.MessageBox('彩度差分値が範囲外です。','入力ミス', 0); exit; end; val(LabeledEdit8.Text, L, Ch); if ch <> 0 then begin application.MessageBox('輝度差分値に間違いがあります。','入力ミス', 0); exit; end; if (L < 0) or (L > 1) then begin application.MessageBox('輝度差分値が範囲外です。','入力ミス', 0); exit; end; HF := True; end; // 画像の合成 Image1.Canvas.Draw(0, 0, difference_Bitmap(BaseBM, DrawBM, X, Y, R, G, B, H, S, L, HF)); end; procedure TForm1.Button2Click(Sender: TObject); begin InF := False; // 背景画像読み込みフラグ FileOpen(InF); end; procedure TForm1.Button3Click(Sender: TObject); begin InF := True;; // 前景画像読み込みプラグ FileOpen(InF); end; procedure TForm1.FormDestroy(Sender: TObject); begin // ビットマップの解放 BaseBM.Free; DrawBM.Free; NewPic.Free; end; procedure TForm1.FormCreate(Sender: TObject); begin // ビットマップの生成 NewPic := TBitmap.Create; BaseBM :=TBitmap.Create; DrawBM :=TBitmap.Create; BaseBM.PixelFormat := pf32bit; DrawBM.PixelFormat := pf32bit; // 画像読み込みフラグクリア グローバル変数なので無くても可 inpf := 0; end; end.