1. 程式人生 > 其它 >C/C++語言讀取SEGY檔案(二)

C/C++語言讀取SEGY檔案(二)

SEGY IO (2D)

本文件將介紹SEGY的讀取與寫入過程,即SEGY檔案的複製,並且在實現過程採用取樣點×道數二維陣列的形式讀寫。

新建標頭檔案SegyDataIO2D.h與C++檔案SegyDataIO2D.cpp,以及主程式main.cpp

1 編寫標頭檔案SegyDataIO2D.h

1.1 程式描述、呼叫、宣告、定義

/**********************************************************************

 * Copyright(C) 2018,Company All Rights Reserved   (1)版權說明
   *
 * @file    : SegyDataIO2D.cpp                  (2) 檔名
   *
 * @brief   :  實現地震資料的讀、寫操作                 (3) 該檔案主要功能簡介
   *
 * @version : 1.0                                  (4) 版本資訊
   *
 * @author  : Fan XinRan                           (5) 建立作者
   *
 * @date    : 2022/2/9 星期三                       (6) 建立時間
   *
 * Others  :                                       (7) 備註、改動資訊等
   **********************************************************************/
  
//呼叫需要的C標頭檔案
#include<stdio.h>   //C Language header file  
#include<stdlib.h>
#include<string.h>
#include<math.h>

//呼叫需要的C++標頭檔案
#include<iostream>  // C++ header file
#include<vector>
#include<algorithm>

//呼叫非標準庫
#include"alloc.h"   // 用於建立多維陣列
#include"segy.h"   // 包含segy與bhed結構體,用於提取卷頭和道頭中採集、儲存的資訊

#define PI 3.141592654   //Constant Number Definition
#define EPS 0.0000001

using namespace std;   // 宣告名稱空間

1.2 宣告函式

輸入和輸出均為檔案指標。

bool copySeismicData2D(const char *filenameInput, const char *filenameOutput); //Copy seismic data from Inputfile to Outputfile

完整程式碼

/**********************************************************************

 * Copyright(C) 2018,Company All Rights Reserved   
   *
 * @file    : SegyDataIO2D.cpp                  
   *
 * @brief   :  實現地震資料的讀、寫操作                 
   *
 * @version : 1.0                                  
   *
 * @author  : Fan XinRan                           
   *
 * @date    : 2022/2/9 星期三                       
   *
 * Others  :                                       
   **********************************************************************/

//(1)呼叫需要的C標頭檔案
#include<stdio.h>   // C Language header file  
#include<stdlib.h>
#include<string.h>
#include<math.h>

//(2)呼叫需要的C++標頭檔案
#include<iostream>  // C++ header file
#include<vector>
#include<algorithm>

//(3)呼叫需要的非標準庫標頭檔案
#include"alloc.h"   // project header file 
#include"segy.h"

//(4)定義全域性常量
#define PI 3.141592654   // Constant Number Definition
#define EPS 0.0000001

//(5)宣告名稱空間
using namespace std;

//(6)宣告函式名、輸入、輸出及其型別
bool copySeismicData2D(const char *filenameInput, const char *filenameOutput); //Copy seismic data from Inputfile to Outputfile

2 編寫C++檔案SegyDataIO2D.cpp

2.1 必要的說明

/*************************************************************************************************************

 Function:       copySeismicData2D                                                 (1)函式名
 Description:    copy segy file from input data to output data                     (2)簡要描述其功能
 Input:          
                 const char *filenameInput [in]    input filename (.segy)          (3)輸入變數及輸入檔案型別
 Output:         
                 const char  *filenameOutput[out]  output filename (.segy)         (4)輸出變數及輸出檔案型別
 Return:         
             bool  true    program success
			 bool  false   program failed                                          (5)返回值及其說明
 Author:     Fan XinRan                                                            (6)建立作者
 Date  :     2022/2/9                                                              (7)建立時間
 Others:                                                                           (8)備註、改動資訊等
 
*************************************************************************************************************/

2.2 定義讀、寫函式

#include "SegyDataIO2D.h"
bool copySeismicData2D(const char *filenameInput, const char *filenameOutput){

    //實現程式碼        
    ...

}

(1) 定義待使用的結構體變數、數值型變數

bhedsegy均為定義在segy.h中的結構體(structure),分別包含了二進位制卷頭資訊與道頭資訊,使用成員訪問運算子.獲取其內容;

使用unsigned int 宣告整型變數,使用long long__int64宣告segy檔案位元組數、地震道位元組數,防止資料量超出範圍,並且儘可能初始化變數。

bhed fileheader;        // file header  卷頭
segy traceheader;       // trace header  道頭
unsigned int nt=0;        // number of sample  取樣點數
unsigned int sizefileheader=sizeof(fileheader);   // size of fileheader;
unsigned int sizetraceheader=sizeof(traceheader);  // size of traceheader;

unsigned int ncdp = 0;      // number of cdp  道數
long long  size_file = 0;   //size of input file
long long  size_trace = 0;  //size of per-trace

(2)新建指標變數

在讀、寫地震道資料這一任務中,需要用到輸入指標、輸出指標、地震道資料指標以及道頭指標,四個指標變數。與1-1 SEGY IO相比多定義了一個關於traceheader的指標變數,用於存放道頭資訊。

FILE *fpinput = NULL;   // input file pointer
FILE *fpoutput = NULL;   //output file pointer
float **dataInput = NULL;  //input data pointer		注意此處**dataInput前有兩個"*",表示的是二級指標
segy *traceheaderArray = NULL; //traceheader pointer

(3)開啟輸入、輸出檔案指標並判空

fpinput = fopen(filenameInput, "rb");  //open input file pointer 
fpoutput = fopen(filenameOutput,"wb");  //open output file pointer

if(fpinput==NULL){                                            //如果檔案指標為NULL
	printf("Cannot open %s file\n", filenameInput);           //列印“檔案開啟失敗”
	return false;                                             //結束程式
}

if(fpoutput==NULL){
	printf("Cannot open %s file\n", filenameOutput);
	return false;
}
//讀寫操作
...
    
//fopen()與fclose()成對出現,在對檔案的操作完成後切記關閉檔案
fclose(fpinput);  //close input file pointer
fclose(fpoutput); //close output file pointer

(4)讀取/計算卷、道資訊

fread(&fileheader,sizefileheader,1,fpinput); // 從輸入流(fpinput)中讀取卷頭資訊到指定地址---->fileheader

nt = fileheader.hns; //從卷頭資訊中獲取取樣點數

_fseeki64(fpinput,0,SEEK_END);   // 從檔案末尾偏移這個結構體0個長度給檔案指標fpinput,即fpinput此時指向檔案尾

size_file = _ftelli64(fpinput);  // 返回當前檔案位置,即檔案總位元組數
size_trace = nt*sizeof(float)+sizetraceheader;  // 每一道的位元組數 = 取樣點位元組數+道頭位元組數

ncdp = (size_file - (long long)sizefileheader)/size_trace; // 道數 = (檔案總位元組數 - 卷頭位元組數)/每一道的位元組數

_fseeki64(fpinput,sizefileheader,SEEK_SET); // 從檔案開頭偏移sizefileheader(卷頭位元組數)個長度給指標fpinput,即fpinput此時指向第一道的開始
fwrite(&fileheader, sizefileheader, 1, fpoutput); // 先寫入卷頭
  1. fread() 從給定流讀取資料到指標所指向的陣列中;

    fread(*ptr, size, nmemb,*stream)

    • ptr -- 指向帶有最小尺寸 size*nmemb 位元組的記憶體塊的指標;
    • size -- 要讀取的每個元素的大小,以位元組為單位。
    • nmemb -- 元素的個數,每個元素的大小為 size 位元組;
    • stream -- 指向 FILE 物件的指標。
  2. fwrite(*ptr, size, nmemb,*stream) 引數與fread()相同,把ptr所指向的陣列中的資料寫入到給定流stream中;

  3. _fseeki64的用法與fseek相同,表示從檔案指定位置偏移一定位元組數;前者具有更高的相容性;

  4. _ftelli64ftell同理,返回給定流的當前檔案位置;

    _fseeki64(*stream, offset, whence)

    • stream -- 指向 FILE 物件的指標;

    • offset -- 相對 whence 的偏移量,以位元組為單位;

    • whence -- 表示開始新增偏移 offset 的位置。一般定義為SEEK_SET(檔案開頭)、SEEK_CUR(檔案指標的當前位置)、SEEK_END(檔案末尾)三類常量。

(5)遍歷每一條地震道,讀、寫資料

dataInput=alloc2float(nt,ncdp); // 分配nt*ncdp(取樣點數×道數)所需的記憶體空間,用來存放二維地震道資料
//其中,alloc2float是解除安裝alloc.h中的函式,建立一個float型的二維陣列
//dataInput為二級指標,可簡記為dataInput指向每一行的開頭

memset(dataInput[0], 0, nt*ncdp * sizeof(float)); // 從第一行的開頭開始,將記憶體塊中nt*ncdp個字元賦值為0
// dataInput指向每行開頭,而dataInput[0]則為整個二維陣列的起始位置

// 在記憶體的動態儲存區中分配ncdp個長度為sizetraceheader的連續空間
traceheaderArray = (segy*)calloc(ncdp,sizetraceheader);

//逐道讀取道頭與地震道資料
for(int itrace = 0; itrace < ncdp; itrace++){
    fread(&traceheaderArray[itrace],sizetraceheader,1,fpinput); // &traceheaderArray[itrace]為第itrace道的地址,讀取該道頭資訊
    fread(dataInput[itrace],nt * sizeof(float),1,fpinput); // dataInput[itrace]指向列開頭,並讀取nt個取樣點的資訊
}//end for(int itrace = 0; itrace < ncdp; itrace++)

//逐道寫入道頭與地震道資料
for (int itrace = 0; itrace < ncdp; itrace++) {
    fwrite(&traceheaderArray[itrace], sizetraceheader, 1, fpoutput); // 寫入該道頭資訊
    fwrite(dataInput[itrace], nt * sizeof(float), 1, fpoutput); // 寫入nt個取樣點的資訊
}//end for(int itrace = 0; itrace < ncdp; itrace++)
 // 在每個條件語句末尾的"}"新增備註,便於尋找和區分

//在寫操作完成後釋放記憶體
free(traceheaderArray); // free traceheader pointer
free2float(dataInput);  // free data input pointer
  1. alloc2float(nt,ncdp):建立nt*ncdp個元素的二維陣列,並分配記憶體;

  2. memset(),將某一塊記憶體中的內容全部設定為指定值, 通常為新申請的記憶體做初始化工作。

    void memset(void *ptr, int c, size_t n),複製字元 c(一個無符號字元)到引數ptr所指向的字串的前 n 個字元;

    • str -- 指向要填充的記憶體塊;
    • c -- 要被設定的值;
    • n -- 要被設定為該值的字元數。

完整程式碼

/*****************************************************************************
 Function:       copySeismicData2D                                              
 Description:    copy segy file from input data to output data                   
 Input:          
                 const char *filenameInput [in]    input filename (.segy)         
 Output:         
                 const char  *filenameOutput[out]  output filename (.segy)        
 Return:         
             bool  true    program success
			 bool  false   program failed                                         
 Author:     Fan XinRan                                                         
 Date  :     2022/2/9                                                             
 Others:                                                                           
*****************************************************************************/
#include "SegyDataIO2D.h"

bool copySeismicData2D(const char *filenameInput, const char *filenameOutput){

    bhed fileheader;        // file header
	segy traceheader;       // trace header 
	unsigned int nt=0;        // number of sample
	unsigned int sizetraceheader=sizeof(traceheader);  // size of traceheader;
	unsigned int sizefileheader=sizeof(fileheader);   // size of fileheader;
	unsigned int ncdp = 0;     // number of cdp
	long long   size_file = 0;  //size of input file
	long long  size_trace = 0;  //size of per-trace

	FILE *fpinput = NULL;   // input file pointer
	FILE *fpoutput = NULL;   //output file pointer
	float *dataInput = NULL;  //input data pointer
    segy *traceheaderArray = NULL; //traceheader pointer

	fpinput = fopen(filenameInput, "rb");  //open input file pointer 
	fpoutput = fopen(filenameOutput,"wb");  //open output file pointer
	
	if(fpinput==NULL){
		printf("Cannot open %s file\n", filenameInput);
		return false;
	}
	
	if(fpoutput==NULL){
		printf("Cannot open %s file\n", filenameOutput);
		return false;
	}
	
	fread(&fileheader,sizefileheader,1,fpinput);
	
	nt = fileheader.hns;
	_fseeki64(fpinput,0,SEEK_END);
	size_file = _ftelli64(fpinput);
	size_trace = nt*sizeof(float)+sizetraceheader;
	
	ncdp = (size_file - (long long)sizefileheader)/size_trace;
	
	_fseeki64(fpinput,sizefileheader,SEEK_SET);
	fwrite(&fileheader, sizefileheader, 1, fpoutput);
	
	dataInput = alloc2float(nt,ncdp);
	memset(dataInput[0], 0, nt*ncdp * sizeof(float));
	traceheaderArray = (segy*)calloc(ncdp,sizetraceheader);
    
	for(int itrace = 0; itrace < ncdp; itrace++){

		fread(&traceheaderArray[itrace],sizetraceheader,1,fpinput);
		fread(dataInput[itrace],nt*sizeof(float),1,fpinput);
	}//end for(int itrace = 0; itrace < ncdp; itrace++)
    
	for (int itrace = 0; itrace < ncdp; itrace++) {
		fwrite(&traceheaderArray[itrace], sizetraceheader, 1, fpoutput);
		fwrite(dataInput[itrace], nt * sizeof(float), 1, fpoutput);
	}//end for(int itrace = 0; itrace < ncdp; itrace++)    
    
	free(traceheaderArray);
	free(dataInput);  // free data input pointer
    fclose(fpinput);  //close input file pointer
	fclose(fpoutput); //close output file pointer
	
	return true;

}

3 主函式main.cpp及執行結果

#include"ReadSeismic.h"

void main(){

	copySeismicData2D("Demo.segy","Output2.segy");

}

執行主函式後,程式會讀入Demo.segy,再寫入到Output2.segy,從而完成對SEGY檔案的複製。

附錄

I 二維陣列的動態記憶體分配方法

a.非連續分配

首先arr是一個二級指標,為arr分配xDim空間,每一維都是一個指向陣列的指標,且每個陣列內部的地址空間是連續的,但是陣列之間的地址空間沒有連續性。

void malloc2D_1(int **&a)  
{  
    a = new int*[xDim];  // new的作用與malloc類似,分配一個一維陣列的記憶體塊
    for(int i=0;i<xDim;i++)       
        a[i] = new int[yDim];  // 對一維陣列的每一個元素繼續分配記憶體塊
    assert(a!=NULL);  
}  
  
int main()  
{  
    int **arr = NULL;  // 宣告一個二級指標
    malloc2D_1(arr);  // 執行分配記憶體操作
}  

釋放

需要逐個陣列釋放

void free2D_1(int **a)  
{  
    for(int i=0;i<xDim;i++)           
        free(a[i]);  
}  

b.連續分配

void malloc2D_2(int **&a)   
{  
    a = (int **)malloc( xDim * sizeof(int *) );     // 分配xDim字元的記憶體用來存放一級指標
    a[0] = (int *)malloc( xDim * yDim * sizeof(int) );  // 分配xDim * yDim個元素所需的記憶體空間
    for(int i=1;i<xDim;i++)  
    {  
        a[i] = a[i-1] + yDim;   // 例如a[0]指向二維陣列起始位置,a[1]=a[0]+yDim,則指向下一行(第一行)的開頭,以此類推... 
    }  
    assert(a!=NULL);  
}  
  
int main()  
{  
    int **arr = NULL;  
    malloc2D_2(arr);  
}  

釋放

只需要釋放兩個分配記憶體的指標即可

void free2D_2(int **a)  
{  
    free(a[0]);  
    free(a);  
} 

II 兩種索引方法

假設建立一個100×100的二維陣列,並且每一行的數值等於其行號:

行號\列號 0 1 2 ... 97 98 99
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
... ... ... ...
98 98 98 98 98 98 98 98
99 99 99 99 99 99 99 99

以此為例,分別介紹兩種索引方法。

a.以一維陣列方式索引

int nrow = 100;
int ncol = 100;   // 宣告行號和列號
float *a1 = (float*)calloc(nrow*ncol,sizeof(float));// 分配nrow*ncol個元素所需的記憶體空間,將起始地址指向a1

for (int irow = 0; irow < nrow; irow++) // 遍歷行
{
	for(int icol = 0; icol < ncol; icol++){ // 遍歷列
		a1[irow*ncol + icol] = irow; // 在一維陣列上依據行、列計算索引,為指定地址賦值	
	}
}

b.以二維陣列方式索引

float **a2 = alloc2float(ncol, nrow);  // 利用alloc2float函式分配nrow*ncol個元素所需的記憶體空間,a2指向每行的起始位置
memset(a2[0],0,ncol*nrow*sizeof(float)); // 陣列初始化為0

for (int irow = 0; irow < nrow; irow++)
{
	for (int icol = 0; icol < ncol; icol++) {

		a2[irow][icol] = irow;  // 以二維陣列的方式定位行、列

		printf("%f\n", a2[irow][icol]);

	}
}