2016年8月15日 星期一

暑假作業2:讀取PCD檔案並轉換成RGB影像

讀取PCD檔案其實就是讀取文字檔而已,重點是PCD檔案中的內容,PCD檔案的全名是point cloud data,顧名思義就是點雲資料,所以通常會儲存一個立體空間中的資料,那麼理所當然的每一個點應該都會是有座標(x,y,z)以及顏色一共四個欄位(x,y,z,color)
打開PCD檔案如同其他影像檔案一樣,前面幾行就是檔案格式


VERSION - PCD檔案使用的版本
FIELDS - 表示出每一欄的data資訊的意義
Examples:
FIELDS x y z                                                                          # XYZ data
FIELDS x y z rgb                                                                   # XYZ + colors
FIELDS x y z normal_x normal_y normal_z                         # XYZ + surface normals
FIELDS j1 j2 j3                                                                       # moment invariants
SIZE - 每個data空間占多少,如果是4,4,4,4代表每個占4byte也就是32bit
Examples:
  • unsigned char/char has 1 byte
  • unsigned short/short has 2 bytes
  • unsigned int/int/float has 4 bytes
  • double has 8 bytes
TYPE - 雖然前面已經知道佔有空間大小了但這裡還是要指名data的類型
  • I - represents signed types int8 (char), int16 (short), and int32 (int)
  • U - represents unsigned types uint8 (unsigned char), uint16 (unsigned short), uint32 (unsigned int)
  • F - represents float types
COUNT - 表示出每個data中有多少個元素,通常是多dimemsion的才會超過1,所以通常會是1,1,1,1

剩下的就是pixel中的資訊了,而所有pixel的內容會在DATA ascii之後一口氣列出,這裡就不用多加贅述了。

不過呢,這次作業說是pcd檔案,但發現其實只是一般的影像的檔案,所以當中的xyz座標是沒有意義的,而之後的DATA ascii就是全部的pixel,我們要先把他的寬高讀好,然後照個寬高一個一個pixel讀進來之後,就可以照一般的影像檔案來做處理了,PCD檔案的部分在平面影像中是沒有用到甚麼的。
但是值得注意的是每個pixel中的xyz座標雖然都是0,但是第四欄中的color不是,他就是我們這次要處理的檔案內容--顏色
example:平面影像data在PCD中的格式
# .PCD v.7 - Point Cloud Data file format
VERSION .7
FIELDS x y z rgb
SIZE 4 4 4 4
TYPE F F F F
COUNT 1 1 1 1
WIDTH 480
HEIGHT 480
VIEWPOINT 0 0 0 1 0 0 0
POINTS 230400
DATA ascii
0 0 0 3.315087e-039
0 0 0 2.576083e-039
0 0 0 3.310419e-039
0 0 0 4.416045e-039
0 0 0 3.769231e-039
0 0 0 4.601135e-039
0 0 0 4.695478e-039
0 0 0 3.131744e-039
0 0 0 2.394906e-039
0 0 0 2.213030e-039
0 0 0 1.568737e-039

1.開檔並且讀取
C++的開檔讀取有很多方法,這次是要一行一行讀取所以我們用最簡單的方法

string line;
string filename = "pcd\\Fig1.pcd"; 
ifstream infile(filename);
if (infile.is_open())
{ 
   while (getline(infile, line))
  {
    //line 
  }
}
else cout << "Unable to open file";
 
我們可以在迴圈中直接處理line,也可以使用

string first, second, third, forth;
infile >> first >> second >> third >> forth;

 

這樣就可以以空白間隔的方式去分開讀取文字了,只是要特別注意,如果infile這個檔案pointer他指到的內容不足,他會放入NULL,不用還好,如果用了可以能發生exception,所以要特別確認一下檔案每一行中以空白隔開的文字是否剛好有4組,否則就不要用這個方法以免發生exception

2.讀取color,把float格式改成RGB格式
我們知道RGB格式才是一般影像檔案常用的顏色格式,只是在這裡變成了float,其實是因為他經過了轉換
由於浮點數是 32 bit 的格式,而 RGB 分別都是 8 bit 的數值,所以轉換的過程為
整數 I (32bit) 0x00000000
放入 R 0x000000RR
放入 G 0x0000RRGG
放入 B 0x00RRGGBB
轉浮點數
所以我們在這裡要轉回去RGB並且放在Mat中方便我們之後取用處理

最簡單的方法是用union

union FloatToChar {
 float f;
 unsigned char  c[4];
};
FloatToChar x;
x.f = (float)atof(forth.c_str());
printf("%X %X %X %X\n\n", x.c[0], x.c[1], x.c[2], x.c[3]);
以8.308537e-040為例
印出的結果會是
15 C 9 0
正常的flaot格式如下
8.308537e-04000000000 00001001 00001100 00010101
用16進位表示
0x00090C15
union之後的結果可以看到array中
第0位CHAR是0x15,第1位CHAR是0x0C,第2位CHAR是0x09,第3位CHAR是0x00
可以發現array是從後面抓8個bit開始往前走的,原因很簡單就是記憶體式堆疊上去的所以是往前,所以這個順序告訴我們很重要的資訊就是
R = 0x09,G = 0x0C,B = 0x15

3.創建一個Mat作為影像容器

知道怎麼讀取RGB之後接下來我們的任務就變得很簡單了,
因為我們之前讀取檔案應該會讀取到寬跟高所以我們要記得存取下來

int width = 0, height = 0;
Mat mat;
 ....
 ....
string first, second, third, forth;
infile >> first >> second >> third >> forth;
 ....
 ....

然後創建一個三通道(RGB)各是8bit(unsigned char)大小的Mat

if (width > 0 && height > 0)
{
 mat.create(height, width,CV_8UC3);
}

接下來就會用到Mat對於元素的存取方式
如果是CV_32F1格式的Mat,可以這樣存取:

mat.at<float>(3*j+i) = (float)(i+j+2.0);

括弧中可以用二維的(i,j)也可以用一維的(3*i+j)
那多個通道呢?例如CV_32F3
直覺使用中括號

mat.at<float>(3*j+i)[0] = (float)(i+j+2.0);
mat.at<float>(3*j+i)[1] = (float)(i+j+2.0);
mat.at<float>(3*j+i)[2] = (float)(i+j+2.0);

但是這樣在執行的時候會有error,告訴我們「float[int]」 做為陣列下標類型無效
也就是我們不能用中括號去存取flaot當中的元素。
但沒關係,我們在<>不能用flaot我們可以改用Vec3f,如此一來就成功了
其他類型的多通道如下
If the image type is 3-channel unsigned char (CV_8UC3),use cv::Vec3b

  • If the image type is 3-channel float (CV_32FC3), then replace cv::Vec3b with cv::Vec3f
  • If the image type is 3-channel double (CV_64FC3), then replace cv::Vec3b with cv::Vec3d
  • If the image type is 3-channel int (CV_32SC3), then replace cv::Vec3b with cv::Vec3i
  • If the image type is 3-channel short int (CV_16SC3) or 16-bit uchar (CV_16UC3), then replace cv::Vec3b with cv::Vec3s
到這裡基本讀取PCD轉RGB的過程大致上就完成了。
指示各位要在特別注意一件事Mat中存放的通道並不是RGB喔,而是GRB,所以要按照這個順序填入才對。
以下是完整的code:
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <fstream>
#include <cstring>
using namespace std;
using namespace cv;
//using namespace Mat;

string filename = "pcd\\Fig4.pcd";
int width = 0, height = 0;
Mat mat;

union FloatToChar {
 float f;
 unsigned char  c[4];
};

int main(int argc, char** argv)

{
 //read file
 
 string line;
 ifstream infile(filename);
 bool startReadPix = false;
 int count = 0;
 if (infile.is_open())
 {
  cout << "now read PCD file" << endl;
  string first, second, third, forth;
  while (getline(infile, line))
  {
   //line 
   
   infile >> first >> second;
   if (startReadPix)
   {
    infile >> third >> forth;
    //cout << forth << endl;
    if (count < width*height)
    {
     
     FloatToChar x;
     x.f = (float)atof(forth.c_str());
     //printf("%d %d %d %d\n\n", x.c[0], x.c[1], x.c[2], x.c[3]);
     mat.at<Vec3b>(count)[0] = x.c[0];
     mat.at<Vec3b>(count)[1] = x.c[1];
     mat.at<Vec3b>(count)[2] = x.c[2];
    }
    count++;
   }
   else{
    
    if (first == "WIDTH")
    {

     width = atoi(second.c_str());
     cout <<  "image width:"<<width << endl;
    }

    if (first == "HEIGHT")
    {
     height = atoi(second.c_str());
     cout << "image height:" << height << endl;
    }

    if (width > 0 && height > 0)
    {
     mat.create(height, width,CV_8UC3);
    }


    if (first == "DATA" && second == "ascii")
    {
     startReadPix = true;
     cout << "pixel processing now....... please wait for second" << endl;
    }
   
   }
   
  }
  infile.close();
  
  IplImage img = IplImage(mat);
  namedWindow("Display", CV_WINDOW_NORMAL);
  imshow("Display", mat);
  cout <<"processing success,pixel count:" << count << endl;
  
  waitKey(0);
  cin >> line;
 }
 else 
  cout << "Unable to open file";
 
 return 0;
}


參考資料:stack overflow,google,各個好心人的blog文章







沒有留言:

張貼留言