5 Haziran 2016 Pazar

Fare Hareketlerini ve Tıklamaları Yakalamak (OpenCV & C++)

Bu yayınımızda da fare hareketlerini nasıl yakalayacağımıza bakalım. Bu yayında, pencerede bir resim göstereceğiz ve o resim üzerinde faremizin hareket ve tıklama koordinatlarını yakalamayı göreceğiz.

Başlangıç olarak yine C: altına "calisma" adında bir klasör açalım ve üzerinde çalışacağımız bir jpg dosyasını bu klasörün altına koyalım. Adına da resim1.jpg diyelim.

#include "stdafx.h"
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

void fareOlaylari(int olay, int x, int y, int flag, void* kullaniciBilgisi) {
    if (olay == EVENT_LBUTTONDOWN) cout << "Sol tiklandi - pozisyon (" << x << ", " << y << ")" << endl;
    else if (olay == EVENT_RBUTTONDOWN) cout << "Sag tiklandi - pozisyon (" << x << ", " << y << ")" << endl;
    else if (olay == EVENT_MBUTTONDOWN) cout << "Orta tiklandi - pozisyon (" << x << ", " << y << ")" << endl;
    else if (olay == EVENT_MOUSEMOVE) cout << "Fare hareketi - pozisyon (" << x << ", " << y << ")" << endl;
}

int main() {
    Mat resim = imread("C:/calisma/resim1.jpg");
    if (resim.empty()) { cout << "Resim yuklenemedi" << endl; system("Pause"); return -1; }
    namedWindow("Pencere");
    setMouseCallback("Pencere", fareOlaylari, NULL);
    imshow("Pencere", resim);
    waitKey(0);
    return 0;
}


Burada açıklamam gerekecek çok bir yer yok sanıyorum.

fareOlaylari() adında bir fonksiyon oluşturuyoruz. Pencere üzerinde bir fare olayı (hareket, tıklama) gerçekleştiğinde bu fonksiyon çalışıyor ve fonksiyonun içinde de olaya göre ekrana olayın ne olduğunu ve fare koordinatlarını yazdırıyoruz.

void fareOlaylari(int olay, int x, int y, int flag, void* kullaniciBilgisi) { ... }
Bu fonksiyonda bu parametrelerin hepsini almak zorunda mıydık? Evet. Bu parametreleri bu sırayla almazsanız, kodunuz hata verecektir. Çünkü bu fonksiyonu çağıran setMouseCallback() fonksiyonu, fonksiyonumuza bu parametreleri bu sırayla göndermek istiyor. Herhangi bir parametreyi sildiğinizdeyse setMouseCallback() fonksiyonu hata veriyor.
Fare olayları sadece bu tıklamalar ve koordinat bilgileri değil tabii... Bunları biraz sonra belirteceğim.

Mat resim = imread("C:/calisma/resim1.jpg");
if (resim.empty()) { cout << "Resim yuklenemedi" << endl; system("Pause"); return -1; }
Burada resmimizi resim adlı Mat nesnemize alıyoruz.
Eğer alamadıysak (resimde bir problem varsa veya resmi o adreste bulamadıysa mesela) bu if blokuna düşüyoruz ve ekrana "Resim yuklenemedi" yazdırıyoruz. Bu yazıyı görebilelim diye sisteme bir tuşa basılıncaya kadar beklemesi için "Pause" diyoruz. Sonra da programımız -1 döndürerek sonlanıyor.

setMouseCallback("Pencere", fareOlaylari, NULL);
Bu fonksiyon ile, "Pencere" adlı penceremiz üzerinde herhangi bir fare olayı gerçekleşirse, fareOlaylari() fonksiyonumuzu çağırıyor. Son parametreye NULL diyoruz.

Şimdi şu fareOlaylari() fonksiyonumuza tekrar göz atalım.

void fareOlaylari(int olay, int x, int y, int flag, void* kullaniciBilgisi) { ... }
Burada alınan parametrelerin mutlaka bulunması gerektiğini söylemiştik. Hangisi ne iş yapıyor ona bakalım.
1) int olay   ~ Bu parametreye, farenin yaptığı olayın kodu gönderilmektedir. Bu olaylar şunlardır:

EVENT_MOUSEMOVEFare hareket ediyor
EVENT_LBUTTONDOWNFareye sol tıklandı
EVENT_RBUTTONDOWNFareye sağ tıklandı
EVENT_MBUTTONDOWNFarenin orta butonuna tıklandı
EVENT_LBUTTONUPFarenin sol butonu bırakıldı
EVENT_RBUTTONUPFarenin sağ butonu bırakıldı
EVENT_MBUTTONUPFarenin orta butonu bırakıldı
EVENT_LBUTTONDBLCLKFarenin sol butonuna çift tıklandı
EVENT_RBUTTONDBLCLKFarenin sağ butonuna çift tıklandı
EVENT_MBUTTONDBLCLKFarenin orta butonuna çift tıklandı

2) int x, int y    ~ Farenin, olay gerçekleştiği sırada penceredeki konumunun x ve y koordinatları.

int flags    ~ Bunu nasıl çevirsem dilimize bilemedim. int bayrak! mı diyeydim? :) flag olarak bıraktım. Bu değişken ile, faremizin olayları gerçekleştiği sırada klavyemizde gerçekleşen bazı olayları takip edebiliyoruz. Şöyle ki, mesela fonksiyonumuz içine şu if bloğunu ekleyebilirdik:

Eğer fare hareket etmişse ve klavyeden Alt tuşuna basıldıysa (İki olay aynı anda gerçekleştiyse)
if ( event == EVENT_MOUSEMOVE && flag == EVENT_FLAG_ALTKEY) { ... }

Eğer klavyeden CTRL tuşuna ve farenin sol tuşuna birlikte basıldıysa:
if ( flag == (EVENT_FLAG_CTRLKEY + EVENT_FLAG_LBUTTON) ) { ... }

Eğer klavyeden Shift tuşuna basıldıysa (Herhangi bir fare hareketi gerçekleşirken)
if ( flag == EVENT_FLAG_SHIFTKEY ) { ... }

Yani event && flag şeklinde de gösterebiliyoruz, flag + flag şeklinde de gösterebiliyoruz. Bu flag'lar sayesinde farenin olayı gerçekleştiği sırada klavyeden CTRL, ALT veya Shift tuşlarına basılıp basılmadığını kontrol edebiliyoruz. Buyurun burada da kullanabileceğimiz flag'larımız:

EVENT_FLAG_LBUTTONSol fare tuşuna tıklandığında
EVENT_FLAG_RBUTTONSağ fare tuşuna tıklandığında
EVENT_FLAG_MBUTTONOrta fare tuşuna tıklandığında
EVENT_FLAG_CTRLKEYKlavyeden CTRL tuşuna basıldığında
EVENT_FLAG_SHIFTKEYKlavyeden Shift tuşuna basıldığında
EVENT_FLAG_ALTKEYKlavyeden ALT tuşuna basıldığında

3) void* kullaniciBilgisi    ~ Herhangi bir işaretçi, üçüncül parametre olarak fareOlayları() fonksiyona gönderilebilir. Bu işaretçi sayesinde fonksiyonda gerçekleştirilecek işlemler sonrası fonksiyon dışından dönüş alınabilir. Şu örneği inceleyelim:

#include "stdafx.h"
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

void fareOlaylari(int olay, int x, int y, int flag, void* kullaniciBilgisi) {
int* deger = (int*)kullaniciBilgisi;
*deger = *deger + 1;
cout << *deger << endl;
}

int main() {
Mat img = imread("C:/calisma/resim1.jpg");
namedWindow("Pencere");
int a = 5;
setMouseCallback("Pencere", fareOlaylari, &a);
imshow("Pencere", img);
waitKey(0);
destroyWindow("Pencere");
cout << "a'nin yeni degeri: " << a << endl;
system("Pause");
return 0;

}

main() fonksiyonunun içine bakarsak, sırasıyla şu işleri yaptık:
~ Mat img = imread("C:/calisma/resim1.jpg"); ile resmini img adlı Mat nesnemize aldık.
~ namedWindow("Pencere"); ile "Pencere" adında bir pencere oluşturduk.
~ int a = 0; ile a adında, int türünde, sıfır değerine sahip bir değişken oluşturduk. Amacımız, herhangi bir fare olayı gerçekleştiğinde bu değerin 1 artması.
~ setMouseCallback("Pencere", fareOlaylari, &a); ile, "Pencere" adlı pencere üzerinde herhangi bir fare olayı gerçekleştiğinde fareOlaylari() fonksiyonu çağırılsın ve kullaniciBilgisi adlı pointer parametreye a değişkeninin adresi gönderilsin. (C++'da pointer konusunu bilmiyorsanız, Google'de arama yaparak öğrenmelisiniz.)
~ imshow("Pencere", img); ile img adlı Mat nesnemizi "Pencere" adlı penceremizde gösterdik.
~ waitKey(0); ile klavyeden herhangi bir tuşa basılıncaya kadar Pencere açık kalacak.
~ destroyWindow("Pencere"); ile "Pencere" adlı penceremizi kapatıyoruz.
~ cout << "a'nin yeni degeri:" << a << endl; ile, a değişkeninin en son aldığı değeri yazdırıyoruz.
~ system("Pause"); ile sistem dursun ki a değişkeninin değerini okuyabilelim diyoruz. Herhangi bir tuşa basılınca da zaten return 0 diyerek main() fonksiyonunu tamamlıyoruz.

void fareOlaylari(int olay, int x, int y, int flag, void* kullaniciBilgisi) { ... } fonksiyonumuzda neler olduğunu da satır satır inceleyelim.
~ Bildiğiniz gibi setMouseCallback() fonksiyonunun son parametresi ile, bu fonksiyonun void türündeki kullaniciBilgisi adlı parametresi için int türündeki a değişkeninin adresini göndermiştik. İlk satırımızda ise buradan gelen değerin türünü int olarak anlayacak bir değişken oluşturduk.
int* deger = (int*)kullaniciBilgisi; ile, int türünde veri tutacak deger adlı bir adres tutucu oluşturduk ve kullaniciBilgisi'ne gelen a değerinin adresini bu deger adlı adres tutucumuza gönderdik.
~ *deger = *deger + 1; ile, deger adresinin içindeki değere 1 ekliyoruz. a değişkenimizin adresi ile deger değişkenimizin adresi aynı olduğu için deger değişkenindeki her değişiklik aynı anda a değişkeninde de olmuş oluyor.
~ cout << *deger << endl; ile, deger adresi içindeki veriyi ekrana yazdırıyorum.

Böylelikle, fonksiyonumuz dışındaki bir değişkenin değerini fonksiyonumuz içinde okuduk ve değiştirdik. setMouseCallBack() fonksiyonundaki 3.parametre bildiğim kadarıyla bu işe yarıyor. Bir değerin adres bilgisini gönderiyorsunuz. Sonra 2.parametrede çağırdığınız fonksiyon içinde de bu adreste tutulan değerle oynayarak bir bakıma fonksiyon dışına bir dönüş yapmış oluyorsunuz.

Konu anlaşılmıştır umarım. Eğer pointer meselesini bilmiyorsanız bu son parametre meselesi kafanızı karışırmış olabilir. Ama C++ içinde pointer'leri Google'layarak kısa sürede bulup öğrenebilirsiniz. Eğer bir talep gelirse ben de anlatabilirim.

Umarım bu yayın da öğretici olmuştur. Bir sonraki yayında görüşmek üzere...





<< AnaSayfa - << Önceki Yayın - Sonraki Yayın >>

Hiç yorum yok:

Yorum Gönder