Wednesday, December 21, 2011

OpenCV 2.31 + QT support

ลอง compile ซอร์สโค้ด OpenCV 2.31 ให้ support QT+OpenGL ครับ เห็นอินเตอร์เฟสที่โชว์ในเว็บแล้วสวยดี

แต่ๆๆๆ ผลลัพธ์ที่ได้




มีข้อความว่า ตอนนี้ไม่สามารถใช้ OpenGL rendering กับการกำหนดสัดส่วนของหน้าต่างได้ ขนาดของรูปที่เล็กกว่า menu bar ด้านบน จึงทำได้รูปเบี้ยวไม่ได้สเกล :( ถ้ารูปกว้างกว่าแถบ menu ก็จะไม่เบี้ยว

ข้อดี
มีเมนูบาร์ สำหรับการ pan ซ้ายขวา บนล่าง ซูมเข้าออก เซฟผลลัพธ์ รวมถึงถ้าวางเมาส์บนรูป จะแสดงพิกัด x y และค่าพิกเซล

ข้อควรปรับปรุง
บักที่ทำให้รูปที่เล็กกว่าเมนูบาร์เบี้ยว

Visual C++ Express 2010 กับ OpenCV 2.31

ถึงเวลาที่ต้องมาใช้ VC++ จนได้ เพราะอยากเอาโค้ดไปเชื่อมกับ Matlab ผ่าน mex file และต้องเชื่อมกับ OpenCV ต่อไป

วิธีการติดตั้ง VC++ เพื่อให้ใช้ได้กับ OpenCV ดูตาม http://opencv.willowgarage.com/wiki/VisualC%2B%2B_VS2010_CMake ได้ครับ ถ้ามีเวลาคงมาสรุปเป็นภาษาไทยอีกครั้ง

ผลการทำพบว่า คอมไพล์ซอร์สโค้ดก็ไม่ยากครับ แต่ไม่สามารถสร้างตัวไบนารีแบบ install ได้ ผมเข้าใจว่ามี bug เล็กน้อยใน makefile ตอนกำหนดไดเรกทอรี อย่างไรก็ตามถ้าก๊อปไฟล์เองก็ใช้งานได้

ปัญหาถัดมา เมื่อทดสอบกับซอร์สโค้ด OpenCV ทำการ build ผ่านไม่มีปัญหาครับ แต่ว่าตอน Debug ไม่ผ่าน ต้องไปรันไฟล์ต่างหากเอง ไม่รู้ว่าเพราะอะไร เซิร์ชหาก็ยังไม่เจอวิธีแก้ครับ คงต้องหลับหูหลับตาผ่านไปก่อน

ข้อดีของ Visual C++ กับ OpenCV
-ตัว auto complete ของ IDE VC++ นี่ดีจริงๆ แต่อืดเหมือนกัน
-Build ง่าย เลือก Debug หรือ Release เวอร์ชันก็ง่าย ขนาดไฟล์ผลลัพธ์เล็ก
-มีคนใช้เยอะ พอเจอปัญหาน่าจะหาทางแก้ง่าย

ปัญหาที่เจอ
-ทำตามคู่มือแล้วแต่คอมไพล์แบบ install ยังไม่สมบูรณ์
-Debug ไม่ผ่าน :( สงสัยผมจะลืมขั้นตอนอะไรไป


Thursday, December 15, 2011

งงกับการ link ของ g++

ทดลองคอมไพล์ซอร์สโค้ดของคนอื่น ด้วย g++ (mingw) บนวินโดวส์

Makefile ของเจ้าของเป็นบน unix

ส่วนของการคอมไพล์ไม่มีปัญหา พอมาลิงก์ แสดงผลว่า undefined reference xxx

อะไรเนี่ย นั่งงมเปิดนั่น โหลดนี่ เซิร์ชหาคำตอบทั่วเน็ต ยอมแม้กระทั่งคอมไพล์ไลบรารีตัวอื่นที่เกี่ยวข้องใหม่หมด

สุดท้าย คำตอบอยู่ที่การเรียงลำดับคำสั่งของ g++

คอมไพล์
g++ -Ixxx -c input.cpp -o output.o 
เหมือนกันทั้ง unix และ windows (xxx คือตำแหน่งของ include files)

ลิงก์ (สมมติว่าสร้าง dynamic library)
-unix
g++ -Lyyy -lzzz -shared output.o -o libxyz.so
-windows
g++ -shared output.o -o libxyz.so -Lyyy -lzzz
(yyy คือตำแหน่งของ library file, zzz คือชื่อ library)

ต่างกันแค่เนี้ยยยย งมไปงมมาจนมึน
สุดท้ายใช้การได้ แต่เจ้าของโค้ดดันออกแบบให้ได้เฉพาะแบบของเขาซะอีก ต้องไปแก้โค้ดอีกเยอะเลย เฮ้อ
ไม่ใช้ซะดีไหมเนี่ย

Saturday, November 26, 2011

Speed Test

วันนี้ว่างๆเลยลองมาทดสอบความเร็ว (แบบบ้านๆ) กับการเข้าถึงพิกเซลของสามโปรแกรม คือ MATLAB, OpenCV (C++) และ ImageJ (JAVA) โดยใช้รูปขนาด 256x256 pixels มาทำ image negative (inverse) ด้วยโค้ดปกติของแต่ละอัน นั่นคือ
  • MATLAB ใช้ J = 255-I;
  • OpenCV และ ImageJ ใช้ลูป 1 ชั้น ประมวลผลรูปแบบเวกเตอร์
เวลาที่ใช้ (โดยประมาณจากโค้ดในการวัด กับดูที่โปรแกรมแสดง) เป็น 0.000075, 0.00062, 0.0030 วินาทีตามลำดับ

ต่อมา ทดลองเพิ่มขนาดรูปเป็น 2550x1900 พิกเซล เวลาโดยประมาณเป็น 0.006, 0.05, 0.12 วินาทีตามลำดับ

เห็นตัวเลขแล้วแปลกๆ ทำไม MATLAB ถึงได้เร็วขนาดนี้ แสดงว่าถ้าประมวลผลทีละพิกเซล MATLAB มีการ optimize โค้ดแบบเมตริกซ์ที่ดีมาก

โอกาสต่อไปจะลองทำการ convolution แล้วมาเทียบกันดูครับ

Saturday, October 1, 2011

Flip image & display

วันนี้ลองมาใช้ฟังก์ชันง่ายๆ เพื่อ flip รูปกันครับ

ผลลัพธ์ที่ต้องการ

เช่นเดียวกันครับ ถือว่ารูปอยู่ที่เดียวกับไฟล์ output
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;

int main()
{
 //Read file and load to variable
 Mat image = imread("lenag.jpg"); 
 //If read failure
 if(!image.data)
 {
  return -1;
 } 
 //Create a window to display the image 
 namedWindow("Original image");
 //Show the image
 imshow("Original image",image);

 //Create another image for result
 Mat result;
 flip(image, result, 1); //1=horizontal, 0=vertical, -1=both
 //Create a window to display the image 
 namedWindow("Output image");
 //Show the image
 imshow("Output image",result);

 //Wait for user to press any key
 waitKey(0);
 return 0;
}

สังเกตว่ามีการสร้าง 2 หน้าต่างเพื่อแสดง input และ output

มีการตรวจสอบว่าอ่านรูปได้หรือไม่ โดย if(!image.data)

และมีการกลับรูปโดยใช้คำสั่ง flip(image, result, 1);     //1=horizontal, 0=vertical, -1=both

จะเห็นว่า OpenCV2 พยายามทำให้รูปแบบการใช้งานคำสั่งต่างๆ ดูง่ายขึ้นกว่ารุ่นก่อนๆครับ

Friday, September 30, 2011

OpenCV 2.31 & MinGW (Dev-C++) Part 3

มาทดสอบ library ที่เราคอมไพล์แล้วดูครับ

ตอนนี้เราก็จะมีโฟลเดอร์ C:\OpenCV ที่เก็บ library ที่ต้องใช้ในการเขียนโปรแกรมของเรา (bin,include,lib,doc) ให้เซ็ต path ของ windows ไปที่ C:\OpenCV\bin ด้วยครับ

เปิด Dev-C++ เลยครับ
เลือกเมนู Tools / Compiler Options แล้วเพิ่มในส่วนของ linker ตามนี้ครับ
-lopencv_core231 -lopencv_highgui231 -lopencv_imgproc231 -lopencv_features2d231 -lopencv_calib3d231 บางตัวก็อาจจะไม่ได้ใช้สำหรับทุกโปรแกรม แต่ก็ไม่เป็นไรครับ มีเกินไว้ก่อนก็ได้


จากนั้นก็กำหนดแทบ Directories ต่างๆตามนี้ครับ


-Binaries
C:\mingw\bin
C:\OpenCV\bin

-Libraries
C:\mingw\lib
C:\OpenCV\lib

-C++ Includes
C:\mingw\include
C:\OpenCV\include

ตอนนี้เราก็พร้อมที่จะเขียนโปรแกรมทดสอบแล้วครับ

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


เอกสารอ้างอิง
http://opencv.itseez.com/doc/tutorials/introduction/display_image/display_image.html#display-image

OpenCV 2.31 & MinGW (Dev-C++) Part 2

เมื่อจะคอมไพล์ source code เอง ก็ต้องมีเครื่องมือให้พร้อมครับ

เดิมทีผมคิดว่าคงเป็นการยาก ไม่ทำดีกว่า แต่ได้อ่านในคู่มือ http://opencv.willowgarage.com/wiki/MinGW ก็น่าจะทำได้

โปรแกรมที่ต้องการนะครับ
  1. MinGW (gcc) ความจริงตัวนี้ก็มีใน Dev-C++ ครับ แต่ว่าเก่าแล้ว เลยไปดาวน์โหลดตัวใหม่กว่ามา มีให้เลือกหลายที่ครับ ผมใช้ http://get.qt.nokia.com/misc/MinGW-gcc440_1.zip เพราะว่าจะลองทดสอบกับ Qt ด้วยภายหลัง สมมติว่าโหลดมาแล้วและก็แตกไว้ที่ C:\mingw นะครับ ให้เซ็ต path ของ windows ไปที่ C:\mingw\bin ด้วยนะครับ
  2. OpenCV source ก็ได้จากการแตกไฟล์ที่ดาวน์โหลดมาคือ OpenCV-2.3.1-win-superpack.exe ครับ สมมติว่าอยู่ที่ C:\OpenCV231Source
  3. CMake สำหรับสร้าง makefile เพื่อคอมไพล์ครับ จะช่วยให้คนที่ไม่รู้เรื่องในการคอมไพล์เช่นผมทำงานได้ง่ายขึ้นมาก ดาวน์โหลดที่นี่ครับ http://www.cmake.org/files/v2.8/cmake-2.8.5-win32-x86.zip สมมติว่าแตกไว้ที่ C:\CMake
ขั้นตอนต่อไปก็ไปเรียกไฟล์ชื่อ cmake-gui ใน C:\CMake\bin ครับ (ใน win7 ให้ run as admin ด้วยนะครับ)

จากนั้น กำหนดค่าของสองช่องด้านบน
ช่องแรก browse ไปที่ source code ของ OpenCV เช่น C:\OpenCV231Source
ช่องสอง browse ไปที่โฟลเดอร์ใหม่ที่ต้องการเก็บผลลัพธ์ของการคอมไพล์ เช่น C:\OpenCV231

จากนั้นคลิกปุ่ม Configure ครับ เลือกตามรูป

จากนั้นก็ browse เลือกคอมไพเลอร์ครับ
แล้วก็ finish
ในขณะนี้โปรแกรมก็จะทำการตรวจสอบ เช็คค่าพารามิเตอร์ต่างๆครับ
ถ้าเสร็จแล้ว ในช่องข้อความล่างสุดแสดงคำว่า configuring done ก็เป็นอันใช้ได้ครับ จะเกิดแถบสีแดงๆตรงช่องกลางเต็มไปหมด


ในคู่มือบอกว่า ให้กำหนดค่าเพิ่มหนึ่งค่าคือ CMAKE_BUILD_TYPE  ให้เป็น Releaseตามรูปครับ

จากนั้นก็กด Configure อีกครั้ง แถบสีแดงก็จะหายไป
ให้กด Generate ถ้าเสร็จแล้ว ปิด CMake ได้

ไปที่ dos prompt แล้ว เข้าไปที่ไดเรกทอรีผลลัพธ์ เช่น C:\OpenCV231
พิมพ์ mingw32-make แล้วรอๆๆๆๆๆ นานอยู่ครับ

ความจริงถึงขั้นตอนนี้ก็เสร็จแล้วครับ แต่ถ้าอยากได้แค่ตัวหลักๆสำหรับการใช้งาน ให้ใช้ dos prompt ที่ไดเรกทอรีเดิม แล้วพิมพ์ mingw32-make install

จากนั้น จะพบว่าในไดเรกทอรีย่อย install จะมีไฟล์ทุกอย่างพร้อมใช้งานครับ ซึ่งหลักๆก็จะมีไดเรกทอรีย่อย bin, lib, include,doc ซึ่งสามารถแจกจ่ายให้คนที่ไม่ต้องการคอมไพล์เองก็ได้

สมมติว่าเราจะสำเนาทุกๆไฟล์ในไดเรกทอรี install นี้ไปไว้ยัง C:\OpenCV เพื่อใช้งานต่อไปนะครับ

เป็นอันว่าเราได้คอมไพล์และลิงก์ OpenCV สำเร็จ ได้ library ต่างๆที่เราต้องการแล้วครับ

เอกสารอ้างอิง
http://opencv.willowgarage.com/wiki/MinGW
http://www.cmake.org
http://www.mingw.org/

OpenCV 2.31 & Mingw (Dev-C++) Part 1

OpenCV เวอร์ชันล่าสุด 2.31 ออกมาได้ระยะหนึ่งแล้วครับ

พอดีว่าผมกลับมาอยากใช้ OpenCV บนวินโดวส์ในช่วงนี้ โดยเฉพาะตัวล่าสุดที่มีฟังก์ชันใหม่ๆที่น่าสนใจ

เลยไปดาวน์โหลดจาก http://sourceforge.net/projects/opencvlibrary/files/opencv-win/2.3.1/ มาครับ

ได้ไฟล์ OpenCV-2.3.1-win-superpack.exe มาครับ มีทุกอย่างเลย ทั้ง source code และ binary (ที่คอมไพล์มาแล้ว) แตกไฟล์ได้ 1 GB :(

ในส่วนของ binary มีทั้งของ x86(32 bits) x64 มีทั้งสำหรับ mingw VC9 VC10

สำหรับผมวางแผนว่าจะใช้กับ mingw (Dev-C++) ก็เลยยิ้ม เพราะมีให้ใช้

กำหนดไดเรกทอรีต่างๆเสร็จ คอมไฟล์ผ่าน ดีใจๆ

แต่ ลองรันโค้ดตามตัวอย่างครับ ไม่ผ่าน เด้งออกตลอด

คราวนี้งงเลยครับ นั่งเสิร์ชหาในเน็ตกระหน่ำ ลองกับโปรแกรมตัวอื่นๆก็ยังไม่เวิร์ค

จะเปลี่ยนไปใช้ Visual C++ ก็ยังไม่อยากใช้

หากันอยู่หนึ่่งวันเต็มๆ สุดท้ายตัดสินใจคอมไพล์ source code ใหม่เลยก็แล้วกัน

Tuesday, March 22, 2011

ImageJ 7: Pixel processing using Lookup Table

ในการประมวลผลทีละพิกเซล บางครั้งถ้าการประมวลผลซับซ้อนและมีพิกเซลจำนวนมาก ก็จะต้องเกิด computing cost + time cost ที่สูง

เพื่อลดปัญหาดังกล่าว สำหรับรูปสีเทาที่มีแค่ 256 สี นิยมใช้ Lookup Table ที่เป็นตารางเก็บค่าผลลัพธ์ของพิกเซลที่มีค่า 0-255 ไว้ จากนั้นจึงนำมาเปรียบเทียบกับรูป ซึ่งจะทำให้การประมวลผลได้เร็วขึ้น (อาจจะใช้หน่วยความจำเพิ่มขึ้นเล็กน้อย)

ตัวอย่างเช่น รูปสีเทารูปหนึ่งมีขนาด 512x512 พิกเซล แต่ละพิกเซลต้องการการประมวลผล 2 วินาที เวลาที่ใช้ทั้งหมดคือ 2^19 วินาที

ถ้าใช้ Lookup Table ขนาด 1x256 จะใช้เวลาในการประมวลผล 2^9 วินาที บวกกับการให้ค่าจากตารางอีกนิดหน่อย รวมแล้วก็จะน้อยกว่าวิธีปกติมาก

ลองสังเกตการเพิ่ม contrast ของรูป 50% และหา inverse โดยใช้วิธีปกติ และ ใช้ Lookup Table

1. วิธีปกติ
public void run(ImageProcessor ip){
 int MN = ip.getPixelCount();
 for(int p=0;p<MN;p++){
  int v = ip.get(p);
  v = (int) Math.round(1.5*v); //50% increase of contrast
  if(v>255)    //clamp max
   v = 255;
  v = 255-v;   //inverse
  ip.set(p,v);
 }
}

2. Lookup Table
public void run(ImageProcessor ip){
 int K = 256; //gray level
 int[] LUT = new int[K]; //lookup table
 for(int i=0;i<K;i++){
  int v = (int) Math.round(i*1.5);    //increase contrast 50%
  if(v>255)   //clamp max
  v = 255;
  v = 255-v;  //inverse
  LUT[i] = v;
 }

 //look for value in lookup table
 int MN = ip.getPixelCount();
 for(int p=0;p<MN;p++){
  ip.set(p,LUT[ip.get(p)]);
 }
 //ip.applyTable(LUT); is better for this loop
}

ซึ่งจากการเปรียบเทียบเมื่อทดสอบบนรูปสีเทาขนาด 256x256 พิกเซล พบว่า ใช้เวลาในการประมวลผล = 0.011 และ 0.006 วินาทีตามลำดับ

หมายเหตุ เราสามารถใช้คำสั่งเพื่อให้ค่าจาก Lookup Table โดยตรงแทนลูปที่สอง โดยใช้คำสั่ง ip.applyTable(LUT); ได้

Monday, March 21, 2011

ImageJ 6: Basic Gray-level Thresholding

การทำ Gray-level Thresholding ก็คือการเปลี่ยนจากรูปสีเทาให้เป็นรูปขาวดำ โดยอาศัยค่า Threshold เป็นตัวแบ่ง ด้วยหลักการง่ายๆ เช่น

ถ้าเลือกค่า Threshold เป็น 100 ดังนั้น ทุกพิกเซลที่มีค่าน้อยกว่าหรือเท่ากับ 100 ให้มีค่าเป็น 0 (ดำ) นอกนั้นให้มีค่าเป็น 255 (ขาว) เช่น


หลังจากทำ Thresholding

สำหรับ ImageJ มีฟังก์ชันในคลาส ImageProcessor ชื่อ void threshold(int) เพื่อทำหน้าที่นี้

ลองดูตัวอย่างครับ
import ij.IJ;
import ij.ImagePlus;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;

public class My_Threshold implements PlugInFilter
{
 public int setup(String arg, ImagePlus imp)
 {
  return DOES_8G;  //accept only 8-bit grayscale image
 }

public void run(ImageProcessor ip)
{
 int th = (int) IJ.getNumber("Threshold value (0-255)",100);
 if(th==IJ.CANCELED) //if click cancel
  return;

 if((th<0) || (th>255)){
  IJ.error("Error","0-255 only");
  return;
 }

 ip.threshold(th);
 //thresholding
 /*
 int MN = ip.getPixelCount();
 for(int p=0;p<MN;p++){
  if(ip.get(p)<=th)
  ip.set(p,0);
  else
  ip.set(p,255);
 }
 */
 }
}

ImageJ 5: Contrast and Brightness

ในหัวข้อนี้เราจะลองปรับค่า contrast และ brightness ของภาพ

contrast คือ ความแตกต่างของเฉดสี เช่น
contrast สูง คือ เฉดสีต่างกันมาก

contrast ต่ำ คือ เฉดสีใกล้เคียงกัน

brightness คือ ความมืดหรือความสว่างของภาพ เช่น
brightness สูง คือ ภาพสว่าง

brightness ต่ำ คือ ภาพมืด

นั่นคือ
การปรับ contrast คือ การคูณหรือหารค่าพิกเซล
การปรับ brightness คือ การบวกหรือลบค่าพิกเซล

ลองพิจารณารหัสโปรแกรมในการปรับค่า contrast ดังนี้
//Plugin to change contrast
import ij.IJ;
import ij.ImagePlus;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;

public class My_Contrast implements PlugInFilter
{
 public int setup(String arg, ImagePlus imp)
 {
  return DOES_8G;  //accept only 8-bit grayscale image
 }

 public void run(ImageProcessor ip)
 {
  double ad = IJ.getNumber("Adjust percent of contrast",50);  
  if(ad==IJ.CANCELED) //if click cancel
   return;

  int MN = ip.getPixelCount();
  for(int p=0;p<MN;p++)
  {
   //contrast
   int v = (int)(ip.get(p)*(1+ad/100));
   if(v>255) //clamp max value
    v = 255;
   if(v<0)  //clamp min value
    v = 0;
   ip.set(p,v);
  }
 }
}

ส่วนการปรับค่า brightness ก็สามารถทำได้ดังนี้
 public void run(ImageProcessor ip)
 {  
  double ad = IJ.getNumber("Adjust value of brigtness",50);
  if(ad==IJ.CANCELED) //if click cancel
   return;

  int MN = ip.getPixelCount();
  for(int p=0;p<MN;p++)
  {
   //brightness
   int v = (int) (ip.get(p) + ad);
   if(v>255) //clamp max value
    v = 255;
   if(v<0)  //clamp min value
    v = 0;
   ip.set(p,v);
  }
 }

Sunday, March 20, 2011

ImageJ 4: Message

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

ใน ImageJ เรามีคำสั่งสำหรับการแสดงข้อความหรือตัวแปร เช่น
IJ.log(String) ตัวอย่างเช่น
 public void run(String arg) 
 {
  // log
  IJ.log("Use log method to debug variable");
  int a = 99;
  IJ.log("a = "+a);  
 }

ซึ่งจะได้ผลลัพธ์ คือ

หากต้องการแสดงข้อความที่ status bar ก็จะสามารถใช้คำสั่ง
IJ.showStatus(String) เช่น
 public void run(String arg) 
 { 
  // show text in status bar
  IJ.showStatus("Message at status bar");
 }

ซึ่งจะได้ผลลัพธ์

 ImageJ ยังสนับสนุนการแสดงข้อความเป็น dialog box ในหลายรูปแบบ เช่น
-แสดงข้อความผิดพลาด ใช้คำสั่ง IJ.error(String Title, String ErrorMessage); เช่น
IJ.error("Error","Need user input");
จะได้ผลลัพธ์เป็น

หากต้องการให้ผู้ใช้ป้อนค่าตัวอักษร สามารถใช้คำสั่ง String IJ.getString(String Title, String DefaultString) เช่น
String name = IJ.getString("Please enter your name: ","Your name here");
ก็จะเกิดกล่องข้อความดังนี้

จากนั้นเราก็สามารถใช้ตัวแปรที่รับมาได้ เช่น ใช้แสดงในกล่องข้อความใหม่ โดยใช้คำสั่ง IJ.showMessage(String Title, String message) เช่น
IJ.showMessage("Finished",name+" is running ImageJ");
จะได้ผลลัพธ์คือ

Sunday, March 13, 2011

ImageJ 3: Histogram

ตัวอย่างนี้จะเป็นการลองสร้าง Histogram ของรูปสีเทารูปนี้ครับ


ผลลัพธ์ที่อยากได้คือ Histogram ประมาณนี้

ImageJ มีฟังก์ชันในการคำนวณ Histogram ให้คือ getHistogram(); ซึ่งจะใช้หรือเขียนฟังก์ชันเองก็ได้ตามตัวอย่างต่อไปนี้ครับ
//Plugin to compute histogram
import ij.ImagePlus;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;
import ij.process.ByteProcessor;

public class My_Histogram implements PlugInFilter 
{
 public int setup(String arg, ImagePlus im) 
 {
  return DOES_8G + NO_CHANGES;  //accept only 8-bit grayscale image
 }

 public void run(ImageProcessor ip) 
 {
  int[] H = new int[256];
  /*
  int MN = ip.getPixelCount();
  for(int p=0;p<MN;p++)
  {
   int v = ip.get(p);
   H[v] += 1;
  }
  */
  H = ip.getHistogram(); //built-in method   
 }
}

ส่วนการแสดงผล จำเป็นต้องสร้างหน้าต่างขึ้นมาใหม่ แล้ววาดจุดสีดำลงไปครับ ซึ่งโค้ดทั้งหมดต่อไปนี้ ยังอยู่ใน method run() ครับ
//find max of histogram bin
  int max=H[0];
  for(int b=1;b<256;b++)
  {
   if(max<H[b])
    max=H[b];
  }
  
  //create the image of histogram
  //prepare blank area of w=256 h=100
  ImageProcessor histip = new ByteProcessor(256,100); 
  histip.setValue(255) ; // white = 255 
  histip.fill(); // clear this image 
  //draw histogram bins    
  for(int b=0;b<256;b++)
  {
   int hh = (int)(100.0*H[b]/max); //normalized bin's height
   for(int r=0;r<hh;r++)
   {
    histip.set(b,99-r,0); //plot black points
   }
  }
  
  // display the histogram image: 
  ImagePlus histim = new ImagePlus("Histogram", histip); 
  histim.show();

ImageJ 2: Invert an image

เรายังอยู่ที่การ invert image ครับ แต่จะลองใช้เทคนิคในการเข้าถึงพิกเซลต่างๆกัน ลองดูตัวอย่างโคัดครับ ในที่นี้มีอย่างน้อย 4 วิธี ดูรายละเอียดตามที่หมายเหตุได้ครับ

//Plugin to invert a grayscale image
import ij.ImagePlus;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;
import ij.process.ByteProcessor;

// _ in the class name = plugin
public class My_Invert implements PlugInFilter 
{
 public int setup(String arg, ImagePlus im) 
 {
  return DOES_8G;  //accept only 8-bit grayscale image
 }

 public void run(ImageProcessor ip) 
 {  
  //1. use getPixel, putPixel to access each pixel
  //they check image boundary+invoke function call, slowest
  int w = ip.getWidth();
  int h = ip.getHeight();
  //for each pixel
  for(int x=0;x<w;x++)
   for(int y=0;y<h;y++)
    ip.putPixel(x,y,255-ip.getPixel(x,y));
    
  /*
  //2. use get,set which do not check boundary
  //still invoke function, faster
  int w = ip.getWidth();
  int h = ip.getHeight();
  //for each pixel
  for(int x=0;x<w;x++)
   for(int y=0;y<h;y++)
    ip.set(x,y,255-ip.get(x,y));
  */
  
  /*  
  //3. use get,set with 1d indices
  //only 1 loop, invoke function, more faster
  int MN = ip.getPixelCount(); //total pixels
  for(int p=0;p<MN;p++)
   ip.set(p, 255-ip.get(p));
  */
  
  /*
  //4. use getPixels to directly process pixel array
  //direct access, no function invocation
  //fastest but more memory need
  //check if ip is ByteProcessor (0-255)
  if(!(ip instanceof ByteProcessor))
   return;
  if(!(ip.getPixels() instanceof byte[]))
   return;
  //dump to array but by reference!!!
  byte[] pixels = (byte[]) ip.getPixels(); 
  int MN = ip.getPixelCount();
  for(int p=0;p<MN;p++)
  {
   int v = 0xFF & pixels[p]; //convert to integer
   v = 255-v;
   pixels[p] = (byte) (0xFF & v); //to byte
  }
  */      
 }
}

ตามทฤษฎีแล้วแต่ละวิธีจะให้ความเร็วต่างกัน ซึ่งจะเห็นชัดขึ้นเมื่อรูปมีขนาดใหญ่ขึ้นครับ

ImageJ 1: Invert an image

ImageJ เป็นซอฟต์แวร์ทางด้านการประมวลผลภาพที่มีประสิทธิภาพ ไม่เสียค่าใช้จ่าย และใช้งานได้ไม่ยากครับ

ในที่นี้เราจะลองศึกษาการประยุกต์ใช้งาน โดยสร้าง plugin ซึ่งเนื้อหาโดยส่วนใหญ่ในหัวข้อนี้และถัดๆไป ผมจะอ้างอิงจากเอกสารในเว็บ http://rsbweb.nih.gov/ij/ และหนังสือ Wilhelm Burger and Mark James Burge, Digital Image Processing: An Algorithmic Introduction using Java, Springer, 2008.

ซึ่งไม่ได้มีเจตนาจะละเมิดลิขสิทธิ์ใดๆทั้งสิ้น

ถ้าพร้อมแล้ว ดาวน์โหลด ImageJ ได้ที่ http://rsbweb.nih.gov/ij/ ครับ

หลังจากคลายไฟล์ที่ดาวน์โหลดมาแ้ล้ว เมื่อเรียกใช้งานก็จะเห็นหน้าตาแบบนี้ครับ

เราจะลองสร้าง plugin เพื่อเพิ่มความสามารถในการประมวลผลภาพตามที่เราต้องการ ในครั้งนี้เราจะลอง invert รูปสีเทาครับ ที่เราเรียกว่าทำ image negative

ขั้นตอนในการสร้าง PlugIn
1. สร้างโฟลเดอร์ หรือ ไฟล์จาวา ในไดเรกทอรี ImageJ/plugins ครับ ในที่นี้เราจะสร้างโฟลเดอร์ชื่อ My และสร้างไฟล์ชื่อ My_Invert.java ไว้
โปรดสังเกตว่า เราใช้เครื่องหมาย _ เพื่อแสดงว่าไฟล์นี้เป็น plugin และจะถูกโหลดเข้าไปใน ImageJ โดยอัตโนมัติ
2. ใช้ IDE ตัวไหนก็ได้แก้ไขไฟล์จาวา ชื่อคลาสต้องตรงกับชื่อไฟล์ plugin
3. ใช้ ImageJ คอมไพล์ไฟล์นั้น
4. แ้ก้ไขไฟล์ถ้ามีข้อผิดพลาด
5. ถ้าไม่มีข้อผิดพลาดก็จะใช้ plugin นั้นได้เลยทันที

ลองมาดูโค้ดของ plugin invert กันครับ
//Plugin to invert a grayscale image
import ij.ImagePlus;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;
import ij.process.ByteProcessor;

// _ in the class name = plugin
public class My_Invert implements PlugInFilter 
{
 public int setup(String arg, ImagePlus im) 
 {
  return DOES_8G;  //accept only 8-bit grayscale image
 }

 public void run(ImageProcessor ip) 
 {  
  //1. use getPixel, putPixel to access each pixel
  //they check image boundary+invoke function call, slowest
  //0.015 second
  int w = ip.getWidth();
  int h = ip.getHeight();
  //for each pixel
  for(int x=0;x<w;x++)
   for(int y=0;y<h;y++)
    ip.putPixel(x,y,255-ip.getPixel(x,y));
        
 }
}

ทดลองโหลดรูปใดๆ ขึ้นมาก่อน เช่น

จากนั้น คอมไพล์โดยใช้เมนู Plugin / Compile and Run...

ผลลัพธ์ที่ได้

จะเห็นได้ว่าเรามีการเข้าถึงและประมวลผลพิกเซลได้โดยสะดวกครับ