Thursday, April 15, 2010

Learning OpenCV: more about image segmentation in OpenCV

ต่อจากเรื่อง image morphology ยังมีเรื่องค้างอีกนิดหน่อยเีกี่ยวกับการ segment ข้อมูล
เรื่องการคือการทำ Flood Fill แนวคิดของ flood fill นี้อาจจะเคยเห็นกันมาแล้วบ้างจากโปรแกรมแต่งรูปต่างๆ
คือจะมีเลือกจุดในรูปภาพ (seed point) แล้วทั้ง seed point และจุดรอบข้างที่มีสีเหมือนกัน(หรือใกล้เคียงกันตามกำหนด) จะถูกแทนที่ด้วยสีที่กำหนด และประเด็นคืออะไร จริงๆ แล้วประเด็นสำคัญคือ ผลที่ได้จาก flood ่่fill operation นั้นจะเป็นพื้นที่ๆ ต่อเนื่องกันพื้นที่หนึ่ง เรียกได้ว่าเป็นการ segment ด้วยสี ลองดูการประกาศฟังก์ชัน


void cvFloodFill(
    IplImage* img,
    CvPoint seedPoint,
    CvScalar newVal,
    CvScalar loDiff = cvScalarAll(0),
    CvScalar upDiff = cvScalarAll(0),
    CvConnectedComp* comp = NULL,
    int flags = 4,
    CvArr* mask = NULL
);

พารามิเตอร์จะเป็น img รูปภาพที่จะ fill, seedPoint คือตำแหน่ง seed point  newVal คือสีที่จะ fill loDiff กับ upDiff คือผลต่างค่าที่จะยอมรับได้ที่จะต้องแทนที่สี(หรือว่าเป็นพื้้นที่เดียวกันนั่นเอง) ปกติผลต่างนัั้นจะคำนวณระหว่าง pixel ที่ติดกันยกเว้นถ้ามี flag CV_FLOODFILL_FIXED_RANGE จะคำนวณผลต่างจาก seed point เท่านั้น

CvConnectedComp จะเก็บข้อมูลสถิติเกี่ยวกับพื้นที่ๆ ถูก fill จะยกยอดไปอธิบายในเรื่อง Image Pyramids


mask ถ้ากำหนดจะต้องมีขนาด 1 channel และขนาดกว้างและยาวกว่า img (เพื่อการคำนวณ filter ภายใน) โดย pixel (x+1,y+1) ใน mask จะ map กับ x,y ใน img โดยการทำงาน Flood Fill จะ fill img เฉพาะในส่วนที่ mask ตรงตำแหน่งเป็น 0 
ผลการ Flood Fill สามารถ กำหนดให้ fill ทั้ง  img และ mask หรือ fill เฉพาะ mask อย่างเดียวก็ได้


flag จะประกอบด้วยข้อมูลสามส่วน
        แปดบิทล่าง จะมีค่าเป็น 4,8 ใช้กำหนดทิศทางในการ fill โดย 4 จะเป็นการ fill เฉพาะแนวตั้งและแนวนอนและ 8 จะ fill รอบด้านทั้งแปดทิศ
        แปดบิทกลาง จะกำหนดว่า mask จะถูกกำหนดว่า mask จะถูก fill ด้วยค่าอะไรถ้า set เป็น 0 จะถูก fill ด้วยค่า 1s(127)
        แปดบิดบน จะเป็นการเซตค่า CV_FLOODFILL_FIXED_RANGE (ที่อธิบายไปแล้ว) และ CV_FLOODFILL_MASK_ONLY fill เฉพาะ mask เท่านั้น

ลองยกตัวอย่าง flag ถ้าเราต้องการให้  fill ทั้งแปดทิศ, fill เฉพาะ mask, fill mask ด้วยค่า 47

flag = 8 | CV_FLOODFILL_MASK_ONLY | CV_FLOODFILL_FIXED_RANGE | (47<<8)

(น่าจะไม่มีปัญหา bitwise or และ shift bit ธรรมดา)

Learning OpenCV: Sequences

ประเภทข้อมูล sequence เป็นข้อมูลที่เก็บไว้ใน memory storage (จากตอนที่แล้ว) ตัวมันเองเป็น linked list ชนิดหนึ่งที่ชี้ไปยังโครงสร้างข้อมูลแบบอื่น ลองดูการประกาศ sequence


typedef struct CvSeq {
    int flags; // miscellaneous flags
    int header_size; // size of sequence header
    CvSeq* h_prev; // previous sequence
    CvSeq* h_next; // next sequence
    CvSeq* v_prev; // 2nd previous sequence
    CvSeq* v_next // 2nd next sequence
    int total; // total number of elements
    int elem_size; // size of sequence element in byte
    char* block_max; // maximal bound of the last block
    char* ptr; // current write pointer
    int delta_elems; // how many elements allocated
    // when the sequence grows
    CvMemStorage* storage; // where the sequence is stored
    CvSeqBlock* free_blocks; // free blocks list
    CvSeqBlock* first; // pointer to the first sequence block
}

จะเห็นมีข้อมูลที่สำคัญอยู่คือ total ที่บอกจำนวนในลิสต์ กับ pointer สำหรับ linked list สี่ตัวคือ h_prev, h_next, v_prev, v_next เป็น linked list ที่ชี้ได้สี่ทิศเลยทีเดียว

โดยตามปกติเช่นเดียว linked list ทั่วไป sequence จะสามารถจัดการข้อมูลที่อยู่บนหัวท้ายได้ดีกว่าข้อมูลที่อยู่ตรงกลาง 

โดยปกติ ฟังก์ชันใน OpenCV จะสร้าง sequence ให้เราโดยอัตโนมัติ เราแค่เรียกใช้ sequence ให้ถูกก็พอ ในกรณีที่ต้องการสร้างและลบ sequence เอง ใช้คำสั่งต่อไปนี้


CvSeq* cvCreateSeq(
    int seq_flags,
    int header_size,
    int elem_size,
    CvMemStorage* storage
);

แล้วลบด้วย


void cvClearSeq(
    CvSeq* seq
);



sequence ใช้หน่วยความจำใน memory storage (ที่ผ่าน parameter ใน cvCreateSeq) ในตอนที่ใช้คำสั่ง cvClearSeq หน่วยความจำจะไม่ถูก recycle (อ้างอิงจากตอนที่แล้ว เรื่อง memory storage)


คำสั่งจริงๆ ที่น่าจะใช้ของ sequence น่าจะเป็นการเข้าถึง object ใน sequence มากกว่า ลองดูคำสั่งตัวอย่างในหนังสือ


for( int i=0; itotal; ++i ) {
    CvPoint* p = (CvPoint*)cvGetSeqElem ( seq, i );
    printf(“(%d,%d)\n”, p->x, p->y );
}

จะเป้นการใช้คำสั่ง cvGetSeqElem โดยใส่ sequence เป็นพารามิเตอร์และ index i ที่ต้องการอย่าลืม casting ให้เป็นข้อมูลทีเราต้องการด้วย


คำสั่งอื่นๆ ในการจัดการ sequence จะขอลิสต์ไว้ ต่อไปนี้

คำสั่งในการ ค้นหา element ใน sequence

  • cvSeqElemIdx
คำสั่งในการเข้าถึง manipulate sequence

  • cvCloneSeq
  • cvSeqSlice
  • cvRemoveSlice
  • cvInsertSlice
การเรียง sequence การแบ่ง sequence และการค้นหาข้อมูลใน sequence

  • cvSeqSort
  • cvSeqSearch
  • cvSeqInvert
  • cvSeqPartition
ในฟังก์ชันส่วนนี้จะต้องกำหนดฟังก์ชันสำหรับเปรียบเทียบด้วย เพื่อที่ OpenCV จะได้เข้าใจว่า object สองตัวเรียงกันอย่างไร (เรียงตาม x หรือ y ใครที่เคยเขียน quick sort ใน C น่าจะนึกภาพออก) โดยกำหนดฟังก์ชันในรูปแบบนี้

typedef int (*CvCmpFunc)(const void* a, const void* b, void* userdata );

การใช้ sequence เป็น stack (สองหัวด้วยนะ)

  • cvSeqPush
  • cvSeqPop
  • cvSeqPushFront
  • cvSeqPopFront
  • cvSeqPushMulti
  • cvSeqPopMulti


การแทรก element กลาง sequence

  • cvSeqInsert
  • cvSeqRemove


การเปลี่ยนขนาด memory block size (จะได้ใช้เหรอ)

  • cvSetBlockSize
การแปลงระหว่าง array กับ sequence

  • cvCvtSeqToArray
  • cvMakeSeqHeaderForArray

ส่วนสุดท้ายคือการอ่านเขียน sequence ซึ่งมีประสิทธิภาพมากกว่าการ insert การ ใช้ฟังก์ชัน cvGetSeqElem โดยใช้วิธีสร้างตัว sequence reader กับ sequence writer ขึ้นมา การจัดการดูแล้วเหมือนการอ่านเขียนไฟล์ใน C

คำสั่งที่เกี่ยวข้องกับ sequence writer
  • cvStartWriteSeq
  • cvStartAppendToSeq
  • cvEndWriteSeq
  • cvFluseSeqWriter
  • CV_WRITE_SEQ_ELEM
  • CV_WRITE_SEQ_ELEM_VAR
คำสั่งที่เกี่ยวข้องกับ sequence reader
  • cvStartReadSeq
  • cvGetSeqReaderPos
  • cvSetSeqReaderPos
  • CV_NEXT_SEQ_ELEM
  • CV_PREV_SEQ_ELEM
  • CV_READ_SEQ_ELEM
  • CV_REV_READ_SEQ_ELEM
เนื่องจากการเขียนใน sequence writer น่าจะสำคัญจะอาจจะได้ใช้ เลยยกตัวอย่างในหนังสือมาเสียหน่อย





We will create a writer and append a
hundred random points drawn from a 320-by-240 rectangle to the new sequence.
CvSeqWriter writer;
cvStartWriteSeq( CV_32SC2, sizeof(CvSeq), sizeof(CvPoint), storage, &writer );
for( i = 0; i < 100; i++ )
{
    CvPoint pt; pt.x = rand()%320; pt.y = rand()%240;
    CV_WRITE_SEQ_ELEM( pt, writer );
}
CvSeq* seq = cvEndWriteSeq( &writer );


Learning OpenCV: Memory Storage in OpenCV

ข้ามมาว่ากันด้วยเรื่อง memory storage กันดีกว่า OpenCV มี dynamic object
ที่เรียกว่า memory storage ซึ่งก็คือ linked lists(ไม่เคยใช้ตั้งแต่จบ data structure ตอนปีสอง) ที่จะ allocate, de-allocate memory block เนื่องจากใน OpenCV
เองมีส่วนการทำงานที่ต้องใช้ memory ส่วนนี้อยู่หลายคำสั่ง คำสั่งที่เกี่ยวกับ memory storage มีประมาณนี้

cvMemStorage* cvCreateMemStorage(int block_size = 0);
void cvReleaseMemStorage(CvMemStorage** storage);
void cvClearMemStorage(CvMemStorage* storage);
void* cvMemStorageAlloc(CvMemStorage* storage,size_t size)

เราใช้คำสั่ง cvMemStorage ในการสร้าง linked list โดยกำหนดขนาด block ของ memory ซึ่งคิดว่าค่า default ที่กำหนดให้ขนาด block = 64k น่าจะโอเค (เพราะไม่มีความรู้ด้านนี้ 555) หลังจบก็ใช้คำสั่ง cvReleaseMemStorage ตาม step การ alloc, dealloc ใน C

มีอย่างหนึ่งที่น่าสังเกตคือคำสั่ง cvClearMemStorage ที่จะช่วยคืนพื้นที่ที่สร้างขึ้นจากคำสั่ง cvCreateMemStorage นี้ เพราะว่าการลบ object ใดๆ ที่อยู่ในพื้นที่นี้ (object เหล่านี้จะเกิดขึ้นในบางฟังก์ชันของ OpenCV เช่น sequences ที่จะกล่าวถึง) จะเป็นการลบ object นั้นทิ้งไปเท่านั้นไม่ได้คืนพื้นที่หน่วยความจำ (ก็หน่วยความจำนี่ cvCreateMemStorage เป็นคนจองนี่ object ในนั้นก็ชี้ไปยังพื้นที่ในนั้น อีกอย่าง C ไม่มีการ garbage collection ด้วย)
ดังนั้นถ้าต้องการจะ recycle memory ก็ใช้คำสั่ง cvClearMemStorage ได้ (จะได้ใช้ถึงขั้นนั้นไหม)

ส่วนคำสั่งสุดท้ายคือ cvMemStorageAlloc เป็นจองเนื้อที่ในหน่วยความจำที่อยู่ใน storage (ยังไม่รู้เลยว่าจะใช้ตอนไหน)

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 จะคิดเป็นจำนวนเท่าของ σ)