背景差分法

 背景と前景画像の差分をとり、差分の大きい部分の前景画像部を取り出す方法です。
特に固定カメラ(監視カメラ)の画像処理で、画面に変化があった時のみ、画像を録画することにより、録画時間を長くすることが出来ます。
移動物体の検出などにも有効です。

背景差分0

 上図は背景差分法の実行例です。
車の部分を取り出そうとしましたか゛、カメラの僅かなブレ、風による草や木の揺れ、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.


   download 背景差分法.zip


画像処理一覧へ戻る

    最初に戻る