Wednesday, April 14, 2010

Mahalanobis Distance

สงสัยเรื่อง mahalanobis distance ที่เห็นในฟังก์ชันของ OpenCV อ่านๆ ตอนหลังพบคำอธิบายที่ง่ายดี สรุปสั้นๆ คือระยะหว่างสองจุดในระนาบ N มิติ ใดๆ ที่ระยะห่างระหว่างแต่ละแกนถูก normalize แล้ว(คล้ายระยะห่างของ z score) ลองดูภาพประกอบดีกว่า
เครดิตภาพมาจากหนังสือ Learning OpenCV ของ OReilly นะครับ

จากจุดสามจุดในภาพนี่ ถ้าคำนวณระยะห่างตาม Euclidean distance แล้วผลที่ได้ออกมาก้อนล่างซ้ายจะอยู่ใกล้ก้อนบนมากกว่าก้อนขวา แต่มีคนเห็นว่าไม่แฟร์เพราะว่าแกน x ยาวกว่าตั้งเยอะ ต้องทำการ normalize ก่อนสิ ผลที่ได้จะเป็นดังรูปข้างขวา(ทำไม normalize แล้วสองแกนยังไม่เท่ากันนะ) สรุปคือถ้าวัดตาม mahalanobis distance แล้วจุดล่างซ้ายจะอยู่ใกล้จุดขวามากกว่าจุดบน

Learning OpenCV: Image Morphology (cont)

มาต่อกันที่ morphology กันอีกที ยังมี morphology พื้นฐานอีกหลายตัวที่สามารถใช้ได้กับ gray scale หรือ color image (ยังไม่เข้าใจว่าทำไม dilation, erosion ใช้ไม่ไ้ด้) ใน OpenCV ใช้คำสั่งดังนี้

void cvMorphologyEx(
    const CvArr*         src,
    CvArr*                  dst,
    CvArr*                  temp,
    IplConvKernel*      element,
    int                        operation,
    int                        iteration = 1
);

มีสิ่งที่เพิ่มมาจาก cvErode, cvDiate สองตัวคือ temp ใช้สำหรับเก็บค่า temp ของการคำนวณซึ่งอาจจะใช้หรือไม่ใช้ก็ได้ขึ้นอยู่กับ operation  ลองดูใน document (ละเอียดกว่านี้เดี๋ยวจะกลายเป็น document ย่อมๆ ไป) ส่วน operation จะมีค่าได้ดังนี้ CV_MOP_OPEN, CV_MOP_CLOSE, CV_MOP_GRADIENT, CV_MOP_TOPHAT, CV_MOP_BLACKHAT


Opening
นิยามของ opening ง่ายๆ คือเอา image มา erode แล้ว ค่อย dilate ใช้ในการลบ noise (เพราะว่า noise หายไปตอน erode แต่ขนาดของวัตถุเล็กลงก็เอาคืนด้วยการ dilate) ใช้ในการ ลบขอบที่ยื่นๆ ของวัตถุด้วย

Closing
ตรงข้างกับ opening คือการนำ image มา dilate แล้ว ค่อย erode ใช้ในการลบ small holes (หายไปตอบ dilate แล้วลดขนาดวัตถุที่บวมขึ้นมาด้วย erode) สามารถใช้ในการ เชื่อมวัตถุที่แยกจากกัน(เพราะ noise)

ทั้ง opening และ closing จะเหมือน erosion กับ dilation ต่างกันที่ opening กับ closing จะรักษาขนาดวัตถุไว้

Gradient
คือการนำภาพที่ dilate(บวม) มาลบด้วยภาพที่ erode(หด) ผลลัพธ์ที่ได้ก็จะเป็นขอบของภาพใช้ในการ detect edge

Top Hat, Black Hat
top hat ใช้ในการ isolate patch ที่สว่างกว่ารอบๆ ข้าง ในขณะที่ balck hat ใช้ isolate patch ที่มืดกว่ารอบๆ ข้าง โดย top hat คือ ภาพต้นฉบับ มาลบด้วย open(noise หาย ขอบยื่นๆ หาย ส่วน) ภาพที่ได้คือส่วนที่เป็นขอบยื่นๆ ที่หาย กับ noise ที่หายไปนั่นเอง ซึ่ง noise ในที่นี้คือพื่นที่สว่างถูก isolate(ไม่มีวัตถุ background สีขาว วัตถุสีดำ) ส่วน black hat ก็คือการนำ close(ภาพที่ holes หายไป และวัตถุ connected กับแล้ว) ลบด้วย ภาพต้นฉบับ ทำให้กลุ่ม patch ที่มืด(dark holes) ถูกแยกออกมา

Learning OpenCV: Image Morphology

มาถึงการ morph image กันแล้ว.(แปลเป็นภาษาไทยว่าอะไรดีหว่า)  เขาว่านิยมสำหรับการกำจัด noise แยกวัตถุ ต่างๆ ออกจากกัน หรือเชื่อมวัตถุต่างๆ เข้าด้วยกัน ใช้หา bumps(อะไรหว่า หลุมบ่อเหรอ) กับ holes

ตอนนี้มาเริ่มกันที่ตัวพื้นฐานเลยดีกว่า

พื้นฐานในการ morph เหล่านี้ส่วนใหญ่อ้างถึงภาพแบบ binary คือมีสองสี แต่สามารถอธิบายไปกับภาพที่เป็น intensity ได้ แต่อาจจะไม่ตรงนัก

Dilation
เดาจากชื่อได้ว่าเป็นการทำให้ image ใหญ่ขึ้นจริงๆ(จริงๆคือวัตถุใน image) คือการ นำ kernel (คือ shape ใดๆ มักจะเป็นวงกลมหรือสี่เหลี่ยม) โดยมีจุด anchor point (มักจะอยู่ตรงกลาง kernel) วางทับไปทั่ว image ภาพที่อยู่ภายใต้ image จะทำการหาค่า maximun แล้ว pixel ที่ตรงกับ จะถูกแทนที่ด้วยค่า maximum นั้น ผลลัพธ์คือรูปที่ได้จะมีขนาดใหญ่ขึ้น (เพราะพื้นที่ส่วนนอกวัตถุไม่ไกลมากเจอ max operator เข้าไป ทำให้พื้นที่ใำกล้ๆ วัตถุที่ว่างเปล่า (ค่า 0) กลายเป็นวัตถุ (ค่า 1) ไป)  และ ความเว้า จะหายไป(ถูกถมด้วยพื้นที่รอบๆ) และยังมีผลทำให้วัตถุใหญ่ๆ ที่อยู่ใกล้ๆ กันเชื่อมกัน(นัยว่ามันขาดไปเพราะเกิด noise)

Erosion
ก็ตรงข้ามกับ Dilation ที่ใช้ max operator ตัว Erosion จะใช้ min operator ทำให้พื้นที่บริเวณใกล้ๆ ขอบของวัตถุถูกกำจัดไปด้วย min operator ทำให้บริเวณผิวของวัตถุหายไป ตรงกับความหมายของ erosion ที่แปลว่ากัดกร่อน ผลที่ได้ วัตถุจะมีขนาดเล็กลง,  และส่วนที่ยื่นออกมาของวัตถุก็จะหายไป (กลับกันกับ Dilation)
และมีผลทำให้จุดด่างหรือวัตถุเล็กๆ ที่อยู่โดดๆ (น่าจะเป็น noise ชนิดหนึ่ง) หายไป

ใน OpenCV มีคำสั่งสำหรับ Dilation และ Erosion คือ
void cvErode(
    IplImage*             src,
    IplImage*             dst,
    IplConvKernel*    B = NULL,
    int                         iterations = 1
);

void cvDilate(
    IplImage*             src,
    IplImage*             dst,
    IplConvKernel*    B = NULL,
    int                         iterations = 1
);


ค่า B ถ้าไม่กำหนดจะเป็นสี่เหลี่ยมขนาด 3x3 เราสามารถกำหนด kernel ได้ด้วยคำสั่ง
IplConvKernel* cvCreateStructuringElementEx(
    int      cols,
    int      rows,
    int      anchor_x,
    int      anchor_y,
    int      shape,
    int*    values=NULL
);

cols, rows คือขนาดของสี่เหลี่ยมที่กำหนด kernel anchor_x, anchor_y คือตำแหน่ง anchor pixel ใน kernel shape จะเป็นค่า CV_SHAPE_RECT, CV_SHAPE_CROSS, CV_SHAPE_ELLIPSE, CV_CUSTOM คงเดาได้จากชื่อ ในส่วนของ CV_CUSTOM จะกำหนดรูปร่างของ kernel อีกที่ด้วย ตัวแปร values ที่แทน array ของ mask สำหรับสี่เหลี่ยมขนาด cols * rows

เสร็จแล้วก็อย่าลืม release ด้วยคำสั่ง

void cvReleaseStructuringElement(IplConvKernel** element);

Learning OpenCV: Smoothing image in OpenCV

ใน OpenCV มีฟังก์ชันการทำ smoothing image มาให้ ตอนแรกไม่เคยคิดมาก่อนว่า smoothing = blurring การทำ smoothing ใน OpenCV ใช้คำสั่ง

void cvSmooth(
      
    const   CvArr*     src,
    CvArr*                dst,
    int                        smoothtype =CV_GAUSSIAN,
    int                        param1=3,
    int                        param2=0,
    int                        param3=0,
    int                        param4=0
);

โดยรวมแล้วคือการเฉลี่ยค่าของ pixel (จริงๆ คือค่าใช้ CvArr นั้นๆ แต่ต่อไปนี้ถือว่าเราประมวลกับภาพ จึงขอเรียก pixel เพื่อความสะดวก) นั้นๆ ด้วย pixel ที่อยู่รอบข้าง

ตัวแปร smoothtype มีค่าคือ CV_BLUR, CV_BLUR_NO_SCALE, CV_MEDIAN, CV_GAUSSIAN, CV_BILATERAL

CV_BLUR คือการทำเฉลี่ยค่า pixel ด้วยค่า pixel รอบๆ ข้าง โดยขนาด pixel รอบข้างกำหนดไว้ใน param1, param2

CV_BLUR_NO_SCALE คือผลรวมค่า pixel ด้วยค่า pixel รอบๆ ข้าง(แบบเดียวกับ CV_BLUR) ผลลัพธ์ที่ได้จะเร็วกว่า CV_BLUR (เพราะใช้ผลรวมในขณะที่ CV_BLUR ต้องหารด้วย param1*param2 อีกที) ข้อควรระวังคือ dst จะต้องมี bit depth ที่มากกว่า src ไม่งั้นจะเกิดการ overflow ได้

CV_MEDIAN เหมือน CV_BLUR แต่ใช้ค่ามัธยฐานแทนค่าเฉลี่ยเลขคณิต ด้วยคุณสมบัติของมัธยฐานที่ส่วนใหญ่ไม่มีผลกระทบต่อข้อมูลที่โดดในกลุ่ม ดังนั้นการใช้วิธีนี้จะทนต่อ noisy image ได้ดีกว่าแบบ CV_BLUR ทั้งสองแบบ

CV_GAUSSIAN คือการใช้ค่าเฉลี่่ยแบบ GAUSSIAN โดยค่า pixel แต่ละ pixel จะถูกถ่วงน้ำหนักด้วยด้วยค่าที่แปรผกผันกับระยะห่างจาก pixel ที่ต้องการ  ยิ่งไกลมากผลของ pixel นั้นก็จะน้อย param1, param2 แสดงถึงขนาดของ filter window  param3,param4 คือค่า σ ในแนวนอนและแนวตั้ง Gaussian distribution ตามลำดับ 


ใน OpenCV มีการ optimize โคดเป็นพิเศษสำหรับ gaussian kernel ขนาด 3x3, 5x5, 7x7 โดยตั้งค่า param3=0


แต่จุดอ่อนของ GAUSSIAN smooth คือ gaussian assume ว่า noise จะเกิดกระจายได้ทั่วๆไม่ได้กระจุกที่ใดที่หนึ่งเป็นพิเศษ และค่าของ pixel จะมีความกระจายแบบต่อเนื่่อง(เข้าใจว่าเป็นผลมาจากการ assumption ว่าเป็นกระจายแบบ gaussian) ซึ่งในส่วน edge ของภาพ pixel จะกระจายไม่ต่อเนื่องกับ pixel ข้างเคียง(ก็เป็นขอบนี่มันก็ต้องตัดกันสิ) ผลที่ได้คือ Gaussian จะ smooth ส่วน edge ทิ้งไป

CV_BILATERAL โดยหลักการของ bilateral filtering นั้นจะคำนวณ pixel โดยถ่วงน้ำหนักจากทั้ง space (ระยะห่าง ยิ่งห่างมากยิ่งมีน้ำหนักน้อย) และ range(ความแตกต่างของสียิ่งแตกต่างมากยิ่งมีน้ำหนักน้อย) ใน OpenCV การถ่วงน้ำหนักที่ใช้ จะใช้ Guassian distribution ทั้ง space และ range(จริงๆ ใช้ distribution อื่นก็ได้) การ smooth แบบนี้จะรักษา edge ของภาพไว้ได้ดีกว่าแบบ CV_GAUSSIAN  param1 จะเป็นค่า σ สำหรับ spatial domain, param2 จะเป็นค่า σ สำหรับ color domain


ค่า σ ใน gaussian distribution จะหมายถึงค่า standard deviation ของการกระจ่ายการตั้งค่า σ ให้มากจะเป็นเพิ่มน้ำหนักให้กับ pixel (เพราะในความเป็นจริงตามสูตร ระยะห่างทั้ง space และ color จะคิดเป็นจำนวนเท่าของ σ)






Learning OpenCV: trivial thing in OpenCV

หลังจากที่ได้ค้นพบ EgmuCV แล้ว ความจำเป็นที่ต้องใช้ highgui สำหรับผม ก็หมดไป ดังนั้นขอสรุปสั้นก่อนจะเข้าเนื้อหาทฤษฎี เลยแล้วกัน

สิ่งที่ยังได้กล่าวถึงในเรื่อง function พื้นฐานของ OpenCV คือการวาดรูป OpenCV มีคำสั่งในการวาดรูป(แน่นอนวาดใน CvArr) อยู่จำนวนหนึ่ง ลองยกตัวอย่างมาดู

  • cvLine 
  • cvRectangle
  • cvCircle
  • cvEllipse
  • cvFillPoly
  • cvFillConvexPoly
  • cvPutText
ทั้งหมดนี้ดูจากชื่อก็คงเดาได้ว่าเอาไว้ทำอะไร แล้วยังมีฟังก์ชันในการจัดการไฟล์อยู่จำนวนหนึ่ง ซึ่งลองอ่านตามคงไม่มีปัญหา แต่อาจจะต้องกล่างถึงการเขียนไฟล์อีกทีหนึ่งทีเดียวกับตอน serialize ใน C#

ส่วนเรื่องของ highgui ขอสรุปรวบยอดว่าทำอะไรได้บ้าง

  • สร้าง window ได้
  • โหลด, และแสดง image ได้
  • รับคีย์บอร์ด input กับ เมาส์ได้
  • มี trackbar ให้
  • จัดการ video ได้ (ทั้งจากไฟล์ และจากกล้อง)
  • อ่าน frame ในวีดีโอได้
  • เขียนไฟล์วีดีโดได้
  • แปลง format ของ image ได้
เท่านี้คงพอเห็นแล้วว่า OpenCV จะทำอะไรได้บ้าง และคงเขียนโคดต่อไดแล้ว เรื่องต่อไปจากที่ดูแล้วหนักแต่ทฤษฏีไม่ค่อยเกี่ยวกับ coding เท่าไร (สบายดี)

Using OpenCV in Visual Studio 2008 with EmguCV

เมื่อวานนั่งลองเขียนโปรแกรม OpenCV บน VS 2008 ดู ให้อารมณ์เหมือนเขียนโปรแกรมบนดอสสมัยก่อนเลย ปุ่มไม่มีคอนโทรลไม่มีให้.. เลยตัดสินใจว่าจะเขียนเป็น windows app เสียหน่อย  ตอนแรกตั้งใจจะเขียนเป็น Visual C++/CLI (C++ version ของ Microsoft ที่ support .NET และ native app) แต่ตัดสินใจหา Google ก่อนปรากฏว่าเจอโปรเจกต์ emguCV ที่เป็น wrapper สามารถเรียกใช้ OpenCV library โดยใช้ C# ได้เลย เลยรอดตัวไม่ต้องใช้ C++/CLI

สรุปวิธีการติดตั้งนะครับ

  1. เนื่องจากโปรแกรมนี้ใช้ WCF (Windows Communication Foundation) ต้องลง .NET 3.0 ก่อน
  2. โหลดตัว install ที่นี่ได้เลย http://sourceforge.net/project/showfiles.php?group_id=216500
  3. install ไปตามขั้นตอนปกติ อาจจะให้อัพเกรดอะไรนิดหน่อย.
  4. เปิด VS 2008 แล้วเปิดโซลูชัน Emgu.CV.Example.sln แล้วลองรันดู ตามขั้นตอนปกติควรจะรันโปรแกรมทดสอบผ่าน
โปรแกรมตัวอย่างที่ให้มาก็มี application เยอะอยู่ พอจะนำไปประยุกต์ใช้ได้หลายงานเลย หลังจากนั้นลองมาสร้าง project ใหม่ดู

  1. สร้าง project หรือ solution ใหม่
  2. add zlib.net.dll, Emgu.Utils.dll, Emgu.CV.dll ใน reference ของ project
  3. copy dll ของ OpenCV(cvXXX.dll, cvauxXXX.dll, cxcoreXXX.dll, highguiXXX.dll, opencv_ffmpegXXX.dll, mlXXX.dll and cvextern.dll) ไปไว้ใน execute directory (ขั้นตอนนี้ส่วนตัวไม่ได้ทำ รันผ่านด้วย)
  4. เขียนโปรแกรมตามปกติโดยใส่ namespace เหล่านี้ไปด้วย
          using Emgu.CV;
          using Emgu.CV.Structure;


เท่านี้ก็น่าจะเริ่มเขียน OpenCV ด้วย C# บน VS2008 ได้แล้ว

ลองดู tutorial เพิ่มเติมได้ที่
http://www.emgu.com/wiki/index.php/Tutorial