Tracking Using Local Binary Pattern

Oke, terakhir kali saya membuat tulisan di blog ini adalah akhir april. Jeda yang cukup panjang untuk memulai membuat tulisan. Ada banyak hal yang terjadi pada saya selama masa jeda tersebut. Mulai dari kegiatan diklat prajabatan CPNS pada bulan mei, panggilan wawancara beasiswa dikti di bulan juni, hingga akhirnya per tanggal 1 Oktober ini saya resmi menjadi abdi negara yang bertugas di kampus.

#devfestjkt

Pada penghujung bulan september kemarin, saya mendapat kesempatan berbicara pada acara Google DevFest di Jakarta. Awalnya memang saya berniat mengisi acara Google DevFest yang di Bandung akhir minggu ini. Pada acara di jakarta saya diminta membahas tentang Computer Vision. Saya sendiri memang suka ngoprek citra untuk diutak-atik, tapi untuk menjadi pembicara tentang computer vision sepertinya kompetensi saya masih jauh dari seorang pakar. Oleh sebab itu dan karena yang meminta adalah rekan satu almamater, saya menyetujui dan mencoba tampil sebagai seorang enthusiast bukan sebagai expert.

Awalnya saya agak grogi karena tidak terbiasa berbicara membawakan materi teknik di hadapan orang yang banyak. Mengajar di kampus pun paling banyak mungkin hanya 100-an, itupun kuliah wajib. Kalau menjadi dosen pengganti di kuliah “Interpretasi dan Pengolahan Citra”, pesertanya jauh lebih sedikit lagi. Mungkin bisa dihitung dengan jari. OK, jari-jari di kedua tangan.🙂 Di luar perkiraan, ternyata responnya cukup memberi semangat.

Nah, ceritanya di acara itu saya menampilkan beberapa video yang sudah diproses dengan menggunakan OpenCV sebagai demonstrasi hal-hal yang bisa dilakukan dengan menggunakan teknik-teknik computer vision. Nah, salah satu video yang saya tampilkan sebetulnya adalah pengembangan dari apa yang pernah saya tuliskan di sini. Karena keterbatasan waktu dan tema presentasi, di acara itu saya hanya sempat menampilkan videonya saja. Oleh sebab itu di tulisan ini saya akan bedah bagaimana hal tersebut dilakukan.

frame pertama

frame pertama

frame terakhir

frame terakhir



(empat paragraf untuk pengantar, saya sepertinya harus meningkatkan kemampuan untuk to the point)

Saya ucapkan terima kasih kalau sempat membaca tulisan di atas dan selamat kalau tidak. Sekarang saya akan membahas mengenai penjejakan (tracking) dengan menggunakan fitur LBP (Local Binary Pattern). Tulisan ini akan memanfaatkan apa yang pernah saya tulis sebelumnya mengenai LBP dan eksperimen kecil untuk melakukan tracking pada wajah menggunakan nilai piksel mentah-mentah (raw pixel’s grayscale value) yang di-update terus menerus dengan menggunakan teknik blending. Model state penjejakan masih sama yaitu hanya terbatas pada penjejakan posisi. parameter skala (lebar dan tinggi) tidak dimodelkan dan diasumsikan tetap karena pada video tersebut posisi kamera dan objek tetap (ada sedikit zoom-in tetapi tidak seberapa).

Berdasarkan eksperimen tersebut, proses deteksi masih harus dilakukan pada selang waktu tertentu. Kali ini saya hanya melakukan deteksi di awal frame saja. Frame selanjutnya akan ditentukan oleh proses tracking. seperti terlihat pada gambar di atas, gambar atas menampilkan area wajah yang didapat dari proses deteksi (lbp cascade detector). gambar di bawahnya merupakan area wajah hasil penjejakan. Pada bagian kiri atas gambar terdapat dua buah gambar kecil yang menampilkan model target yang digunakan. Ada dua model target yaitu model tekstur yang dibuat konstan dan model penampakan (appearance yang di-update tiap pergantian frame. Model tekstur diambil dari ekstraksi fitur LBP terhadap area hasil deteksi pada frame pertama. Model penampakan di-update dengan menggunakan rata-rata model sekarang dengan area hasil estimasi saat ini (persis seperti yang dibahas pada tulisn sebelumnya).

Dari paparan di atas, ada dua hal yang jadi pertanyaan:

  • Mengapa model tekstur tidak di-update? dan
  • Mengapa model penampakan harus di-update?

Saya akan bahas mulai dari pertanyaan kedua, yaitu “mengapa model penampakan harus di-update?”. Seperti yang sudah pernah dibahas di tulisan sebelumnya. Walaupun dengan melakukan pencocokan templat dengan fungsi perbandingan Squared Difference, hasil penjejakan masih akan terjadi drifting atau bahasa sederhananya, penjejakannya akan terpeleset menjauh dari lokasi yang seharusnya sehingga masih perlu dilakukan inisialisasi ulang dengan cara menjalankan detektor wajah. Proses ini masih belum optimal.

Jawaban atas pertanyaan pertama sebetulnya disebabkan oleh sifat dari LBP sendiri. Saya sempat mencoba melakukan hal yang sama seperti untuk model penampakan yaitu melakukan blending antara model yang didapat ketika inisialisasi dan fitur setelah tracking dan ternyata hasilnya justru terpeleset, bahkan lebih parah. Kalau ditinjau kembali, ternyata memang struktur dari fitur LBP sendiri memang tidak bisa semudah itu dilakukan penghitungan rata-rata untuk melakukan update. Fitur LBP dibentuk dari fungsi tanda (sign) yang diasosiasikan dengan posisi bit pada fitur akhir. Walaupun fungsi jarak (metrik euclidean) tetap bisa digunakan untuk menentukan kesamaan, tetapi interpolasi antara dua pola tidak dapat menggunakan interpolasi linier. Pola bit tersebut dapat dianggap sebagai string bit sehingga interpolasi dapat dilakukan dengan melakukan string editing dan fungsi jarak yang menurut saya paling tepat adalah jarak string yang untuk pola bit seperti ini dapat menggunakan Hamming distance.

Tanpa banyak basa-basi lagi, mari kita langsung menuju kode untuk melakukan tracking tersebut.

import numpy as np
import cv2
import cv
from scipy.weave import inline

MASK = np.array([[0,-1],[1,-1],[1,0],[1,1],[0,1],[-1,-1],[-1,0],[-1,1]])
def calc_lbp(src, dst,thr=0):
    code = r"""
        for (int y=1; y<Nsrc[0]-1; ++y){
            for (int x=1; x<Nsrc[1]-1; ++x){
                unsigned char px = SRC2(y,x);
                unsigned char n = 0;
                unsigned abstot = 0;
                for(int m=0; m<8; ++m) {
                     int delta = SRC2(y+MASK2(m,1),x+MASK2(m,0))-px;
                    abstot += abs(delta);
                    if(delta>0) n |= 1 << m;
                }
                if (abstot<thr) n = 0;
                DST2(y,x) = n;
            }
        }
    """
    inline(code, ['src','dst','MASK','thr'], compiler='gcc')
    return dst
 
video_src = 0 #webcam
video_src = r"angklung\angklung.avi"
cascade_fn = "lbpcascade_frontalface.xml"
#cascade_fn = "haarcascade_frontalface_alt.xml"
cascade = cv2.CascadeClassifier(cascade_fn)
cam = cv2.VideoCapture(video_src)
gotface = False
lbp = None

def search_window(x,y,w,h,window_size,maxw,maxh):
    y1 = max(y-window_size, 0)
    x1 = max(x-window_size, 0)
    x2 = min(x+w+window_size, maxw)
    y2 = min(y+h+window_size, maxh)
    return x1,y1,x2,y2
 
framectr = 0
while True:
    ret, img = cam.read()

    if not ret: break
    framectr += 1
    gray = cv2.cvtColor(img, cv.CV_BGR2GRAY)
    if lbp is None: lbp = gray.copy()
    calc_lbp(gray, lbp)
    imgh,imgw = gray.shape

    timeout = False
    if not gotface or timeout: #detect a face
        print "detecting face.."
        framectr = 0
        rects = cascade.detectMultiScale(img, scaleFactor=1.1, minNeighbors=2, minSize=(20, 20))
        if len(rects)>0: 
            gotface = True
            x,y,width,height = rects[0]
            x2,y2=x,y
            x3,y3=x,y
            #create the first template for tracking from detected area
            face = np.array([0]*width*height, dtype=np.uint8).reshape((width,height))
            facelbp = face.copy()
            face[:,:] = gray[y:y+height,x:x+width]
            facelbp[:,:] = lbp[y:y+height,x:x+width]
            face2 = face.copy()
            face3 = face.copy()
    else: #track that face
        #window enlargement value to be used as search area
        wnd = min(width, height)*2/3
        #track using squared difference measurement
        sw = search_window(x,y,width,height,wnd,imgw,imgh)
        result = cv2.matchTemplate(lbp[sw[1]:sw[3],sw[0]:sw[2]], facelbp, cv.CV_TM_SQDIFF)

        result2 = cv2.matchTemplate(gray[sw[1]:sw[3],sw[0]:sw[2]], face, cv.CV_TM_SQDIFF)
        merged = (result.max()-result)*result2
        merged = result*result2

        yy,xx = np.unravel_index(merged.argmin(), merged.shape)
        merged -= merged.min()
        merged /= merged.max()
        todraw = cv2.cvtColor((merged*255).astype(np.uint8), cv.CV_GRAY2BGR)
        img[-merged.shape[0]:,:merged.shape[1]] = todraw[:,:]

        x,y = sw[0] + xx, sw[1] + yy
        alpha = 0.4 #blending factor for template updating
        if (y+height<imgh) and (x+width<imgw): face[:,:] = face*alpha + (1.0-alpha) * gray[y:y+height, x:x+width]
    
    if gotface: #display tracked face
        cv2.rectangle(img, (x, y), (x+width, y+height), (255,0,0), 2)

    todraw = cv2.cvtColor(facelbp, cv.CV_GRAY2BGR)
    img[:height,:width] = todraw[:,:]

    todraw = cv2.cvtColor(face, cv.CV_GRAY2BGR)
    img[height:height+height,:width] = todraw[:,:]

    cv2.imshow('facedetect',img)

    img2 = img.copy()
    k = cv2.waitKey(1)
    if k == 27: break

Dibandingkan dengan kode tracking sebelumnya, kode di atas ada tambahan fungsi untuk menghitung fitur lbp yang diimplementasi dalam C++ menggunakan scipy.weave . Selain itu kode untuk menghitung jarak pada model penampakan hanya menggunakan satu metode (SQDIFF) dan keputusan akhir penjejakan dilakukan dengan menggabungkan (fusion) hasil dari model tekstur dengan model penampakan. Penggabungan dilakukan dengan melakukan perkalian dan posisi nilai minimum menentukan posisi di tempat yang baru. Hasil penggabungan pencocokan templat ini diperlihatkan di pojok kiri bawah pada gambar ‘frame terakhir’. Area yang gelap menunjukkan jarak yang kecil (dekat/mirip).

Tinggalkan Balasan

Isikan data di bawah atau klik salah satu ikon untuk log in:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s