21
Eyl
2020

Python ve OpenCV ile konturları kullanarak hareket tespiti ve izleme

OpenCV kütüphanesi ile binary görüntü üzerindeki bilgiyi vektörel hale yani bir kontur dizisine dönüştürebiliriz. Bu özelliği kullanarak bir video üzerinde basitçe görüntü takibi yapılabilir. Aşağıdaki video, bu işlem için uygun ve kısa bir video olduğu için tercih edilmiştir.

Videoyu Python proje klasörümüze SecurityCam.mp4 adıyla kaydedelim. Oluşturduğumuz .py uzantılı Python script dosyamıza videoyu bir değişken adı ile aşağıdaki komut satırı ile yükleyelim.

videom = cv2.VideoCapture('SecurityCam.mp4')

Video dosyasının ilk karesini şimdilik frame1 ve frame2 adında iki farklı değişkene gönderiyoruz. İlk karede değişkenler aynı olsa da, while döngüsü içerisinde iki değişken ardışık iki kareyi temsil edecek şekilde kodlanacak.

ret, frame1 = videom.read()
ret, frame2 = videom.read()

Python’ da video işlemek için gerekli olan while döngüsünü   while(videom.isOpened()): şeklinde başlatalım. Bundan sonraki tüm işlemler doğal olarak bu döngü içerisinde çalışacak. Bu döngü video görüntüsünün son karesine kadar veya biz klavyeden çıkmak için gerekli tuşa basana kadar çalışacaktır.

Hareketli bir görüntüde bir nesnenin hareket edip etmediğini anlamanın en basit yolu ardışık iki frame’ i karşılaştırmaktır. Bu uygulamada; frame1 ve frame2 görüntülerinin arasındaki farkı hesaplayıp yeni bir matrise atacağız. Amacımız ardışık iki frame arasında matematiksel farkın olduğu pikselleri bulmak. Matematiksel olarak farklı pikseller bize videonun o bölümünde hareketli nesnelerin olduğu kısımları gösterir. Fark hesaplarken negatif değerle karşılaşmamak için mutlak fark hesaplayalım.

fark = cv2.absdiff(frame1, frame2)

Hesaplanan farkı görüntülersek hareket halindeki nesnelerin açık renk, diğer kısımların koyu renk olduğunu görürüz.

Seçilen bir pikselin frame1 ve frame2 deki değerleri aynı ise aradaki fark 0, değerler yakın ise fark 0′ a yakındır. Yani fark matrisinde söz konusu pikselin değeri 0 ise siyah renkte, 0′ a yakın değerde ise siyaha yakın renkte gösterilir. Ancak bir frame1 ve frame2 deki değerleri farklı ise, o pikselde bir sonraki karede değişiklik olmuş, yani hareketlilik olmuş demektir ve fark değeri 0’ dan büyük hesaplanacaktır. Hesaplanan fark matrisi, videonun BGR olması nedeniyle BGR yani 3 katmanlıdır. Ancak kontur hesaplamak için tek katmanlı bir görüntü gerekmektedir. Bu nedenle fark görüntüsünü aşağıdaki komut satırı ile gri tonlamalı forma çevirelim.

gri = cv2.cvtColor(fark, cv2.COLOR_BGR2GRAY)

Gri tonlamalı forma dönüşmüş matriste gürültü (noise) den kurtulmak gerekir. Çünkü görüntüde fark edilmese de, frame1 ile frame2 mutlak farkını hesaplarken bazı bölgelerde gürültü (noise) oluşmuş olabilir. Bu da kontur hesaplarken yarayışsız konturların da ortaya çıkmasına yol açabilir. Gürültüden kurtulmanın en basit yolu görüntüyü yumuşatmaktır. Aşağıdaki komut satırı ile Gauss yumuşatması kullanarak görüntüyü yumuşatalım.

blur = cv2.GaussianBlur(gri, (5,5), 0)

Gürültüden kurtulduğumuz matriste artık hareketli alanları belirleyebiliriz. Ancak kontur hesaplayabilmek için matrisi binary görüntüye dönüştürmek gerekiyor. Bunun için bir eşik değeri belirleyip thresholding işlemi uygulayalım.

_, esik = cv2.threshold(blur, 20, 255, cv2.THRESH_BINARY)

Burada, eşik değerini (threshold value) 20 seçerek değeri 20’ den büyük tüm pikselleri 1, diğer pikselleri 0’ a eşitleyip esik adında bir binary matrise attık. Burada eşik değerini tahmini olarak seçildi. Ancak gerçek zamanlı uygulamalarda bu değerin seçimi oldukça önemli bir konudur. Oluşturduğumuz esik matrisini görüntülersek:

Eşik matrisinde görüldüğü üzere artık videodaki hareketli nesneler seçilebiliyor. Ancak bu nesneleri daha belirgin hale getirirsek daha başarılı sonuç elde edebiliriz. Bunun için cv2.dilate() metodunu kullanarak eşik değeri üzerindeki alanları biraz genişletelim.

genis = cv2.dilate(esik, None, iterations = 3)

Genişletilmiş görüntü üzerinde kontur hesaplaması yapmak için aşağıdaki komut satırını kullanabiliriz.

kontur,_ = cv2.findContours(genis, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

Elde edilen kontur vektörel dizisi, genişletilmiş binary görüntüdeki beyaz alanların vektörel ifadesidir. Bu konturları dilersek cv2.drawContours() metoduyla çizdirebiliriz. Ancak bu kontur alanlarını dikdörtgen içine almak istiyoruz. Bunun için cv2.boundingRect() metodu ile kontur çerçeve noktalarını hesaplayıp cv2.rectangle() metoduyla etraflarına dikdörtgen çizebiliriz.

for k in kontur:
    (x, y, w, h) = cv2.boundingRect(k)
    if cv2.contourArea(k)  > 700:        
        cv2.rectangle(frame1, (x, y), (w+x, h+y), (0, 0, 255), 2)

Yukarıda if cv2.contourArea(k)  > 700 kullanılmasının sebebi, 700 piksellik bir alandan küçük konturların etrafına dikdörtgen çizilmemesini sağlamaktır. Çünkü burada bir ağacın yaprağının kıpırdaması bile bir kontur dizisi oluşmasını sağlar. Filtrelemeyi basitçe boyut kullanarak yapabiliriz. Sonuç olarak video oynatıldığında, 700 pikselden büyük hareketli nesnelerin dikdörtgen içine alındığını görebiliriz.

Tüm kaynak kod

import cv2
videom = cv2.VideoCapture('SecurityCam.mp4')

ret, frame1 = videom.read()
ret, frame2 = videom.read()

while(videom.isOpened()):     
    fark = cv2.absdiff(frame1, frame2)
    gri = cv2.cvtColor(fark, cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(gri, (5,5), 0)
    _, esik = cv2.threshold(blur, 20, 255, cv2.THRESH_BINARY)
    genis = cv2.dilate(esik, None, iterations = 3)
    kontur,_ = cv2.findContours(genis, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

    for k in kontur:
        (x, y, w, h) = cv2.boundingRect(k)
        if cv2.contourArea(k) > 700: 
            cv2.rectangle(frame1, (x, y), (w+x, h+y), (0, 0, 255), 2)

    cv2.imshow('feed', frame1)    
    frame1 = frame2
    ret, frame2 = videom.read()
 
   # Eğer q tuşuna basıldı ise oynatmayı durdur.
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

videom.release()
cv2.destroyAllWindows()