1. 程式人生 > >C++搭建框架,利用OpenGL、GDAL、Qt進行分塊顯示遙感影像

C++搭建框架,利用OpenGL、GDAL、Qt進行分塊顯示遙感影像

 

主要是利用C++搭建的框架,利用OpenGL、GDAL及Qt進行影像分塊顯示遙感影像,目前測試顯示600M的資源3號衛星影像,僅僅需要15秒左右的時間。

此文章不對OpenGL以及GDAL做解釋,如果對OpenGL和GDAL不熟悉,請自行查閱相應的文件。

利用OpenGL顯示影像,那麼第一步首先是要對OpenGL進行初始化。
void GeoGraphicsView::initializeGL()
{
      initializeOpenGLFunctions();
      glShadeModel(GL_SMOOTH); glClearDepth(1.0f);
      glClearColor(d_ptr->bgcolor().x, d_ptr->bgcolor().y,
      d_ptr->bgcolor().z, d_ptr->bgcolor().w);
      glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
      glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
      createShader(); getLocationId();
      QOpenGLWidget::initializeGL();
}

在initializeGL介面中d_ptr是GeoGraphicsView的一個類成員,僅僅儲存了一些類的私有成員。這也符合Qt程式設計規範。

createShader介面是用來初始化著色器。
bool GeoGraphicsView::createShader()
{

       NApplication app; QString shader = app.currentShaderVertex();
       d_ptr->program()->addShaderFromSourceCode(QOpenGLShader::Vertex, shader);
       QString log = d_ptr->program()->log(); if(false == log.isEmpty())  return false;
       shader = app.currentShaderFragment();
       d_ptr->program()->addShaderFromSourceCode(QOpenGLShader::Fragment, shader);
       log = d_ptr->program()->log(); if(false == log.isEmpty()) return false;
       d_ptr->program()->link(); log = d_ptr->program()->log(); if(false == log.isEmpty()) return false;
       d_ptr->program()->bind(); log = d_ptr->program()->log(); if(false == log.isEmpty()) return false;

       return true;
}

getLocationId介面是用來獲取在著色器中定義的變數。
void GeoGraphicsView::getLocationId()
{
     d_ptr->matrix(d_ptr->program()->uniformLocation("matrix"));
     d_ptr->sclaeid(d_ptr->program()->attributeLocation("scale"));
     d_ptr->posAttr(d_ptr->program()->attributeLocation("posAttr"));
     d_ptr->colAttr(d_ptr->program()->attributeLocation("colAttr"));
}
到此OpenGL初始化完成。

下一步就是渲染了。
void GeoGraphicsView::paintGL()
{
      modifyShaderVariable();
      renderData();
     QOpenGLWidget::paintGL();
}
在渲染介面中,首先是修改著色器中的變數值,因為我們可能對顯示的影像進行縮放或者平移操作。這些操作都是需要在著色器中對變數進行修改。
void GeoGraphicsView::modifyShaderVariable()
{
      float halfWidth = d_ptr->winwidth() / 2.0f;
      float halfHeight = d_ptr->winheight() / 2.0f;
      glViewport(0, 0, d_ptr->winwidth(), d_ptr->winheight());
      QMatrix4x4 screenProj, rotatex, rotatey, rotate, scale, move;
      screenProj.ortho(-halfWidth, halfWidth, halfHeight, -halfHeight, -80.0f, 80.0f);
      rotatex.rotate(d_ptr->rotateangle().x, 0, 1); rotatey.rotate(d_ptr->rotateangle().y, 1, 0);
      rotate = rotatey *rotatex; screenProj = screenProj * rotate;
      screenProj.scale(d_ptr->scale());
      screenProj.translate(d_ptr->movedist().x(), d_ptr->movedist().y());
      d_ptr->program()->setUniformValue(d_ptr->matrix(), screenProj);
}
這個介面中都是使用OpenGL和Qt的介面,不懂的可以自行查閱文件。

接下來就是對資料進行渲染,而渲染又可以分為兩種方式,一種是利用紋理進行貼圖,第二種就是將影像資料進行按照實際的柵格格式進行渲染顯示。顯示DEM資料的時候就是利用紋理貼圖的方式進行顯示,而遙感影像是使用第二種方式。
void GeoGraphicsView::renderData()
{
     glClearColor(d_ptr->bgcolor().x, d_ptr->bgcolor().y,
     d_ptr->bgcolor().z, d_ptr->bgcolor().w);
     glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
     for(int idx = 0; idx < d_ptr->block(); ++idx)
     {
           glEnableVertexAttribArray(d_ptr->colAttr());
           glEnableVertexAttribArray(d_ptr->posAttr());

           glBindBuffer(GL_ARRAY_BUFFER, d_ptr->colorid()[idx]);
           glVertexAttribPointer(d_ptr->colAttr(), 3, GL_FLOAT, GL_FALSE, sizeof(float3), 0);

           glBindBuffer(GL_ARRAY_BUFFER, d_ptr->dataid()[idx]);
           glVertexAttribPointer(d_ptr->posAttr(), 2, GL_FLOAT, GL_FALSE, sizeof(float2), 0);

           glDrawArrays(GL_POINTS, 0, d_ptr->verticeslen()[idx]);
           glBindBuffer(GL_ARRAY_BUFFER, 0);
           glBindBuffer(GL_ARRAY_BUFFER, 0);
           glDisableVertexAttribArray(d_ptr->posAttr());
           glDisableVertexAttribArray(d_ptr->colAttr());
     }
}

在開啟軟體的時候因為沒有資料,所以d_ptr中的block為0,這個block也就是分塊的總數。後面有介紹。到此OpenGL初始化以及渲染都完成。接下來就是組織資料,將資料傳入著色器中。

前方高能,警報!!!警報!!!

開啟影像,AdapterRaster類是對GDAL進行了封裝,沒有其他特別的。
bool GeoGraphicsView::openFile(const QString &file)
{
     if(true == file.isEmpty()) return false;
     AdapterRaster raster;
     if(false == raster.openImage(file, ReadOnly)) return false;
     if(raster->imageZSize() != 1) readImageRGB(raster);
     else readImageGray(raster);
     return true;
}
接下來就是讀取影像。。。。(包括對資料進行分塊)
void GeoGraphicsView::readImageRGB(const Object<IAdapterRaster> &raster)
{
     glClearColor(d_ptr->bgcolor().x, d_ptr->bgcolor().y,
     d_ptr->bgcolor().z, d_ptr->bgcolor().w);
     glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
     int bandMap[3] = { 3, 2, 1 };
     int w = raster.imageXSize(); d_ptr->width(w);
     int h = raster.imageYSize(); d_ptr->height(h);
     int xblock = w / 640, yblock = h / 640;
     xblock = (w + xblock + 639) / 640;
     yblock = (h + yblock + 639) / 640;
     int block = xblock * yblock;
     d_ptr->block(block);
     d_ptr->dataid() = new uint[block];
     d_ptr->colorid() = new uint[block];
     d_ptr->verticeslen() = new uint[block];
     double2 rmm, gmm, bmm;
     double rmin, rmax, gmin, gmax, bmin, bmax;
     raster.imageMinMax(bandMap[0], rmm.x, rmm.y);
     raster.imageMinMax(bandMap[1], gmm.x, gmm.y);
     raster.imageMinMax(bandMap[2], bmm.x, bmm.y);
     initializeProgressBar(S2Q("正在讀取資料..."), 0, block - 1);
     for(int yb = 0; yb < yblock; ++yb)
     {
          int ybuf = yb == yblock - 1 ? h - yb * 640 : 640;
          for(int xb = 0; xb < xblock; ++xb)
          {
               int xbuf = xb == xblock - 1 ? w - xb * 640 : 640;
               SYPImageData data; int bk = yb * xblock + xb;
               setProgressBarPosition(bk);
               data.datatype = raster.imageDataType();
               float2 *verticespos = new float2[xbuf * ybuf];
               float3 *verticescolor = new float3[xbuf * ybuf];
               data.xpos = xb == 0 ? 0 : xb * 640 - xb;
               data.ypos = yb == 0 ? 0 : yb * 640 - yb;
               data.col = xbuf; data.row = ybuf; data.band = 3;
               data.allocator();
               raster.readImage(data, bandMap, BSQ);
               createRenderData(data, xbuf, ybuf, xb, yb, verticespos, verticescolor, rmm, gmm, bmm);
               beginRenderData(bk, xbuf * ybuf, verticespos, verticescolor);
               delete verticescolor; delete verticespos;   
          }
     }
     restoreProgressBar();
}

這裡面需要解釋的應該就是createRenderData和beginRenderData兩個介面。
void GeoGraphicsView::createRenderData(const SYPImageData &data, int XBuf, int YBuf, int XBlock, int YBlock, float2 *VPos, float3 *VColor, const float2 &rmm, const float2 &gmm, const float2 &bmm)
{
     double rval, gval, bval; int bs = XBuf * YBuf;
     for(int row = 0; row < YBuf; ++row)
     {
        for(int col = 0; col < XBuf; ++col)
        {
               int index = row * XBuf + col;
               VPos[index].x = XBlock * 640 + col - d_ptr->width() / 2;
               VPos[index].y = YBlock * 640 + row - d_ptr->height() / 2;
               switch(data.datatype)
               {
                   case 1: valueFromPointer(((byte *)data.imageData()), index, bs, rval, gval, bval); break;
                   case 2: valueFromPointer(((ushort *)data.imageData()), index, bs, rval, gval, bval); break;
                   case 3: valueFromPointer(((short *)data.imageData()), index, bs, rval, gval, bval); break;
                   case 4: valueFromPointer(((ulong *)data.imageData()), index, bs, rval, gval, bval); break;
                   case 5: valueFromPointer(((long *)data.imageData()), index, bs, rval, gval, bval); break;
                   case 6: valueFromPointer(((float *)data.imageData()), index, bs, rval, gval, bval); break;
                   case 7: valueFromPointer(((double *)data.imageData()), index, bs, rval, gval, bval); break;
                   default: break;
              }
              rval = rval < rmm.x ? rmm.x : rval > rmm.y ? rmm.y : rval;
              VColor[index].x = double(rval - rmm.x) / (double)(rmm.y - rmm.x);
              gval = gval < gmm.x ? gmm.x : gval > gmm.y ? gmm.y : gval;
              VColor[index].y = double(gval - gmm.x) / (double)(gmm.y - gmm.x);
              bval = bval < bmm.x ? bmm.x : bval > bmm.y ? bmm.y : bval;
              VColor[index].z = double(bval - bmm.x) / (double)(bmm.y - bmm.x);
           }
       }
}
void GeoGraphicsView::beginRenderData(int bk, int bs, const float2 *VPos, const float3 *VColor)
{
     d_ptr->verticeslen()[bk] = bs; int len;

     glEnableVertexAttribArray(d_ptr->colAttr());
     glEnableVertexAttribArray(d_ptr->posAttr());

     glGenBuffers(1, &d_ptr->dataid()[bk]);
     glVertexAttribPointer(d_ptr->colAttr(), 3, GL_FLOAT, GL_FALSE, sizeof(float3), 0);
     glBindBuffer(GL_ARRAY_BUFFER, d_ptr->dataid()[bk]);
     len = bs * sizeof(float2);
     glBufferData(GL_ARRAY_BUFFER, len, VPos, GL_STATIC_DRAW);

     glGenBuffers(1, &d_ptr->colorid()[bk]);
     glVertexAttribPointer(d_ptr->posAttr(), 2, GL_FLOAT, GL_FALSE, sizeof(float2), 0);
     glBindBuffer(GL_ARRAY_BUFFER, d_ptr->colorid()[bk]);
     len = bs * sizeof(float3);
     glBufferData(GL_ARRAY_BUFFER, len, VColor, GL_STATIC_DRAW);

     glDrawArrays(GL_POINTS, 0, bs);
     glBindBuffer(GL_ARRAY_BUFFER, 0);
     glBindBuffer(GL_ARRAY_BUFFER, 0);
     glDisableVertexAttribArray(d_ptr->posAttr());
     glDisableVertexAttribArray(d_ptr->colAttr());
}

到此影像顯示結束。

下面是顯示效果:

放大之後的效果:

放大之後顯示可以看出是一個一個小方塊,實際上這並不是小方塊,而是我們看到的錯覺。再放大之後我們可以看到其實是一個個的點。

到此整個顯示完成,後續將優化顯示效果。

資源下載地址:https://download.csdn.net/download/yangfahe1/10781676

目前支援小資料分塊顯示,後續將繼續更新大資料顯示