Do it yourself CCTV/Surveilance from scratch

Terinspirasi dengan tulisan tentang memasang sendiri sistem cctv dengan zoneminder saya jadi iseng membuat sistem serupa (bukan memasang lagi, ini membuat). Awalnya memang tidak berniat untuk ke arah sana, hanya ingin melakukan interpretasi citra dengan memanfaatkan webcam, tapi kemudian terhambat karena framerate yang lambat ketika awal-awal mencoba.


Pendahuluan

Oke, mari dimulai percobaannya. Spesifikasi kita kali ini adalah membuat sistem cctv yang mengambil gambar dari webcam dan dapat diakses melalui web server (ujung yang cukup generik, bisa diperluas dengan membuat mobile client yang mengakses http :D). Selain itu tiap gambar diarsipkan sehingga memungkinkan untuk pemrosesan yang tidak membutuhkan tenggat. Agar lebih sederhana, kita akan mendekomposisi sistem yang kita buat menjadi 3 modul :

  1. program pengakses webcam (GUI).
  2. program perantara (CLI, bisa jadi opsional, nanti akan dijelaskan alasannya).
  3. halaman web (HTML+PHP)

Alat dan Bahan
Berikutnya alat yang diperlukan :

  • web server (saya menggunakan xampp 1.5). modul yang diperlukan sebetulnya hanya apache dan php. database tidak diperlukan saat ini untuk menyederhanakan program.
  • compiler delphi (anda bisa menggunakan apapun sebetulnya, tapi saya menggunakan delphi)
  • A win32 machine. ya ya.. saya pakai API Windows tapi nggak pakai DirectX, cuma pakai VFW (Video for Windows). stok lama.
  • Webcam. USB atau apapun. sekali-lagi, demi kesederhanaan, saya hanya menggunakan 1 webcam (default) yang ada di laptop. πŸ˜€

Arsitektur sistem
gambaran umum sistem bisa dilihat dalam gambar berikut.
arsitektur cctv (7KB)
Program 1,2, dan 3 sesuai dengan penjelasan di awal.

Program 1

Program ini dibuat menggunakan delphi dan terdiri dari 1 modul webcam dan 1 form. berikut ini kode untuk unit pengakses webcam (konstanta dan prosedur sudah di-dietkan supaya hanya mengandung yang diperlukan saja).

unit _cam;

interface

uses
  Windows,
  Messages,
  Controls,
  ExtCtrls;

const
  WM_CAP_START      = WM_USER;
  WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10;
  WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11;
  WM_CAP_SAVEDIB    = WM_CAP_START + 25;
  WM_CAP_COPY       = WM_CAP_START + 30;
  WM_CAP_SET_SCALE  = WM_CAP_START + 53;
  WM_CAP_SET_PREVIEW = ( WM_CAP_START + 50 );
  WM_CAP_SET_OVERLAY = ( WM_CAP_START + 51 );
  WM_CAP_SET_PREVIEWRATE = ( WM_CAP_START + 52 );

type
  TCam = class
  protected
    hWndC: HWND;
    FIsCapturing: Boolean;
    procedure BeginCapture( Container: TWinControl );
    procedure EndCapture;
  public
  	constructor Create( Container: TWinControl );
    destructor Free;
  	property Handle:HWND read hwndc;
  	property Capturing:boolean read FIsCapturing;
    procedure CopyToClipboard;
    procedure SaveToFile( filename: string );
  end;

implementation

uses
  ClipBrd;

function capCreateCaptureWindowA( lpszWindowName: PCHAR;
  dwStyle: longint;
  x: integer;
  y: integer;
  nWidth: integer;
  nHeight: integer;
  ParentWin: HWND;
  nId: integer ): HWND; stdcall external
'AVICAP32.DLL';

{ TCam }

procedure TCam.BeginCapture( Container: TWinControl );
begin
  hWndC := capCreateCaptureWindowA( 'Capture Window',
    WS_CHILD or WS_VISIBLE,
    Container.Left,
    Container.Top,
    Container.Width,
    Container.Height,
    Container.Handle,
    0 );
  if hWndC <> 0 then begin
    SendMessage( hWndC, WM_CAP_DRIVER_CONNECT, 0, 0 );
    SendMessage( hWndC, WM_CAP_SET_SCALE, 1, 0 );
    SendMessage( hWndC, WM_CAP_SET_PREVIEWRATE, 24, 0 );
    SendMessage( hWndC, WM_CAP_SET_PREVIEW, 1, 0 );
    FIsCapturing := True;
  end;
end;

procedure TCam.CopyToClipboard;
begin
	if hWndC <> 0 then begin
    SendMessage( hwndc, WM_CAP_COPY, 0, 0 );
  end;
end;

procedure TCam.EndCapture;
begin
	FIsCapturing := False;
	if hWndC <> 0 then begin
    SendMessage( hWndC, WM_CAP_DRIVER_DISCONNECT, 0, 0 );
    hWndC := 0;
  end;
end;

procedure TCam.SaveToFile( filename: string );
begin
  if hWndC <> 0 then begin
    SendMessage( hWndC,
      WM_CAP_SAVEDIB,
      0,
      longint( pchar( filename ) )
      );
  end;
end;

constructor TCam.Create(Container: TWinControl);
begin
	inherited Create;
	BeginCapture(Container);
end;

destructor TCam.Free;
begin
	EndCapture;
end;

end.

sedangkan kode untuk formnya adalah sebagai berikut. satu hal yang perlu diperhatikan. form berikut saya buat StayOnTop karena kalau di-minimize atau tersembunyi, gambarnya tidak akan di-update. Hal lainnya adalah pengaturan webcam agar bisa didapat framerate yang tinggi (di laptop saya setelah beberapa fitur dimatikan seperti low-light boost dan color boost baru bisa berjalan di 25 FPS).

//unit1.dfm
object Form1: TForm1
  Left = 192
  Top = 107
  BorderStyle = bsDialog
  Caption = 'Form1'
  ClientHeight = 241
  ClientWidth = 321
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  FormStyle = fsStayOnTop
  OldCreateOrder = False
  OnCloseQuery = FormCloseQuery
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
  object Panel1: TPanel
    Left = 0
    Top = 0
    Width = 320
    Height = 240
    BevelOuter = bvNone
    Color = clBlack
    TabOrder = 0
  end
  object _proc: TTimer
    Enabled = False
    Interval = 48
    OnTimer = _procTimer
    Left = 56
    Top = 16
  end
end

dan code-behind-nya adalah sebagai berikut.

unit Unit1;

interface
uses
  Windows,
  Messages,
  SysUtils,
  Classes,
  Graphics,
  Controls,
  Forms,
  Dialogs,
  ExtCtrls,
  MPlayer,
  StdCtrls,
  citra,
  Spin,
  ClipBrd,
  _cam,
  jpeg,
  inifiles;

type
  TForm1 = class( TForm )
    Panel1: TPanel;
    _proc: TTimer;
    procedure FormCreate( Sender: TObject );
    procedure FormCloseQuery( Sender: TObject; var CanClose: Boolean );
    procedure _procTimer( Sender: TObject );
  private
    cam: TCam;
    bmp: TBitmap;
    jpg: TJpegImage;
    ini: TIniFile;
  public
  end;

var
  Form1             : TForm1;
  capmode           : integer;
  datapath          : string;

implementation
{$R *.DFM}

{ TForm1 }

procedure TForm1.FormCreate( Sender: TObject );
begin
  ini := TIniFile.Create( extractfilepath( application.ExeName ) + 'config.ini' );
  capmode := ini.ReadInteger( 'server', 'mode', 0 );
  if capmode = 2 then begin
    datapath := ini.ReadString( 'server', 'datadir', 'data' );
    if pos( ':', datapath ) = 0 then datapath := extractfilepath( application.ExeName ) + datapath;
  end;
  jpg := TJpegImage.Create;
  jpg.CompressionQuality := ini.ReadInteger('server','jpegquality', 40);
  _proc.Interval := 1000 div ini.ReadInteger('server', 'fps', 5);
  bmp := TBitmap.Create;
  if not Assigned( cam ) then cam := TCam.Create( Panel1 );
  _proc.Enabled := cam.Capturing;
end;

procedure TForm1.FormCloseQuery( Sender: TObject; var CanClose: Boolean );
begin
  CanClose := False;
  _proc.Enabled := false;
  jpg.Free;
  bmp.Free;
  if Assigned( cam ) then cam.Free;
  CanClose := True;
end;

procedure TForm1._procTimer( Sender: TObject );
begin
  if cam.Capturing then begin
    if capmode > 0 then begin
      cam.CopyToClipboard;
      if capmode = 2 then begin
        bmp.Assign( clipboard );
        jpg.Assign( bmp );
        jpg.SaveToFile( format( '%s\%.12d.jpg', [datapath, gettickcount] ) );
      end;
    end;
  end;
end;

end.

Program di atas akan menyimpan hasil rekaman dari webcam ke dalam file dalam format JPEG yang kualitasnya dapat diparameterkan sehingga ukuran file keluarannya dapat juga diatur. Nama file rekaman menggunakan gettickcount yang dikodekan menjadi string sepanjang 12 digit. Penggunaan angkan dimaksudkan agar memudahkan dalam mencari file terakhir tanpa harus menggunakan database. Untuk sementara semua hasil rekaman dari webcam disimpan dalam satu direktori (datadir). Selanjutnya bisa menggunakan struktur direktori yang mencerminkan tanggal (YYYY\MM\DD\) untuk memudahkan pengarsipan.
program di atas menggunakan file konfigurasi dengan nama config.ini yang lokasinya sama dengan lokasi executable.

[server]
#mode capture
#0:no save
#1:save to clipboard
#2:save to file (jpeg)
mode=2
datadir=data
jpegquality=40
fps=10

konfigurasi di atas akan membuat program webcam menyimpan 10 gambar tiap detik dengan kualitas kompresi sekitar 40 (untuk resolusi webcam 320×240 membutuhkan sekitar 5KB). Dari hasil hitung-hitungan. kalau 1 detik 10 gambar dikali 5 KB jadi 50. satu jam perlu 180MB. satu hari perlu 4GB. sedangkan 1 tahun perlu ~1.5TB. angka-angka tersebut adalah gambaran kasar dengan asumsi tiap detik direkam (biarpun tidak ada perubahan). untuk analisis lebih lanjut (membuat gambar-gambar yang tidak berubah) bisa dilakukan dengan membuat program lain yang bekerja pada direktori tersebut ^_^.

Web Interface

Berikutnya adalah program nomor 3 yaitu halaman web yang terdiri dari dua file yaitu index.html dan getimg.php.

<html>
	<head>
		<meta http-equiv="refresh" content="3" />
	</head>
	<body>
	<img src="getimg.php"/></body>
</html>
<?php
//file: getimg.php
$t = time().'.txt';
$result = system('cctv "C:\Program Files\Borland\Delphi7\Projects\cctv\data" > '.$t);
if (file_exists($t)){
	$result = file_get_contents($t);
	$img = urldecode(str_replace('%0D%0A', '', urlencode($result)));
	header('Content-type:image');
	echo file_get_contents($img);
	unlink($t);
}
?>

Perantara

Yang terakhir adalah program perantara. Ada alasan khusus mengapa saya membuat program perantara dibanding menjadikannya langsung dalam skrip PHP yaitu yang pertama terpikir. hehehe.. persoalannya adalah mengambil file terakhir (timestamp terbesar) dalam direktori yang membutuhkan iterasi ke tiap file (silakan coba buat versi skrip php-nya dan bandingkan dengan versi pemanggilan command-line).

program cctv;
{$APPTYPE CONSOLE}

uses
  SysUtils;

var
	tdir : string;

procedure pandu;
begin
  writeln( 'ERR: use cctv <datadir>' );
end;

function getlastfile( dir: string ): string;
var
  sr                : TSearchRec;
  FileAttrs         : integer;
  tmp               : string;
  mask              : string;
  cur, last         : longint;
begin
  result := '';
  last := -1;
  mask := '*.*';
  FileAttrs := faAnyFile or faDirectory;
  if FindFirst( dir + '\' + mask, FileAttrs, sr ) = 0 then begin
    repeat
      if ( Pos( '.', sr.Name ) <> 1 ) then begin
        tmp := dir + '\' + sr.Name;
        trystrtoint( changefileext( sr.Name, '' ), cur );
        if cur > last then begin
          result := tmp;
          last := cur;
        end;
      end;
    until FindNext( sr ) <> 0;
    FindClose( sr );
  end;
end;

begin
  if ParamCount < 1 then pandu;
  tdir := getlastfile( paramstr( 1 ) );
  if tdir <> '' then writeln( tdir );
end.

Penutup
Akhirnya selesai juga. pesan terakhir dari saya, tidak ada skinsyut hasil program karena subjek yang dijadikan skrinsyutnya (saya) sudah kusut :D. silakan dicoba sendiri, kalau ada yang nggak berhasil silakan komentar di sini (belum tentu saya bantu juga sih :D).

14 comments

  1. iang · Januari 29, 2009

    ah kecewa tak ada skrinsut xD

  2. dengxiang · Februari 14, 2009

    mas bagaimana cara mendeteksi warna pada live webcam

  3. aravir · Maret 18, 2009

    trims very much, it safe my live..

  4. jaya · Mei 14, 2009

    mas bisa lebih detail ga mas.. tolong kirimin ke email aku mas

  5. pebbie · Mei 14, 2009

    astaga, sudah segitu banyaknya kode masih dianggap belum detail?

  6. orge · Mei 21, 2009

    orang2 kaya anda,yang dibutuhkan negara ini…kalau anda calon presiden anda lah yang gw pilih…thanks for posting this post…
    regard

    Dian

  7. orge · Mei 21, 2009

    Lebih bagus lagi anda menambah screen shootnya..
    GO IGOS..
    btw,anda orang open sourcejuga ya?

  8. pebbie · Mei 22, 2009

    waktu post ini ditulis, yang ada skrinsut yang ‘tidak pantas’ dikonsumsi umum jadi skrinsutnya nggak ditampilin *ngeles* πŸ˜€

    waduh, ogah ah jadi presiden..

    mm, saya nggak tau orang opensource itu yang kayak apa.. πŸ˜› saya cuma bagi2 source code doang (sebetulnya sih cuma catatan pribadi karena nggak tau mo ngisi blognya sama apa hehehe).. πŸ˜›

  9. orge · Juli 1, 2009

    pebbie,ada refrensi tentang webcam or program diatas?

  10. pebbie · Juli 1, 2009

    itu pake referensi lama VFW (Video for Windows).. ada di MSDN πŸ˜€

  11. orge · Juli 4, 2009

    pebbie..saya sedang tugas akhir,tapi saya menggunakan VB itu doank bedanya..saaya juga menggunakan PHP..kalau saya buat motion detection,kalau ada gerak baru webcam ambil gambar..tidak secara terus menerus..dan saya ambil cara dengan membandingkna 2 gambar..pebbie minta saran , tips , tutorial and lain – lainnya donk karena pebbie pernah buat project kaya diatas itu…Thanks ya pebbie..Btw , ada FB tidak?

    Regards

    Ian

  12. randurkgames · Januari 13, 2010

    gak ada contoh gambarnya ya mas (photo) ?

  13. arjuna · Februari 27, 2010

    hehe…

    nice.
    saya suka..

    keep movin’ forward kk!!
    kalo pake vcl tscap32 bs g kak..?

    • pebbie · Maret 2, 2010

      thx
      bisa juga kok pake tscap32, yang penting kan dapet gambarnya πŸ˜€

Tinggalkan Balasan ke iang Batalkan balasan

Situs ini menggunakan Akismet untuk mengurangi spam. Pelajari bagaimana data komentar Anda diproses.