【題外話】html
最近實驗室要我修改C3D(The 3D Biomechanics Data Standard)文件,雖然從網上找到了一個叫c3d4sharp的類庫,這個類庫單純讀取C3D文件的話還能夠,可是若是要實現修改或者建立C3D文件就比較麻煩了。同時c3d4sharp實現得比較簡單,不少C3D文件裏有的數據都不支持。好在C3D文件整體不是很複雜,因而我就從頭從新寫了一個C3D文件讀寫的庫,如今在codeplex上建立了個項目叫C3D.NET。數組
【文章索引】google
首先說C3D文件總體不是很複雜,也沒有不少複雜的概念,C3D的文檔格式能夠從其官網下載或在線閱讀。首先C3D文件是以Section爲單位存儲的,每個Section固定爲512字節。Section必定是按順序存儲的,不過有意思的是,Section的序號是從1開始的,而不是0。C3D文件分爲三部分,分別是Section ID = 1的C3D文件頭(固定爲一個Section,512字節),Section ID通常等於2(在文件頭內會給出)的C3D參數集合以及Section ID不知道等於幾(在文件頭和參數集合中都會給出)的C3D數據部分。3d
不過C3D也有很複雜的地方,一個是關於整型的使用,可使用使用有符號的(Int16),也可使用無符號的(UInt16),只不事後者能存儲的數據量要多一些罷了,既然這樣,不知爲什麼當初還要採用有符號的整型。並且最關鍵的是,文檔內沒有任何標識能指出文檔使用的是何種整型。官方給出的解決方法是,能夠根據例如幀總數、幀索引等判斷,若是讀出負數,則採用無符號的,不然採用有符號的。code
另外一個是C3D文件能在不一樣類型的CPU上生成,這表現於不一樣CPU可能採用的字節序(Endian)和浮點數字不一樣,好比你們用的CPU都是採用Little-Endian以及IEEE754的浮點數標準。從網上查還發現有DEC (VAX)以及IBM等CPU採用不一樣的浮點數標準,詳見我以前一篇文章:http://www.cnblogs.com/mayswind/p/3365594.html。而C3D則是支持3類CPU,Intel CPU採用Little-Endian以及IEEE754標準的浮點數,DEC (VAX)採用的Little-Endian以及特有的浮點數,MIPS (SGI)採用的Big-Endian以及IEEE754標準的浮點數,因此在讀取文檔的時候可能須要額外進行處理,在第三節會詳細說明。htm
首先來講第一部分,也就是C3D的文件頭,C3D的文件頭必定只佔1個Section,即固定的512字節,因此只要讀取前512字節就能夠把整個頭數據獲取到了。雖然每一個Section有512字節之多,可是對於C3D的文件頭只佔了不多的一部分,在文件頭中有大量空白的區域。其中第一部分是文件頭參數部分,內容以下:索引
字節 | 類型 | 說明 |
00H | Byte | 參數集合開始的Section ID(一般爲0x02,但也不必定) |
01H | Byte | 文件標識(固定爲0x50) |
02H-03H | Int16 | 每幀裏3D座標點的個數 |
04H-05H | Int16 | 每幀裏模擬測量的個數 |
06H-07H | Int16 | 第1幀的序號(最小爲1) |
08H-09H | Int16 | 最後1幀的序號 |
0AH-0BH | Int16 | 最大插值差距 |
0CH-0FH | Single | 比例因子(正數表示存儲的幀爲整數幀,負數爲浮點幀) |
10H-11H | Int16 | 數據區域開始的Section ID |
12H-13H | Int16 | 每幀模擬採樣個數 |
14H-17H | Single | 幀率 |
在此以後的第二部分,也就是存儲的事件,聽上去應該佔不少字節,可是因爲限制了事件數量最多不能超過18個,同時事件名稱最長爲4字節,因此事件部分也只佔不多的空間。因爲C3D主要是爲了記錄運動的數據,可能在其中有不少比較關鍵的地方,事件就是用於標記出這些地方的。一個事件包括三個內容,分別是最長四字節的事件名稱、一字節的事件是否應該顯示的狀態以及一個四字節的單精度浮點數表示事件出現的時間。事件
字節 | 類型 | 說明 |
12AH-12BH | Int16 | 事件名是否支持4字節(支持爲0x3039,不支持爲0) |
12CH-12DH | Int16 | 事件數量(最大爲18) |
130H-176H | Single[] | 按事件順序存儲的每一個事件發生的時間(第1個幀爲0.0s) |
178H-188H | Byte[] | 按事件順序存儲的每一個事件是否應該顯示(1爲顯示,0爲不顯示) |
18CH-1D2H | Char[] | 按事件順序存儲的每一個事件的名稱(每一個事件佔4字節) |
C3D文件存儲了大量的參數,其採用了相似目錄的方式存儲了參數,不過還好只有一級。即參數部分只有參數組和參數,而且每一個參數組裏只能有參數不能再包含參數組,每一個參數必須在一個參數組內。參數集合起始於文件頭中的第一個字節表示的Section ID,一般爲2,可是也不必定,有的文件會在文件頭後留出空白,而後參數集合起始的Section ID就順延了。因此判斷是否爲C3D文件千萬不要一開始讀進來個Int16而後判斷是否是0x5002,而必定要判斷第二個字節是否是0x50,肯定參數集合的位置也必定要根據文件的第一字節來。
而對於參數集合,開頭的4字節定義以下:
字節 | 類型 | 說明 |
00H | Byte | 第一個參數所在的Section在整個參數集合中的位置(一般爲0x01,說明開頭4字節以後就是第一個參數) |
01H | Byte | 參數集合部分標識(固定爲0x50) |
02H | Byte | 參數集合所佔Section數量 |
03H | Byte | 生成文件的CPU類型(0x54爲Intel,0x55爲DEC (VAX, PDP-11),0x56爲MIPS (SGI/MIPS)) |
其中前2個字節官方說直接忽略就行,可是爲了兼容在寫入的時候仍是要寫進去的。第3字節其實咱們按順序讀到頭也不須要這個數量。這其中重要的是CPU類型,因爲不一樣CPU類型採用的字節序以及存儲的浮點數字有所不一樣,因此咱們還須要根據CPU類型進行相應的處理。
對於Intel和DEC生成的文檔,都是採用Little-Endian字節序存儲的文檔,因此必定要使用Little-Endian來讀取Int1六、Single等類型;而MIPS則採用的Big-Endian字節序存儲文檔,因此在讀取的時候必定要判斷當前計算機默認的字節序以及文檔採用的字節序。
而對於Intel和MIPS生成的文檔,對於浮點數字的存儲都是採用標準的IEEE754浮點數字,對於.NET而言不須要進行任何處理;而DEC生成的文檔則採用特有浮點數,須要將4個字節所有讀取之後再進行特殊的轉換,轉換方法見我以前的文章:http://www.cnblogs.com/mayswind/p/3365594.html。
在此之下就存儲着全部的參數了,參數分爲兩類,分別是參數組和參數。
對於參數組,要存儲如下6個內容:
字節 | 類型 | 說明 |
00H | SByte | 參數組名稱長度(若是爲負數則表示該參數組鎖定請不要修改,而長度爲絕對值) |
01H | SByte | 參數組ID的負數 |
02H - ... | Char[] | 參數組名稱(僅包含大寫字母、0-9以及下劃線_) |
... + 1 - ... + 2 | Int16 | 下一參數組/參數的偏移(包含本內容的2字節) |
... + 3 |
Byte | 參數組描述長度 |
... + 4 - | Char[] | 參數組描述內容(ASCII碼) |
C3D文件沒有規定一個參數組後邊跟另外一個參數組仍是跟該參數組裏的全部參數,因此讀取的時候要注意下。而參數的內容則與參數組基本同樣,只是在下一參數組/參數的偏移與參數組描述長度之間存放着該參數的實際數據罷了,因爲位置描述起來太麻煩了,這裏就不寫了。
字節 | 類型 | 說明 |
以前的內容 | ||
Int16 | 下一參數組/參數的偏移(包含本內容的2字節) | |
Byte | 參數存放內容的類型(-1 Char,1 Byte,2 Int16,4 Single),絕對值即爲長度 | |
Byte | 參數內容維數(0-3) | |
Byte[] | 參數每一維大小(若是維數爲0,就沒有此部分) | |
Byte[] | 參數實際內容 | |
Byte | 參數組描述長度 | |
以後的內容 |
這裏須要說明的就是,因爲參數能夠存放數組,因此增長了維數的標識,即當維數爲0時,存放的內容爲Char、Byte、Int1六、Single等轉換出的字節數組;而當維數爲1時,存放的爲Char[]、Byte[]、Int16[]、Single[]等轉換出的字節數組,以此類推。而對數組的存儲,其實就是數組每一個元素依次進行存儲,而對於多維數組,則是按行優先進行存儲的,好比三維數組,先存儲Data[0,0,1]再存儲Data[0,0,2],依次類推。
不過須要說明的是,對於Char[]以及Char[,]這兩種,若是表示的話其實應該對應的是String以及String[]。
C3D數據區域以幀爲單位存放的,其實至關於這個區域就是一個幀的集合。而C3D幀其實分爲兩種,一種是整數幀,而另外一種是浮點幀。這二者的區別在於,前者存儲的全部內容都是Int16,然後者則爲Single,除此以外,前者的3D座標點(X、Y、Z)還須要乘以比例因子才能夠,然後者存儲的內容至關於已經乘以了比例因子了。
數據區域起始於參數集合中的"POINT"參數組中的"DATA_START"參數,其表示數據區域起始的Section ID,除此以外,在文件頭中也有一份副本。不過按照官方的說法,若是文件頭和參數集合中都有的內容,優先讀取參數集合中的數據。
對於每一個幀,又包含兩個部分,第一部分爲3D座標點部分,第二部分爲模擬採樣部分。
real world value = (data value - zero offset) * channel scale * general scale
前頭說了這麼多,其實若是用C3D.NET來解析的話實際上是很是簡單的。你們能夠從https://c3d.codeplex.com/下載C3D.NET的二進制文件或者源碼,引用後主要的類都在C3D這個命名空間下。
對於遍歷全部的3D座標能夠採用如下的方式,首先能夠從文件或者從流中建立C3D文件,而後從文件頭中讀取存儲的第1幀的序號,而後讀取採樣點的數量就能夠了,固然也能夠不從參數組中讀取,直接使用file.AllFrames[i].Point3Ds.Length也能夠:
1 C3DFile file = C3DFile.LoadFromFile("文件路徑"); 2 Int16 firstFrameIndex = file.Header.FirstFrameIndex; 3 Int16 pointCount = file.Parameters["POINT:USED"].GetData<Int16>(); 4 5 for (Int16 i = 0; i < file.AllFrames.Count; i++) 6 { 7 for (Int16 j = 0; j < pointCount; j++) 8 { 9 Console.WriteLine("Frame {0} : X = {1}, Y = {2}, Z = {3}", 10 firstFrameIndex + i, 11 file.AllFrames[i].Point3Ds[j].X, 12 file.AllFrames[i].Point3Ds[j].Y , 13 file.AllFrames[i].Point3Ds[j].Z); 14 } 15 }
而讀取模擬採樣的話,採用的方法也相似:
1 Single frameRate = file.Parameters["POINT", "RATE"].GetData<Single>(); 2 Int16 analogChannelCount = file.Parameters["ANALOG", "USED"].GetData<Int16>(); 3 Int16 analogSamplesPerFrame = (Int16)(file.Parameters["ANALOG", "RATE"].GetData<Int16>() / frameRate); 4 5 for (Int16 i = 0; i < file.AllFrames.Count; i++) 6 { 7 for (Int16 j = 0; j < analogChannelCount; j++) 8 { 9 for (Int16 k = 0; k < analogSamplesPerFrame; k++) 10 { 11 Console.WriteLine("Frame {0}, Sample {1} : {2}", 12 firstFrameIndex + i, j + 1, 13 file.AllFrames[i].AnalogSamples[j][k]); 14 } 15 } 16 }
除了一次性將C3D文件內容所有讀取出來的這種方式之外,還可使用C3DReader來一幀一幀的讀取。
1 using (FileStream fs = new FileStream("文件路徑", FileMode.Open, FileAccess.Read)) 2 { 3 C3DReader reader = new C3DReader(fs); 4 C3DHeader header = reader.ReadHeader(); 5 C3DParameterDictionary dictionary = reader.ReadParameters(); 6 Int32 index = header.FirstFrameIndex; 7 8 while (true) 9 { 10 C3DFrame frame = reader.ReadNextFrame(dictionary); 11 12 if (frame == null) 13 { 14 break; 15 } 16 17 for (Int16 j = 0; j < frame.Point3Ds.Length; j++) 18 { 19 Console.WriteLine("Frame {0} : X = {1}, Y = {2}, Z = {3}", 20 index++, 21 frame.Point3Ds[j].X, 22 frame.Point3Ds[j].Y, 23 frame.Point3Ds[j].Z); 24 } 25 } 26 }
對於建立一個C3D文件,只須要使用C3DFile.Create()就能夠建立一個空的C3D文件的,不包含任何的參數集合。而保存C3D文件則直接使用file.SaveTo("文件路徑")就能夠了。
對於添加參數集合可使用如下的代碼:
1 //首先須要添加參數集合,ID爲正數 2 file.Parameters.AddGroup(1, "POINT", ""); 3 //而後往指定ID的參數集合中添加參數便可 4 file.Parameters[1].Add("USED", "").SetData<Int16>(5);
添加幀可使用以下的代碼:
1 file.AllFrames.Add(new C3DFrame(new C3DPoint3DData[] { 2 new C3DPoint3DData() { X = x, Y = y, Z = z, Residual = residual, CameraMask = cameraMask}, 3 new C3DPoint3DData() { X = x, Y = y, Z = z, Residual = residual, CameraMask = cameraMask}, 4 new C3DPoint3DData() { X = x, Y = y, Z = z, Residual = residual, CameraMask = cameraMask}, 5 new C3DPoint3DData() { X = x, Y = y, Z = z, Residual = residual, CameraMask = cameraMask}, 6 new C3DPoint3DData() { X = x, Y = y, Z = z, Residual = residual, CameraMask = cameraMask} }));
固然,也能夠將C3DPoint3DData數組換成C3DAnalogSamples數組,或者二者同時添加也能夠。
【相關連接】