Opencv, c İle Nasıl Kullanılır? -Görüntü İşleme- (1)

Açık Kaynak, Programlama, tutorial, Uzun Kod İçerikli, ytulinux

Evet güne hızlı başladım, şimdi de opencv ile bir resim nasıl açılır, nasıl görüntülenir, üzerinde bir iki basit işlem nasıl yapılır gibi şeyleri öğreneceğiz. (Linux için opencv paketini yükleyip, en altta verdiğim parametrelerle derleme yapmanız yeterli. Windows için biraz arama yapmanız gerekecek :) )

Öncelikle her yazıda yaptığımız gibi, konu ile ilgili struct‘ın ne olduğunu görelim. Açtığımız resimler ile ilgili bilgiler (IplImage *) struct’ında tutulur. Şu an yapacağım kopyala yapıştırdan gözünüz korkmasın, hepsini bilmek zorunda değilsiniz. width, widthstep, height, nChannels, depth ve imageData‘yı bilmeniz yeterli, bunlar birazdan açıklanacak.

typedef struct _IplImage
{
int nSize; /* sizeof(IplImage) */
int ID; /* version (=0)*/
int nChannels; /* Most of OpenCV functions support 1,2,3 or 4 channels */
int alphaChannel; /* ignored by OpenCV */
int depth; /* pixel depth in bits: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16S,
IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F are supported */

char colorModel[4]; /* ignored by OpenCV */
char channelSeq[4]; /* ditto */
int dataOrder; /* 0 – interleaved color channels, 1 – separate color channels.
cvCreateImage can only create interleaved images */

int origin; /* 0 – top-left origin,
1 – bottom-left origin (Windows bitmaps style) */

int align; /* Alignment of image rows (4 or 8).
OpenCV ignores it and uses widthStep instead */

int width; /* image width in pixels */
int height; /* image height in pixels */
struct _IplROI *roi;/* image ROI. when it is not NULL, this specifies image region to process */
struct _IplImage *maskROI; /* must be NULL in OpenCV */
void *imageId; /* ditto */
struct _IplTileInfo *tileInfo; /* ditto */
int imageSize
; /* image data size in bytes
(=image->height*image->widthStep
in case of interleaved data)*/

char *imageData; /* pointer to aligned image data */
int widthStep; /* size of aligned image row in bytes */
int BorderMode[4]; /* border completion mode, ignored by OpenCV */
int BorderConst[4]; /* ditto */
char *imageDataOrigin
; /* pointer to a very origin of image data
(not necessarily aligned) –
it is needed for correct image deallocation */

}
IplImage;

Tahmin edebileceğiniz gibi, width resmin genişliğini, height yüksekliğini, nChannel kaç tip renk kanalı olduğunu (kırmızı yeşil mavi gibi) widthStep ise bir satırın toplam genişliğini veriyor. Data, unsigned char tipinde, bize resme ait ve headerdan geriye kalan yani renk bilgilerini gösteren kısmı verir.

Şimdi örneğimize geçebiliriz. Örneğe geçmeden önce bir açıklamada bulunayım. Örnekte aslında iki şey yapıyoruz.

1. Bir resmin rengini normalizasyon yöntemi ile açmak (örneğin 255 ile 0 arasında bir renk değerini 50 açmak istiyoruz, bunun için direk 50 eklemek yerine rengi 50 ile 255 arasına oranlıyoruz)
2. Bir resmi grinin tonlarına dönüştürmek.

İlkini yapmak için, konsoldan parametre olarak girilmiş olan resmi yüklüyoruz, sonra normRenkAc fonksiyonu ile rengini açıyoruz, sonra da bu resmi bir pencerede gösteriyoruz. Bu resme ait IplImage değişkeninin adı img.
İkincisini yapmak için, konsoldan parametre olarak girilmiş olan ve yüklemiş olduğumuz resme ek olarak, cvCreateImage fonksiyonu ile gri tonlarında ve yüklenmiş olan resmin boyutlarında bir resim alanı yaratıp (IplImage * dimg), cvCvtColor ile img de bulunan renkli bilgiyi dimg ye gri tonlarında olacak şekilde aktarıyoruz.

Kodlara geçmemiz gerekirse;
// IplImage * img, bizim main fonksiyonunda belleğe yükleyeceğimiz (ki bu fonksiyon çalışırken yüklenmiş olacak) resme ait struct. miktar ise, bizim rengi ne kadar açmak istediğimiz.

void normRenkAc(IplImage * img,int miktar)
{
int i,j,k,yer;

// renk bilgilerinin tutulduğu data değişkeninden, unsigned char *’a typecast yaparak data işaretçimize bu alan değerini atıyoruz.
uchar * data  = (uchar *)img->imageData;

// eveeet… Burada biraz karmaşık bir yapı var. Öncelikle bir hatırlatmada bulunalım. height resmin yüksekliği, width kalınlığı, nChannel her pixel e ait genişlik veya renk kanalı sayısı, widthStep ise bir satırda kaç bytelık yer olduğuydu. Şimdi, eğer (uchar * data) da renklerin bir matris şeklinde değil de arka arkaya gelen bytelar dizisi şeklinde tutulduğunu göz önünde tutarsak, i inci satırın j inci sütununa nasıl erişebiliriz?
// Şöyle düşünelim, elimizde bir 10*10 = 100 lük bir dizi olsa, ve bu diziyi 10*10’luk bir matris olarak kullanmak istersek, dizinin adı da A ise, 5 inci satırın 3’üncü sütununa nasıl erişiriz? A[5*i+3] değil mi? Burada da aslında buna benzer birşey yapıyoruz. (i*img->widthStep)+(j*img->nChannels)+k formülünde de aslında buna benzer birşey yapıyoruz. Burada +k ile k’ıncı kanala erişim sağlıyoruz.

for(i=0;i<img->height;i++) for(j=0;j<img->width;j++) for(k=0;k<img->nChannels;k++)
{
yer = i*img->widthStep+j*img->nChannels+k;
// Göreceğiniz gibi, her renk değerini 0 ile 255 arasında iken, miktar-255 arasına oranlıyoruz, bu şekilde renk açma hadisesini halletmiş durumdayız.
data[yer]=(data[yer]*(255-miktar))/255+miktar;
}
}

Gelelim main fonksiyonumuza, zurnanın zırt dememesi gereken yere :)

int main(int argc, char *argv[])
{

// açacağımız resim
IplImage* img = NULL;
// bellek açarak, yaratacağımız alan. Bu alana, dosyadan aldığımız resmin gri tonlu halini atacağız.
IplImage* dimg = NULL;
// Argümanlar konsoldan yanlış verildiyse o zaman uyarı vermemiz gerekiyor.
if(argc!=2){
printf(“kullanim: ./hede.o acilacak_dosya\n”);
exit(0);
}

// Resmi yüklüyoruz :)

// bakalım prototipinde ne deniliyormuş?
/*

// Loads an image from file
IplImage* cvLoadImage( const char* filename, int iscolor CV_DEFAULT(1));

iscolor
if >0, the loaded image will always have 3 channels;

if 0, the loaded image will always have 1 channel;

if <0, the loaded image will be loaded as is (with number of channels depends on the file)

Yani, resmimizin kanalları nasılsa, o şekilde yüklenmesini istiyoruz.

*/
img=cvLoadImage(argv[1],-1);
//resim yüklenemediyse uyarı veriyoruz.
if(!img){
printf(“Resim yuklenemedi: %s\n”,argv[1]);
exit(0);
}
// Resim açıldığında, resme ait bilgileri ekrana yazdırmakta fayda var. Eğer çalışmada sıkıntı çıkarsa o zaman debug açısından faydalı oluyor.
printf(“%dx%d resmi %d kanalda aciyoruz\n”,img->height,img->width,img->nChannels);
// Gri tonlarında yaratacağımız resim… IPL_DEPTH_8U nedir bilmiyorum yazdım, çalıştı ben de fazla kurcalamadım. yanındaki 1 ise tek kanala sahip olduğunu yani gri tonlu bir resim olduğunu ifade ediyor.
dimg = cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);
// burada resmimizi gri tonlarına çevirip, dimg ye atıyoruz. CV_BGR2GRAY, RGB resmi GRAYSCALE e yani gri tonlarına çevirmek istediğimizi anlatıyor. Yani neymiş, cvCvtColor fonksiyonunu resimlerimizi farklı şeylere çevirmek için de kullanabiliyor muşuz, tabi farklı parametreler vererek.
cvCvtColor(img,dimg,CV_BGR2GRAY);
// burada da img resmimizin rengini önce 50, sonra bir 50 daha açıyoruz. Tabi 100 açmayla iki 50 açmak farklı şeyler, işin felsefi boyutunu size bırakıp devam ediyorum.
normRenkAc(img,50);
normRenkAc(img,50);
// Burada iki işlem yaptık. renk açma, gri tonlara çevirme ve oluşturduğumuz bu iki farklı resmi bir şekilde kullanıcıya göstermemiz gerekiyor ki anlamı olsun. Biz de o zaman gri tonlara çevrilmiş resmi ekranda bir pencere ile gösterelim, rengi açılmış olanı da farklı bir isimle kaydedelim :)
if (!cvSaveImage(“acilmis.jpg”,img)) fprint(“Kaydedilemedi\n”);
else
printf(“Resim basariyla kaydedildi.\n”);
//bu fonksiyonun içeriği de gösterilecek.
penceredeGoster(dimg);

return 0;
}

Gelelim pencerede resim göstermek için kullandığımız fonksiyona :)

void penceredeGoster(IplImage * img)
{
// Pencere yarat, büyüklüğü içeriğine göre otomatik değişsin
cvNamedWindow(“mainWin”, CV_WINDOW_AUTOSIZE);
// pencereyi sol üste göre (100,100) konumuna getir ki köşeye yapışıp iğrenç bir görüntü sergilemesin
cvMoveWindow(“mainWin”, 100, 100);

// resmi goster
cvShowImage(“mainWin”, img );

// klavyeden bir tus icin bekle
cvWaitKey(0);

// free()
cvReleaseImage(&img );
}

Evet, böylece opencv ye küçük bir giriş yapmış olduk. Umarım faydası olmuştur. Projenin tamamı:

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <cv.h>
#include <highgui.h>

void normRenkAc(IplImage *,int);

void penceredeGoster(IplImage *);

int main(int argc, char *argv[])
{
IplImage* img = NULL;
IplImage* dimg = NULL;
if(argc!=2){
printf(“kullanim: ./hede.o acilacak_dosya\n”);
exit(0);
}

// Resmi yukle
img=cvLoadImage(argv[1],-1);
if(!img){
printf(“Resim yuklenemedi: %s\n”,argv[1]);
exit(0);
}

printf(“%dx%d resmi %d kanalda aciyoruz\n”,img->height,img->width,img->nChannels);
dimg = cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);
cvCvtColor(img,dimg,CV_BGR2GRAY);
normRenkAc(img,50);
normRenkAc(img,50);
if (!cvSaveImage(“acilmis.jpg”,dimg)) fprintf(stderr, “Kaydedilemedi\n”);
else
printf(“Resim basariyla kaydedildi.\n”);
penceredeGoster(dimg);

return 0;
}

void penceredeGoster(IplImage * img)
{  // Pencere yarat
cvNamedWindow(“mainWin”, CV_WINDOW_AUTOSIZE);
cvMoveWindow(“mainWin”, 100, 100);

// resmi goster
cvShowImage(“mainWin”, img );

// bir tus icin bekle
cvWaitKey(0);

// free()
cvReleaseImage(&img );
}

void normRenkAc(IplImage * img,int miktar)
{
int i,j,k,yer;
uchar * data  = (uchar *)img->imageData;
for(i=0;i<img->height;i++) for(j=0;j<img->width;j++) for(k=0;k<img->nChannels;k++)
{
yer = i*img->widthStep+j*img->nChannels+k;
data[yer]=(data[yer]*(255-miktar))/255+miktar;
}
}

Makefile içeriği: (-I ile başlayan parametre değişebilir, whereis opencv diyerek bilgisayarınızda nerede bulunduğunu anlayabilirsiniz)
hede.o: hede.c
·   gcc hede.c -o hede.o -I/usr/include/opencv -lcv -lcxcore -lcvaux -lhighgui -lml

Bu yazı toplamda 5322, bugün ise 0 kez görüntülenmiş

16 Comments
  • tahtalishow

    valla hüseyin iyi hoş da, bu opencv kütüphanesi oldukça fazla hata veriyor, hatta beni 1 dönem boyunca az kanser etmemiştir 😀 lan bir ekliyorum codeblock ta programa 1200 tane hata veriyor, hayat enerjimi emmişti lan 😛

  • huseyinalb

    hacı zaten var ya kütüphane kullanmaya alışkın olmadığımızdan oluyor bunlar.
    bende de ilk uğraştığım zamanlar baya hata veriyodu.
    1. Kütüphane derlemek için gereken parametrelerde sıkıntı çekiyoruz.
    2. Algoritma öğrenirken düğüm yapılarını kullanmıyoruz, o konuda eksiğimiz var.
    Bir süre struct kullanmaya kastırdım, şu nesne tabanlı c olayına ondan kasıyordum :) Nerede ne kullanmam gerektiği konusunda baya pratik yaptım o zaman hata vermemeye başladı.

  • http://asdasdasd Taygun Kekec

    Gideri var tebrik ederim, ama keşke opencv kullanmayı öğrenmeden görseydim bu yazıyı daha bi gideri olurdu =)

    Eline sağlık hüsocan

  • burak

    Anlatım için teşekkürler, ben C/C++’a yeni başladım, daha doğrusu MATLAB’ın “Image Processing” kısmında yaptığım çalışmalar executable hale getirilirken sürekli hata verdiği için C/C++ a mecbur kaldım 😀 Neyse konuya dönersek bende Codeblocks var derleyici olarak, onun dışında bir şey yüklemedim. C/C++ kodlarını derleyip güzelce çalıştırıyor. Daha sonra kendi projem için Opencv (1.0) yi yükledim, internetten bulabildiğim kadarıyla Codeblocksun ayarlarını da yaptım, örnek kodu sorunsuz derliyor fakat çalıştırmaya çalıştığım zaman çeşitli hatalar alıyorum (cxcore110.dll bulunamadı bunu bi şekilde çözdüm daha sonra da null~~ gibi bir hata verdi). Şimdi benim sorum şu, opencv çalışması için Visual C++ mı yüklemem gerekiyor? Çünkü her yerde bu visual çıktı karşıma 😀 Şuanki halde görüntü alıp işleme yapamaz mıyım? Dediğim gibi bu konuda yeniyim yardım ederseniz sevinirim:)ha bir de Linux değil işletim sistemim Vista

  • huseyinalb

    Merhaba, baya geç cevap verdim kusura bakmayın, mesajı gördüğüm gün aklımın bir köşesindeydi derlemeyi deneyip cevap yazmak ama unutmuşum.
    Windowsta Devcpp de programı derlemeye çalıştım, http://sourceforge.net/projects/opencvlibrary/files/opencv-win/1.0/OpenCV_1.0.exe/download linkinden opencv yi kurup, http://opencv.willowgarage.com/wiki/DevCpp linkindeki adımları takip ettikten sonra, derleyip çalıştırabiliyoruz demek isterdim ama çalıştıramıyoruz :) Bir saatlik uğraşım sonunda bir yerden okuduğum kadarıyla opencv windows kodları visual studioda yazılmış, visual studio ya ait bir kütüphaneye ihtiyaç duyuyormuş. Hatayı googleda yazıp bir süre sayfaları taradıktan sonra bulduğum http://com-pro.blogspot.com/2008/10/opencv-re-opencv-11pre1-0xc0150002_31.html linkinde bahsettiği gibi http://www.microsoft.com/downloads/details.aspx?familyid=200B2FD9-AE1A-4A14-984D-389C36F85647&displaylang=en linkindeki dosyayı indirip kurunca problemimiz çözülüyor.
    Şu an bende çalışıyor. Muhtemelen çözmüşsünüzdür ama yine de çözümün burada bulunmasında fayda var, belki biri okur işine yarar :)

  • burak

    Cevap için teşekkürler, ama dediğiniz gibi ben sonunda uğraşa uğraşa, internetten baya bir araştırmadan sonra sorunumu hallettim, Codeblocks ve Opencv kardeş kardeş çalışıyorlar 😀
    Şuanda tek derdim 320×240 video çözünürlüğünü değiştirememem :( İnternetten bir çok kaynak buldum ama bir türlü çözünürlüğü artıramadım :( kameram 1024×768 e kadar destekliyor ve uygulamalarım için 320×240 ın yeterli olacağını düşünmüyorum :(

  • huseyinalb

    Öncelikle tebrik ederim, kısa sürede baya birşey yapmışsınız opencv ile ilgili gördüğüm kadarıyla. Ben sizin kadar çalışmadım opencv üzerinde, bu sayfada gördükleriniz o gün merak edip de opencv kütüphanesini indirip denediğim kısımlar sadece :) Eğer devam etseydim ne ile uğraştıysam onları da yazacaktım ama bilmiyorum bir daha ne zaman uğraşırım :)
    Kameradan video çekmeyle ilgili bir bilgim yok, dolayısıyla yardımcı olamayacağım ne yazık ki :)

  • cihan

    Merhaba. Yazınız için teşekkürler, çok yardımcı oldu. Benim takıldığım nokta ise bir resmin renkleriyle nasıl oynayabilirim. Yani resmi tamamen açıp-koyulaştırmak değil de , renk değişiklikleri yapmak. istedigim rengi başka bir renkle nasıl degiştirebilirim mesela. Yardımcı olursanız sevinirim..

  • huseyinalb

    merhabalar,

    renk açma fonksiyonundaki for döngüsünün içine oradaki dönüşüm formülü yerine
    if (data[yer] == renk_degeri) {
    data[yer]=diger_renk_degeri;
    }
    şeklinde değiştirebilirsiniz.
    Tabi dosyayı açarken eğer renkli bir resmi değiştirmek istiyorsanız ona göre bu resmi yaratırken gri tonlarında değil de renkli olarak yaratmanız gerekecek.

    Tabi bu çözüm en iyisi olmayabilir, daha iyisi bununla ilgili özel bir fonksiyonun bulunmasıdır mesela. Böylece yazacağınız kod kütüphanede zaten bulunan fonksiyonlar yerine yazdığınız kodla kirlenmez, okunması daha kolay olur. Yapmak istediğiniz şey örneğin belli renk aralığındaki tonları tek renge çevirmek vesaire olabilir. Yapmak istediğiniz amaca yönelik bir fonksiyonun bulunması imkan dahilinde, ancak çok fazla opencv ile uğraşmadığım için bu özel fonksiyonlar hakkında bilgim yok.

  • cihan

    renk degeri demekle kastetiğiniz şey, 255 degeri mi? Eğer öyleyse, RGB renk kodlarına göre, istedigim yeri mavi yapmam için mavinin renk kodunu yani “000 000 255” i kulanmam gerekmezmi? Bu degeri yazınca olmuyor..Sadece 255 degerini kullandıgım zaman ise alakasız renkler geliyor.

  • huseyinalb

    aslında orada bir farklılık söz konusu;
    for(k=0;knChannels;k++)
    satırında da görüleceği gibi, her pixelin her channel ı için biz bu işlemi tekrarlıyoruz. RGB söz konusu olduğu zaman dediğiniz gibi üç bytelık bir yer kullanılıyor ancak bu resmin özelliğine göre değişebilir, örneğin saydamlık ile ilgili bir bytelık daha veri olabilir bu durumda her pixel aslında 4 bytedan oluşur. Dolayısıyla işlenecek olan dosyaya ait veri yapısının incelenmesinde ve işlemlerin bu şekilde yapılmasında fayda var.
    Yani sadece rgb nin b kısmına ulaşmak istiyorsak ve kanal sayısı 3 ise, şu an gittiğimiz mantıktan hareketle (ve char ile yani byte byte veri erişimi yaptığımıza göre) her seferinde (widthStep*i+2) + j*3 (bu üç aslında nChannel değişkeninin değeri tabi, örnek somut olsun diye sayı olarak kullanıyorum) şeklinde erişmemiz gerekir. For döngüsünün de bu şekilde ayarlanması lazım bu durumda.

  • huseyinalb

    size tavsiyem, küçük bir bmp yahut png dosyası üstünde çalışın (10*10 örneğin) ve çıktıyı hexdump yapıp ne yaptığınızı inceleyin, bu şekilde çok daha kolay öğreniliyor.

  • Onur

    Merhabalar;
    ben Bir resmin pixel değerlerini 2-boyutlu bir matrise atmak istiyorum .Opencv de yapmaya çalıştım
    ———–
    for(int i=0;i<height;i++)
    {
    for(int j=0;j<width;j++)
    {
    cout<<data1[i*step+j*channels+0]<<" ";
    }
    }
    —————————————
    fakat sonsuz döngüye giriyor.Tek tek okumaya kalktığımda ise bir sayı yerine bir harf gösteriyor.Fikri olan var mı acaba ?
    teşekkürler..

  • huseyinalb

    merhaba,
    cpp bilgim çok fazla değil ama data1 char* tipi bir değişken olduğu için bu durumla karşılaşıyor olabilirsiniz. eğer channel sizeof(int) kadarsa alttaki çalışacaktır diye tahmin ediyorum. (type cast)
    cout<< (int) data1[i*step+j*channels+0]<<" " eğer sizeof(int) değilse o zaman her seferinde yüksek anlamlı bitleri silmeniz gerekecektir. aslında en mantıklısı printf ile hex olarak yazdırmak, bu şekilde daha iyi inceleyebilirsiniz.

  • Fadime

    Merhaba.Resimdeki her bir piksele eriştikten sonra o pikselin siyah mı beyaz mı olduğunu ayırt etmek için ne yapmam gerekiyor,nasıl bir for döngüsü kullanmalıyım?

  • huseyinalb

    normRenkAç fonksiyonundaki döngüyü kullanabilirsiniz, oradaki atama satırı yerine karşılaştırma yapmanız gerekecek