Thursday, April 8, 2010

Remote Sensing Software

ปีนี้ผมมีงานวิจัยที่เกี่ยวข้องกับ Remote Sensing Image Processing ครับ เลยต้องหาซอฟต์แวร์ทางด้านนี้มาทดลองใช้งาน ตัวดังๆก็ได้แก่ ARCGIS ERDAS แต่ไม่มีตังค์ซื้อครับ เลยต้องทดลองหาของฟรีมาใช้ ที่สนใจอยู่ตอนนี้ก็มี 3 ตัวครับ คือ
  1. MultiSpec ตัวเล็ก ทำได้หลายอย่าง เสียอย่างเดียวไม่เป็น open source และไม่มีระบบ plug-in น่าจะเหมาะกับการเรียนรู้มากกว่าเอาไปใช้งานจริง
  2. Opticks น่าสนใจครับ เป็น open source ด้วย สนับสนุน plug-in (แต่มีน้อยเหลือเกิน) ดูจะขาดเครื่องมือทางด้าน clustering อยู่ :(
  3. GRASS GIS อันนี้ตัวใหญ่เลยครับ ทำได้ทั้งทางด้าน GIS+remote sensing แต่ว่าคู่มือ รายละเอียดต่างๆยังไม่ค่อยชัดเจนเท่าไหร่ เป็นความหวังอันหนึ่งเลยครับ
ตอนนี้คงต้องลองๆไปก่อนละครับ หวังว่าสุดท้ายคงไม่ต้องกลับไปใช้ matlab หรือ OpenCV นะ _/|\_

Wednesday, April 7, 2010

OpenCV 2.1 มาแล้วครับ

OpenCV 2.1 มีให้ดาวน์โหลดไปใช้งานแล้วครับ ตั้งแต่วันที่ 6 เมษายน 2553 ตามเอกสารระบุว่ามีการแก้ไข bug มากกว่า 200 รายการ สนับสนุนระบบปฏิบัติการแบบ 64 บิต และแบบอื่นๆ อีกมากมาย

ลองไปดาวน์โหลดได้ที่ http://sourceforge.net/projects/opencvlibrary/files/ ครับ

สำหรับ Windows ควรเลือกไฟล์  OpenCV-2.1.0-win32-vs2008.exe นะครับ

ขั้นตอนการติดตั้งและใช้งานกับ Dev-CPP
สำหรับเวอร์ชันนี้ผมคิดว่าสนับสนุนการใช้งานกับ Visual Studio 2008 อยู่แล้วตามชื่อไฟล์ เราลองมาติดตั้งและใช้งานกับ Dev-CPP กันดูครับ

1. uninstall OpenCV เวอร์ชันเดิมออกก่อน
2. ติดตั้งเวอร์ชันใหม่ สมมติว่าติดตั้งที่ C:\OpenCV2.1
3. เปิดโปรแกรม Dev-CPP เลือกหัวข้อ Tools/ Compiler Options
4. แก้ไขคำสั่งเพิ่มเติมในส่วนของ Linker ให้เป็น -lcv210 -lcvaux210 -lcxcore210 -lhighgui210 -lml210 ตามรูป

5. แก้ไขส่วนของแทบ Directories
5.1 หัวข้อ Binaries เพิ่ม C:\OpenCV2.1\bin

5.2 หัวข้อ libraries เพิ่ม C:\OpenCV2.1\lib

5.3 หัวข้อ C-Includes และ C++-Includes ให้เพิ่ม C:\OpenCV2.1\include\opencv

จากนั้นทดสอบโปรแกรมโหลดและแสดงผลรูปตามนี้ครับ สมมติว่ามีไฟล์รูป lena.jpg อยู่ในไดเรกทอรีเดียวกับตัวรหัสโปรแกรมนะครับ


ข้อดีในการติดตั้ง
ไม่ต้องแก้ไขไฟล์ cxoperations.hpp เหมือนในเวอร์ชัน 2.0 อีก

ปัญหาที่พบ
 ผมไม่สามารถใช้คำสั่งภายใต้ namespace cv ได้เลย เช่นคำสั่ง imread, imshow, namedWindow, waitKey ตัวคอมไพเลอร์แจ้งว่ามีข้อผิดพลาดที่ linker ไม่เจอคำสั่งเหล่านี้ ทั้งๆที่ในเวอร์ชัน 2.0 กลับทำได้ ต้องลองหาทางแก้ต่อไปครับ

Tuesday, April 6, 2010

การเปลี่ยนขนาดรูป (Resize)

เมื่อต้องการเปลี่ยนขนาดของรูป เช่น ย่อ หรือ ขยาย เราสามารถใช้คำสั่ง cvResize() ได้ ตามรูปแบบดังนี้

cvResize(imgin, imgout, type)

เมื่อ imgin imgout และ type คือรูปต้นฉบับ รูปผลลัพธ์ และ ชนิดของการทำ interpolation ตามลำดับ

สังเกตว่า คำสั่งนี้ไม่มีการระบุว่าให้เปลี่ยนขนาดของรูปเป็นเท่าใด ทั้งนี้เพราะ รูปจะถูกเปลี่ยนให้มีขนาดเท่ากับรูปผลลัพธ์นั่นเอง ดังนั้น เราจำเป็นต้องกำหนดขนาดใหม่ที่ต้องการให้กับรูปผลลัพธ์ไว้ล่วงหน้า

ชนิดของการทำ interpolation มีสี่รูปแบบคือ CV_INTER_NN (Nearest neighbor), CV_INTER_LINEAR (Bilinear), CV_INTER_AREA (Pixel area resampling) และ CV_INTER_CUBIC (Bicubic)

ลองพิจารณาัตัวอย่างต่อไปนี้

ผลลัพธ์

แปลงภาพสีเป็นภาพขาวดำ 2 (Color to grayscale image)

บทความก่อนหน้านี้แสดงการแปลงภาพสีให้เป็นภาพขาวดำ โดยหาค่าเฉลี่ยของพิกเซลสีในแต่ละ channel

OpenCV ได้เตรียมคำสั่งสำหรับการเปลี่ยนแปลง channel ของสีไว้ให้ใช้งานได้ง่ายยิ่งขึ้น ด้วยคำสั่ง cvCvtColor เช่น
    cvCvtColor(img, out, CV_BGR2GRAY);
คือการเปลี่ยนรูป img ให้เป็น out โดยมีการเปลี่ยนจากรูปสี (BGR) เป็น grayscale (CV_BGR2GRAY)

คำสั่งนี้ยังสามารถใช้เปลี่ยนรูปแบบ channel ของสี เช่น จาก RGB เป็น HLS, HSV, YCrCb หรืออื่นๆได้ด้วย

พิจารณารหัสโปรแกรมดังต่อไปนี้


ผลลัพธ์

Monday, April 5, 2010

แปลงภาพสีเป็นภาพขาวดำ (Color to grayscale image)

หากเรามีภาพสี และต้องการเปลี่ยนให้เป็นภาพขาวดำ จะทำได้อย่างไร

หลักการง่ายๆก็คือ หาค่าเฉลี่ยของแม่สีสามสี คือ น้ำเงิน เขียวและแดง นั่นคือ
G = (B+G+R)/3
อย่างไรก็ตาม เราพบว่าตาของเราตอบสนองต่อแม่สีทั้งสามต่างกัน จึงมีผู้เสนอให้ใช้สมการต่อไปนี้แทน
G = 0.114B + 0.587G + 0.299R

หากเราต้องการเขียนโปรแกรมใน OpenCV เพื่อตอบโจทย์นี้ เราต้อง
-สร้างรูปเปล่าที่เป็น grayscale ขึ้นเพื่อเก็บผลลัพธ์
-คำนวณผลรวมพิกเซลจากรูปสี แล้วบันทึกลงในพิกเซลของรูป grayscale ณ ตำแหน่งเดียวกัน

ตัวอย่างรหัสโปรแกรมจะเป็นดังนี้


ผลลัพธ์

จากโปรแกรมข้างต้น มีข้อสังเกตดังนี้

1. เราสร้างรูป grayscale เปล่าๆ ด้วยคำสั่ง
IplImage* out = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U,1);

จากพารามิเตอร์ของคำสั่ง cvCreateImage จะอธิบายได้ว่า เราต้องการสร้างรูปที่มีขนาดเท่ากับรูปต้นฉบับ (cvGetSize(img)) ให้เป็นแบบ unsigned 8 บิต (IPL_DEPTH_8U) และมีเพียง 1 channel

2. เราใช้พอยน์เตอร์สองตัว ตัวแรกชี้ที่รูปต้นฉบับ อีกตัวชี้ที่รูปผลลัพธ์

3. OpenCV มีการเรียง channel เป็นแบบ blue green red

4. เราหาผลรวมของแต่ละสี แล้วใช้พอยน์เตอร์เปลี่ยนค่าของรูป grayscale

5. หากบันทึกรูปผลลัพธ์นี้ จะได้รูปที่เป็น 8 บิต grayscale ด้วย

บันทึกไฟล์ภาพ

เราทราบดีแล้วว่า การอ่านไฟล์ภาพจะใช้คำสั่้ง
cvLoadImage()

ตรงกันข้าม หากเราต้องการบันทึกภาพเป็นไฟล์ เราจะใช้คำสั่ง
cvSaveImage() ซึ่งมีรูปแบบคือ
int cvSaveImage(const char* filename, const CvArr* image)

เช่น  หากเรามีรูปในหน่วยความจำที่เก็บในตัวแปร imgout และต้องการบันทึกเป็นไฟล์ชื่อ lenaNegative.jpg เราจะใช้คำสั่งได้ว่า
    if(!cvSaveImage("lenaNegative.jpg",imgout))
        cout<<"Could not save the image";

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

  • Windows bitmaps - BMP, DIB
  • JPEG files - JPEG, JPG, JPE
  • Portable Network Graphics - PNG
  • Portable image format - PBM, PGM, PPM
  • Sun rasters - SR, RAS
  • TIFF files - TIFF, TIF

Sunday, April 4, 2010

Image Negative

ในบทความก่อนหน้านี้ เราพูดถึงการหาค่า negative ของภาพแบบ grayscale ไปแล้ว ในบทความนี้ จะกล่าวถึงการหาคำนวณ image negative ของภาพใดๆรวมถึงภาพสี โดยการประมวลผลทีละพิกเซล

ก่อนที่จะทำการคำนวณ เราจะต้องพิจารณาถึงลักษณะการเก็บค่าพิกเซลในหน่วยความจำเสียก่อน ซึ่งโดยทั่วไปแล้ว ภาพจะประกอบด้วยเฟรมสามเฟรมซ้อนกัน นั่นคือเฟรมของสีแดง เขียว และน้ำเงิน เราอาจเรียกเฟรมดังกล่าวว่า channel (ใน OpenCV จะมีการเรียง channel เป็นแบบ น้ำเงิน เขียว แดง หรือ BGR)

ในอีกมุมมองหนึ่ง รูปภาพก็อาจจะมองได้ว่าเป็นอาร์เรยสามมิติ ที่ประกอบไปด้วยความสูง (แถว) ความกว้าง (คอลัมน์) และความลึก (channel) นั่นเอง


OpenCV มีการเก็บข้อมูลรูปในลักษณะที่ไม่ใช่อาร์เรย์สามมิติ แต่เป็นลักษณะเป็นแถวเรียงกันไป (อาจคิดได้ว่าเป็นเวกเตอร์) ดังนี้

นั่นคือ มีการเก็บพิกเซลของแต่ละสี (channel) เรียงต่อกันไป จากซ้ายไปขวาและจากบนลงล่าง

ดังนั้น หากต้องการเข้าถึงแต่ละพิกเซล จะได้ว่าตำแหน่งของพิกเซลใดๆ (x) จะมีค่าเป็น
x = (r*widthStep) + (c*nchannel) + k;
เมื่อ r และ c คือตำแหน่งของแถวและคอลัมน์ของภาพ nchannel คือ จำนวนของ channel ทั้งหมด (ในที่นี้เป็น 3) และ k คือค่าของ channel ที่กำลังสนใจ (มีค่า 0-2)

หลักการนี้สามารถนำไปใช้ในการคำนวณ image negative ได้ดังรหัสโปรแกรมต่อไปนี้



ให้สังเกตในส่วนของการประมวลผลว่ามีการใช้ถึงสามลูปซ้อนกัน นั่นคือ แถว คอลัมน์ และ channel

ผลลัพธ์

Friday, April 2, 2010

Clone รูป + หน้าต่างใหม่

ในตัวอย่างที่แล้วเราทำการหาค่า negative ของรูป ซึ่งแสดงผลลัพธ์เพียงอย่างเดียว หากเราต้องการแสดงทั้งรูปตั้งต้นและผลลัพธ์พร้อมๆกันเพื่อให้ง่ายต่อการเปรียบเทียบ เราจะต้องเพิ่มสองขั้นตอนต่อไปนี้คือ
  1. สำเนารูปภาพไว้ (clone) เพื่อให้รูปต้นฉบับยังคงเดิม
  2. สร้างหน้าต่างเพิ่มอีกหนึ่งหน้าต่างเพื่อรองรับรูปที่เพิ่มขึ้น
การสำเนารูปภาพ เราจะใช้คำสั่ง cvCloneImage ซึ่งมีรูปแบบดังนี้
IplImage* cvCloneImage(const IplImage* image)
เช่น
IplImage* imgout = cvCloneImage(img);

ส่วนการสร้างหน้าต่างใหม่ สามารถใช้คำสั่ง
cvNamedWindow
เหมือนตัวอย่างที่แล้วมา

ลองพิจารณาการปรับปรุงรหัสโปรแกรมเพื่อแสดงผลภาพ negative ใหม่ ดังต่อไปนี้


ผลลัพธ์

Thursday, April 1, 2010

Grayscale Image Negative

ในบทความต่อไปนี้เราจะทดลองประมวลผลภาพในระดับสีเทา (grayscale) โดยทำการกลับสี (image negative) นั่นคือ ปกติแล้วระดับสีเทาของภาพแบบ 8 บิตจะอยู่ในช่วง 0-255 ดังนั้นการกลับสีก็คือการหาค่า 255-ค่าพิกเซล นั่นเอง ตัวอย่างเช่น

หากค่าพิกเซลเป็น 0 จะกลายเป็น 255
หากค่าพิกเซลเป็น 255 จะกลายเป็น 0
หากค่าพิกเซลเป็น 100 จะกลายเป็น 155

ลองพิจารณารหัสโปรแกรมดังต่อไปนี้


ผลลัพธ์


คำอธิบาย
ในตัวอย่างนี้ เราต้องการโหลดรูปในรูปแบบ grayscale ดังนั้นในคำสั่ง cvLoadImage เราจึงใช้พารามิเตอร์เพิ่มคือ CV_LOAD_IMAGE_GRAYSCALE เพื่อบังคับให้รูปที่ได้เป็นแบบเฉดสีเทาเท่านั้น

โดยทั่วไปแล้ว คำสั่ง cvLoadImage จะมีพารามิเตอร์ที่เป้นไปได้สามค่าคือ
  • CV_LOAD_IMAGE_COLOR the loaded image is forced to be a 3-channel color image
  • CV_LOAD_IMAGE_GRAYSCALE the loaded image is forced to be grayscale
  • CV_LOAD_IMAGE_UNCHANGED the loaded image will be loaded as is.
จากนั้นเรามีการตรวจสอบว่าสามารถโหลดรูปได้หรือไม่ โดย


หากโหลดรูปได้สำเร็จ เราจะทำการประมวลผลแต่ละพิกเซลของรูป ซึ่งโครงสร้างของรูปใน OpenCV ที่เรียกว่า IplImage จะประกอบไปด้วยองค์ประกอบมากมาย และมีการจัดเก็บ ดังนี้

ดังนั้น ในการจัดการแต่ละพิกเซลของรูปให้เป็นค่า negative เราจึงจะใช้รหัสโปรแกรม


เมื่อ ptr คือพอยน์เตอร์ที่ชี้ไปยังแถวแรกของรูป และตำแหน่งพิกเซลใดๆในรูป สามารถคำนวณได้จาก
rc = (r*img->widthStep) + c;
เมื่อ r และ c คือแถวและหลักของรูปตามลำดับ

ที่มา:
1. http://opencv.willowgarage.com/documentation/c/reading_and_writing_images_and_video.html#cvLoadImage
2. http://www.cs.iit.edu/~agam/cs512/lect-notes/opencv-intro/index.html

โครงสร้างของรูปใน OpenCV

รูปใน OpenCV จะถูกเก็บด้วยตัวแปรโครงสร้างชื่อ IplImage ซึ่งมีรายละเีอียดดังนี้

typedef struct _IplImage
{
int nSize;
int ID;
int nChannels; //ชั้นของสี อยู่ระหว่าง 1-4 (สีเทา แดง เขียว น้ำเงิน อัลฟ่า)
int alphaChannel;
int depth; //ความลึกของบิต (bit depth) ตั้งแต่ 8-64 บิต มีชื่อเฉพาะ
char colorModel[4];
char channelSeq[4];
int dataOrder;
int origin;
int align;
int width; //ความกว้างของภาพ
int height; //ความสูงของภาพ
struct _IplROI *roi;
struct _IplImage *maskROI;
void *imageId;
struct _IplTileInfo *tileInfo;
int imageSize; //ขนาดของรูปในหน่วยไบต์ = height*widthStep
char *imageData; //พอยน์เตอร์ที่ชี้ไปยังแถวแรกของภาพ
int widthStep; //จำนวนไบต์ระหว่างสองพิกเซลที่อยู่ในคอลัมน์เดียวกัน แต่คนละแถว
int BorderMode[4];
int BorderConst[4];
char *imageDataOrigin;
}
IplImage;

รายละเอียดเพิ่มเติมสามารถหาดูได้ที่
http://opencv.willowgarage.com/documentation/c/basic_structures.html#index-714
http://www.cs.iit.edu/~agam/cs512/lect-notes/opencv-intro/index.html

ซึ่งทำให้เราจินตนาการได้ว่า การเก็บรูปใน OpenCV จะไม่ใช่เป็นแบบเมตริกซ์สองมิติธรรมดา แต่จะเป็นดังรูปต่อไปนี้ (สมมติว่าเป็นรูป grayscale)
นั่นคือ รูปใน OpenCV จะถูกเก็บในหน่วยความจำเรียงต่อๆกันไปเหมือนอาร์เรย์ 1 มิติหรือเวกเตอร์
พิกเซลที่ (0,0) ก็จะเทียบได้กับตำแหน่งที่ 0
พิกเซลที่ (0,1) ก็จะเทียบได้กับตำแหน่งที่ 1
พิกเซลที่ (1,0) ก็จะเทียบได้กับตำแหน่งที่ widthStep
พิกเซลที่ (1,1) ก็จะเทียบได้กับตำแหน่งที่ widthStep+1
พิกเซลที่ (2,1) ก็จะเทียบได้กับตำแหน่งที่ 2*widthStep
.....

พิกเซลที่ (r,c) ก็จะเทียบได้กับตำแหน่งที่ r*widthStep+c นั่นเอง

ดังนั้น หากต้องการใช้ลูปเพื่อประมวลผลพิกเซล จะสามารถใช้รหัสดังต่อไปนี้ได้


ลองดูตัวอย่างการใช้งานในบทความถัดไปได้ครับ