Tuesday, April 13, 2010

Learning OpenCV: Image and Matrix Opertors in OpenCV

เป็นเรื่องท้ายๆ ก่อนจะเข้าการทำงานด้าน CV ซะที ตอนนี้จะเป็นการลิสต์ฟังก์ชันเกี่ยวกับ Image และ Matrix operation ใน OpenCV มาให้ดูคร่าวเป็นไอเดีย โดยแบ่งเป็นส่วนๆ ดังนี้


  • ฟังก์ชันเกี่ยวกับการเข้าถึงข้อมูล การสร้าง matrix เช่น cvGetCol,cvGetCols, cvGetDiag, cvGetDims, cvGetDimSize, cvGetRow, cvGetRows, cvGetSize, cvGetSubRect, cvZero, cvSplit, cvCopy, cvMerge, cvReduce, cvRepeat, cvSet, cvSetZero, cvSetIdentity
  • ฟังก์ชันเกี่ยวกับ operation พื้นฐาน เช่น cvAbs, cvAbsDiff, cvAbsDiffS, cvAdd, cvAddS, cvAddWeighted(ใช้ทำ alpha blending), cvDiv, cvMul, cvSub, cvSubS, cvSubRS
  • ฟังก์ชันเกี่ยวกับ logic เช่น cvCmp, cvCmpS, cvNot, cvXor, cvXorS, cvInRange, cvInRangeS, cvOr, cvOrS
  • ฟังก์ชันเกี่ยวกับ matrix operation  cvCalcCovarMatrix, cvCrossProduct, cvDet, cvDotProduct, cvInvert cvSum, cvTranspose, cvNorm, cvNormalize, cvSVD, cvSVBkSb, cvTrace, cvFlip, cvSolve(ใช้แก้สมการ linear), cvEigenVVcvGEMM
  • ฟังก์ขันทางสถิติ  cvAvg, cvAvgSdv, cvCountNonZero, cvMax, cvMaxS, cvMin, cvMinS, cvMinMaxLoc, cvMahalanobis
  • ฟังก์ชันการ convert ต่างๆ  cvConvertScale, cvConvertScaleAbs, cvCvtColor

โดยส่วนตัวแล้วยังไม่เข้าใจว่าไอ้ฟังก์ชันแดงๆ นี่ไว้ทำอะไรไม่เกี่ยวกับ matrix และ image เท่าไร หวังว่าหลังๆ จะเจอตัวอย่างที่ช่วยอธิบาย






Learning OpenCV: IplImage in OpenCV short guide

มาถึงข้อมูลที่น่าจะใชกันบ่อยจริงๆ ใน OpenCV นั่นคือ IplImage คอนเซปต์โดยรวมก็เหมือนกับ CvMat (ก็เหมือนถ่ายทอดกันมา) เพียงแต่ใน header มีข้อมูล พวก channel, bit depth, color mode และอื่นๆ ที่น่าจะเข้าใจไม่ยาก ลองยกตัวอย่างวิธีการเข้าถึงข้อมูลแต่ละ pixel ใน IplImage กัน


void saturate_sv( IplImage* img ) {
    for( int y=0; yheight; y++ ) {
        uchar* ptr = (uchar*) (
        img->imageData + y * img->widthStep
        );
       for( int x=0; xwidth; x++ ) {
          ptr[3*x+1] = 255;
          ptr[3*x+2] = 255;
      }
   }
}

เป็นฟังก์ชันเซตค่า image ในโหมด HSV ที่มี 3 channel ให้เพิ่มค่า S, V สูงสุด โดยไม่เปลี่ยน H

สังเกตวิธีการเข้าถึงทุก pixel ในภาพ การวนลูปจะวนตามแถว(row) ซึ่งจำนวน row มีค่าเท่ากับ height (ไม่งงใช่ไหม) ถ้าวนลูปกลับกันการเข้าถึงข้อมูลจะกระโดดไปมา และสังเกตว่าในแต่ละ row จะมีข้อมูลเฉพาะของแต่ละแถวอยู่ ขนาดข้อมูลส่วนนี้อยู่รวมอยู่ใน widthStep (แบบเดียวกับ step ใน CvMat) ดังนั้นเวลา iteration แต่ละแถวค่าที่เพิ่มขึ้นจะต้องเป็น widthStep ไม่ใช่ width*channel*bit dept

ข้อมูลที่่สำคัญอีกอันหนึ่งของ IplImage ที่ไม่มีใน CvMat คือ ROI(region on interest) ซึ่งโอเปอร์เรชั่นส่วนมากใน OpenCV สนับสนุน ROI ด้วย ข้อมูลใน IplImage จะถูกประมวลผลเฉพาะในส่วนที่ตั้งค่าไว้ใน ROI เท่านั้น มีคำสั่งที่สำคัญสำหรับ ROI คือ

  • cvSetImageROI ตั้งค่า ROI
  • cvResetImageROI ยกเลิกการตัั้งค่า ROI
เราจำเป็นต้องยกเลิกการตั้งค่า ROI ด้วยเนื่องจาก ROI มีผลต่อฟังก์ชันการแสดงผล ถ้าไม่ยกเลิกการตั้งค่าคำสั่งแสดงผลเช่น cvShowImage จะแสดงเฉพาะส่วน ROI เท่านั้น


แต่การใช้ ROI มีข้อจำกัดที่ว่าสามารถใช้ได้ทีละครั้ง แต่มีเทคนิคการในการสร้าง ROI หลายๆ อันได้ โดยใช้ประโยชน์ของ widthStep ขั้นตอนมีดังนี้

  1. สร้าง IplImage header โดยยังไม่ต้องสร้าง data (ประโยชน์หนึ่งของการสร้างแยกกันระหว่าง header กับ data) โดยตั้งค่าต่างๆ (depth, channel)ให้เท่ากับภาพต้นฉบับ ยกเว้น width กับ height
  2. ตั้งค่า widthStep ของ sub image ให้ตรงกับของต้นฉบับ
  3. ตั้งค่า pointer data ของ sub image ให้ตรงกับจุดเริ่มต้น ROI ในภาพต้นฉบับที่เราสนใจ
การตั้งค่าข้อสามนั้นจะทำให้ภาพของเราเริ่มต้องที่ ROI จากต้นฉบับ (แต่ C ไม่รับรู้เนื่องจากเป็น pointer) และทุกครั้งที่ iteration ข้อมูลในแต่ละแถวจะสิ้นสุดที่ ROI (เนื่องจากเราตั้งค่า width ไว้ใน header ของ sub image) การ iteration ไปยังแถวถัดไปจะใช้ค่า widthStep ของต้นฉบับ ดังนั้นจะเริ่มต้นที่คอลัมภ์เดียวกันในแนวถัดไปเสมอ (อย่าลืมว่าพื้นที่ข้อมูลเป็นเส้นตรงและข้อมูลของ sub image ยังใช้ข้อมูลของภาพต้นฉบับอยู่) ลองดูตัวอย่างจากหนังสือ Learning OpenCV


IplImage *sub_img = cvCreateImageHeader(
   cvSize(
      interest_rect.width,
      interest_rect.height
   ),
   interest_img->depth,
   interest_img->nChannels
);
sub_img->origin = interest_img->origin;
sub_img->widthStep = interest_img->widthStep;
sub_img->imageData = interest_img->imageData +
   interest_rect.y * interest_img->widthStep +
   interest_rect.x * interest_img->nChannels;
.................// operation with sub_image here
cvReleaseImageHeader(&sub_img);



โดย interest_img คือภาพต้นฉบับ, interest_rect คือ ROI ที่สัมพันธ์กับต้นฉบับ sub_image จะเป็นภาพ ROI ของ interest_img โดยวิธีนี้สามารถสร้าง ROI ได้หลายๆ อันในครั้งเดียว

Learning OpenCV: CvMat in OpenCV short guide

จากชื่่อก็คงจะเดาได้อยู่แล้วว่า CvMat นั้นเอาใช้ใช้แทน matrix ซึ่ง CvMat นี่ประกอบไปด้วยสองส่วน คือส่วน Header กับ ส่วน Data (คงพอเดากันได้ว่า Data นั้นจริงๆ ก็คือ  pointer ธรรมดาแล้ว malloc ค่าเอานั่นเอง) ซึ่งจริงๆ เวลาสร้าง CvMat นั้นคือคำสั่ง cvCreateMat ซึ่งแท้จริงแล้วคือการเรียกคำสั่ง cvCreateMateHeader และ cvCreateData เหตุผลก็คือเราสามารถ สร้าง header ก่อนแล้วค่อยกำหนด data
พอใช้งานเสร็จแล้ว ก็อย่าลืม release ด้วย(ย้ำอีกที C ไม่มี auto dealloc)  โดยคำสั่ง cvReleaseMat ฟังก์ชันที่เกี่ยวข้องกับ CvMat สรุปคร่าวๆ ได้ดังนี้ (คงไม่ต้องอธิบายอ่านจากชื่อเอาน่าจะพอไหว)

  • cvCreateMat
  • cvCreateMatHeader
  • cvInitHeader
  • cvMat
  • cvCloneMat
  • cvReleaseMat


ทีนี้มาถีงเรื่องสำคัญกับการเข้าถึงข้อมูลใน CvMat OpenCV เตรียม function และ macro ไว้สำหรับการเข้าถึงข้อมูล แต่เขาแนะนำกันว่ามันช้าโดยเฉพาะอย่างยิ่งการประมวลผลกราฟฟิกต้องทำงานช้าอยู่แล้ว จึงแนะนำ ให้ประมวลผลโดยใช้วิธีการ iteration ไปยังค่าแต่ละค่าใน CvMat แทนดังตัวอย่าง


float sum( const CvMat* mat ) {
        float s = 0.0f;
        for(int row=0; rowrows; row++ ) {
               const float* ptr = (const float*)(mat->data.ptr + row * mat->step);
               for( col=0; colcols; col++ ) {
                        s += *ptr++;
               }
      }
      return( s );
}

อย่างลืมเวลาวนลูปนี่ข้างนอกเป็น row ห้ามสลับกัน เนื่องจากการวนลูปแบบนี้เนื้อที่ที่ถูก access ใน หน่วยความจำจะเรียกต่อกันพอดี (หน่วยความจำจริงๆ มีมิติเดียวการอ้างถึงแบบนี้จะเป็นการอ้างถึงข้อมูลที่ต่อเนื่องกันจากการ alloc)


matrix แต่ละตัวอาจจะมีข้อมูลพิเศษเก็บไว้ในแต่ละแถว ดังนั้นขนาดของแถวหนึ่งๆ อาจจะไม่เท่ากับ width*size of (int or float) ค่า step เป็นค่าที่รวมค่าทั้งหมดของแต่ละแถวไว้แล้ว


มีข้อยกเว้นในการใช้ macro กับ function ในการอ้างถึงข้อมูล ถ้าอ้างถึงข้อมูลเพียงจุดเดียวการใช้ macro หรือ function ก็ดูเป็นการประหยัดเวลาการเขียนโปรแกรม




สำคัญ(อีกแล้ว) ดังนั้นการสร้าง CvMat ขนาด MxN, NxM, 1x(M*N), (M*N)x1 จึงอาจจะไม่จำเป็นต้องเหมือนกัน ทำให้การส่งผ้านข้อมูลไปยังพารามิเตอร์ที่เป็น CvArr เกิดการจัดการข้อมูลผิดพลาดได้(เพราะ CvArr ไม่เช็คตรงนี้) แต่ปัญหาส่วนใหญ่คงไม่เกิด เพราะว่าถ้าเรายุ่งกับ image เราคงไม่จัดการ matrix เอง




*source code ตัวอย่างนำมาจากหนังสือ Learning OpenCV ของ Oreilly

Learning OpenCV: Basic of OpenCV data type

ก่อนอื่นมาทำความรู้จักกับประเภทข้อมูลใน OpenCV

เท่าที่อ่านในข้อมูล OpenCV มีประเภทข้อมูลอยู่หลักอยู่ไม่เยอะ จริงๆ จะข้ามไปก็ได้เพราะน่าจะเดาได้กัน
เป็นส่วนใหญ่อยู่แล้ว เลยมาสรุปกันคร่าวๆ ดังนี้

  • CvPoint, CvPoint2D32f, CvPoint3D32f ใช้ระบุจุดในระนาบ สองมิติ, สามมิติ
  • CvSize ระบุขนาดของรูป
  • CvRect ระบุพื้นที่สี่เหลี่ยม
  • CvScalar
ตัวที่สำคัญต้องเรียนรู้หน่อยคือตัว CvScalar นั้นเป็นค่า array 4 ตัว เป็นค่าคงที่สำหรับ channel โดยสนับสนุนได้ถึง 4 channel  หลายฟังก์ชันใน OpenCV จะมี CvScalar เป็นพารามิเตอร์ ซึ่งจริงๆ แล้วคงหมายถึงค่าคงที่ในแต่ละ channel นั่นเอง

สำหรับข้อมูลที่น่าจะใช้บ่อยจริงๆ ใน OpenCV ก็ได้แก่

  • CvArr แทน array
  • CvMat แทน matrix
  • IplImage แทน image


สามตัวนี้จริงๆ คงจะเหมือนจะ inherit กัน โดย IplImage มาจาก CvMat, CvMat มากจาก CvArr (แต่ว่า C ไม่มี inherit นะ) การเข้าใจถึงความสัมพันธ์ของตัวแปรสามตัวนี้จะสามารถทำให้เข้าใจวิธีการส่งผ่านพารามิเตอร์ในหลายๆ ฟังก์ชันได้ เช่นการส่งผ่านพารามิเตอร์ที่เป็น CvArr สามารถรับข้อมูล CvMat หรือ IplImage ได้  (จริงๆ แล้ว CvArr ก็คือ void นั่นเอง ดังนั้นส่งอะไรมาก็ได้) ในที่นี้ใข้แทน abstract class ของ array ซึ่งใน C ไม่มี class เดี๋ยวมาว่ากันต่อเรื่อง CvMat กับ IplImage

mini guide how to install OpenCV 2.10 for Visual Studio 2008

ก่อนจะใช้งาน OpenCV ก็ต้องติดตั้งกันก่อน

- หาที่ download ก่อน http://sourceforge.net/projects/opencvlibrary/files/

- โหลด OpenCV-2.1.0-win32-vs2008.exe มาใช้ได้เลย เพราะว่าคอมไพล์เป้นไลบรารี มาให้เรียบร้อยแล้วมีข้อจำกัดนิดนึงที่ว่าถ้าใช้ Microsoft Visual C++ 2008 SP1 Redistributable Package (x86) จะไช้ได้แต่ไลบรารีที่เป็น release ถ้าใช้ visual studio 2008 ก็ไม่มีปัญหา สำหรับคนที่ต้องการคอมไพล์เองก็โหลดอีกตัวมาใช้ แล้ว ใช้ CMAKE สร้าง (ขอบายถ้ามีชอยส์อื่น 555)

- เปิดโปรเจกต์ C ธรรมดาขึ้นมาแล้วเซตค่า include path กับ library ก็จบ
include path ใส่ที่นี่
เลือก Project property
Configuration properties
C/C++
General
Additional Include Directories ใส่ C:\OpenCV2.1\include\opencv
library ใส่ที่นี่
ลือก Project property
Configuration properties
Linker
Additional Dependencies

ใส่ค่าประมาณนี้เข้าไปสำหรับ Debug configuration
C:\OpenCV2.1\lib\cv210d.lib
C:\OpenCV2.1\lib\cvaux210d.lib
C:\OpenCV2.1\lib\cxcore210d.lib
C:\OpenCV2.1\lib\highgui210d.lib
C:\OpenCV2.1\lib\ml210d.lib
C:\OpenCV2.1\lib\opencv_ffmpeg210d.lib
ในส่วน release ใส่อันนี้แทน
C:\OpenCV2.1\lib\cv210.lib
C:\OpenCV2.1\lib\cvaux210.lib
C:\OpenCV2.1\lib\cxcore210.lib
C:\OpenCV2.1\lib\highgui210.lib
C:\OpenCV2.1\lib\ml210.lib
C:\OpenCV2.1\lib\opencv_ffmpeg210.lib

สำหรับคนที่ไม่อยากจะตั้งค่า OpenCV ทุกๆ project ก็สามารถตั้งค่าใน Visual Studio ให้เป็น default ได้เลย
เลือกที่ Tool
Options
Projects and Solutions
VC++ Directories
ที่ Library files ใส่ C:\OpenCV2.1\lib
ที่ Include files C:\OpenCV2.1\include\opencv
      ที่ Source files ใส่
                C:\OpenCV2.1\src\cv
                C:\OpenCV2.1\src\cvaux
                C:\OpenCV2.1\src\cxcore
                C:\OpenCV2.1\src\highgui
แล้วในโปรเจกต์ในส่วนของ library ก็ใส่คล้ายเดิมเพียงแต่ไม่ต้องใส่ path ให้กับ Additional Dependencies ของ debug ใส่
cv210d.lib
cvaux210d.lib
cxcore210d.lib
highgui210d.lib
ml210d.lib
opencv_ffmpeg210d.lib
ของ release ใส่


cv210.lib
cvaux210.lib
cxcore210.lib
highgui210.lib
ml210.lib
opencv_ffmpeg210.lib

ลองคอมไพล์ดู เท่านี้ก็เรียบร้อย ไม่ต้อง MAKE เองชีวิตง่ายขึ้นเยอะเลย

ขั้นต่อไปว่าจะอ่านตามหนังสือ Learning OpenCV ของ O'Reilly ลองดูหรือโหลดตัวอย่างโคดได้ที่
ส่วนหน้าตาหนังสือเป็นแบบนี้
http://www.oreilly.com/catalog/9780596516130