Qt/Gcc


4.画像の処理用配列への取り込み(二次元)
 画像処理を実行するため、配列を動的に確保して、QImageからコピーします。

 4-1 概要
 二次元配列を動的に確保して、QImageを配列にコピーするクラスをつくります。そして、二次元配列からQImageにデータを書き戻す機能も持たせます。

クラス名は、CDataCreate2Dとします。

4-1-1 ヘッダファイルの定義(cdatacreate2d.h)
このクラスは処理用配列と出力用(処理後)配列を作成する。

(1)メンバー変数および、関数の定義
   コンストラクタとデストラクタを定義します。今回は動的に2次元配列を作成します。
    CDataCreate2D(int hsize,int vsize);
    ~CDataCreate2D();

 以下のように2次元配列用のポインタのポインタを定義します。つまり、ポインタの配列を作って
 そのポインタそれぞれに実体を定義するというやり方です。
  hsizeとvsizeはそれぞれ、縦横の数を保持します。
  
  unsigned char **Rdata;//Red用データのポインタのポインタ
    unsigned char **Gdata;//Green用データのポインタのポインタ
    unsigned char **Bdata;//Blue用データのポインタのポインタ
    int hsize;
    int vsize;

(2)QImageのための追加
   #include <qimage.h>//

以下はQImageから作業用配列にデータをコピーします。
QImageがポインタで渡されても、実体で渡されても対応できるように
多重定義とします。

void mImage2data(QImage *linImage);//ポインタ渡し
void mImage2data(QImage linImage);//参照渡し

そして、作業用配列からQImageにコピーする関数を定義します。

 void mdata2Image(QImage *linImage);//作業用配列からQImageにコピーする

動作確認用にメンバー関数を定義します。この関数はラプラシアンフィルタを実行します。
テスト用の関数です。

    //test
    void laplacian(unsigned char **image_in,double amp, int type,int hsize,int vsize);

4-1-2 クラスの実装(cdatacreate2d.cpp)
 (1)コンストラクタとデストラクタの実装
  コンストラクタで動的に2次元配列を確保します。
  以下にコードを記載します。
  考え方は、ポインタのポインタを定義して、はじめにポインタの配列を確保します。
  はじめに水平方向の個数を確保します。そして、確保したポインタ配列のひとつひとつ
  に対して配列を確保することで二次元配列を確保しています。こんなことしなくても
  C++のVectorなどを使えばいいかもしれません。この配列は以下のような配列アクセスが可能
//確保した配列へのアクセス  
for(int loop_y=0; loop_y<vsize; loop_y++){
   for(int loop_x=0; loop_x<hsize; loop_x++){
             r = qRed(lImage->pixel(loop_x, loop_y));
             g = qGreen(lImage->pixel(loop_x, loop_y));
             b = qBlue(lImage->pixel(loop_x, loop_y));

                     Rdata[loop_x][loop_y] =(unsigned char)r;
                     Gdata[loop_x][loop_y] =(unsigned char)g;
                     Bdata[loop_x][loop_y] =(unsigned char)b;
  }
}


以下コンストラクタの実装です。
//lhsize 水平方向の画素数
//lvsize 垂直方向の画素数
CDataCreate2D::CDataCreate2D(int lhsize,int lvsize)
{
    /*****二次元配列の確保*/
    Rdata=NULL;
    hsize=lhsize;
    vsize=lvsize;

    Rdata = (unsigned char **)new unsigned char *[hsize];
    Gdata = (unsigned char **)new unsigned char *[hsize];
    Bdata = (unsigned char **)new unsigned char *[hsize];

       for(int k=0;k<hsize;k++){
        Rdata[k] = (unsigned char *)new unsigned char[vsize];
        Gdata[k] = (unsigned char *)new unsigned char[vsize];
        Bdata[k] = (unsigned char *)new unsigned char[vsize];
    }

     /*上記の場合は、以下のような配列アクセスが可能
     for(int loop_y=0; loop_y<vsize; loop_y++){
         for(int loop_x=0; loop_x<hsize; loop_x++){
             r = qRed(lImage->pixel(loop_x, loop_y));
             g = qGreen(lImage->pixel(loop_x, loop_y));
             b = qBlue(lImage->pixel(loop_x, loop_y));

                     Rdata[loop_x][loop_y] =(unsigned char)r;
                     Gdata[loop_x][loop_y] =(unsigned char)g;
                     Bdata[loop_x][loop_y] =(unsigned char)b;
         }
     }
     */
}
 確保した二次元配列は、デストラクタで廃棄します。
 ただし、配列を確保していない場合を考慮して、確保しているときのみdeleteで配列を開放します。
 以下デストラクタのコードです。
CDataCreate2D::~CDataCreate2D()
{
     /*****二次元配列の破棄*/

    if(Rdata!=NULL){
        for(int k=0;k<hsize;k++){
            delete Rdata[k];
            delete Gdata[k];
            delete Bdata[k];
        }
        delete Rdata;
        delete Gdata;
        delete Bdata;
    }
}

(2)メンバー関数の実装
 QImageから配列にデータをコピーするメンバー関数を定義します。

これは、void CDataCreate2D::mImage2data(QImage linImage)関数で実行しています。
ポイントは、qRed()等の関数を使うところです。具体的には、RGB各データを取り出すために
qRed(),qGreen(),qBlue()の各関数を使って、画素からコピーします。一度r、g、bの変数に移して
このデータを動的に確保した二次元配列にコピーしています。

void CDataCreate2D::mImage2data(QImage linImage)
{

    int r,g,b;
    for(int loop_y=0; loop_y<vsize; loop_y++){
        for(int loop_x=0; loop_x<hsize; loop_x++){
            r = qRed(linImage.pixel(loop_x, loop_y));//ピクセルからRedデータを取り出す。
            g = qGreen(linImage.pixel(loop_x, loop_y));//ピクセルからGreenデータを取り出す。
            b = qBlue(linImage.pixel(loop_x, loop_y));//ピクセルからBlueデータを取り出す。

                    Rdata[loop_x][loop_y] =(unsigned char)r;
                    Gdata[loop_x][loop_y] =(unsigned char)g;
                    Bdata[loop_x][loop_y] =(unsigned char)b;
        }
    }

}

次は配列からQImageにコピーします。以下のメンバー関数を定義します。
void CDataCreate2D::mdata2Image(QImage *linImage)
今回はテストとして、この関数内でラプラシアンフィルタを実行します。

 laplacian(Rdata,(double)5.0,1,hsize,vsize);//test

ラプラシアンフィルタを実行した後は、処理した配列をQImageにコピーします。
この関数内で使用するQImageは読み込んだデータのQImageを使っているので、
元の画像情報がそのまま残っています。そこで、グレースケール画像かどうかを判断して
グレースケールの場合は、グレースケール画像としてQImageに書き戻します。

 //Gray scale? or color
            if(linImage->allGray()){ //lImage->isGrayscale()
               linImage->setPixel(loop_x, loop_y,qGray(r,g,b));//グレースケールの場合
            }
            else{
                    linImage->setPixel(loop_x, loop_y,qRgb(r,g,b));//カラー画像の場合
            }

以下ソースコードです。
void CDataCreate2D::mdata2Image(QImage *linImage)
{
    laplacian(Rdata,(double)5.0,1,hsize,vsize);//test

    unsigned int r,g,b;
    int loop_y;
    int loop_x;
    for(loop_y=0; loop_y<vsize; loop_y++){
        for(loop_x=0; loop_x<hsize; loop_x++){
            r=Rdata[loop_x][loop_y];
            g=Gdata[loop_x][loop_y];
            b=Bdata[loop_x][loop_y];


            //Gray scale? or color
             if(linImage->allGray()){ //lImage->isGrayscale()
                  linImage->setPixel(loop_x, loop_y,qGray(r,g,b));//グレースケールの場合
              }
              else{
                  linImage->setPixel(loop_x, loop_y,qRgb(r,g,b));//カラー画像の場合
              }

         }

    }
}

なお、ここで使ったlaplacian()関数は「C言語で学ぶ実践画像処理(オーム社)」
に記載のコードを使いました。

4-2 クラスの呼び出し
上記クラスを確保するためにmainwindow.hに以下のように記載する。

#include "cdatacreate2d.h"//二次元配列を動的に確保するクラス。例:data[hsize][vsize]

publicとして以下の変数を宣言する。
    CDataCreate2D *m_dcrt2d;//CDataCreate2Dで二次元配列を確保するクラスを定義する。

mainwindow.cppvoid MainWindow::newFile()内に
以下、動作テストのためのコードを記載する。
imageデータからRGB配列への変換は、 CDataCreate2Dクラスを生成している。このクラスは内部でdata[][]という2次元配列を作っている。

  if(flag==1){
            delete m_dcrt2d;//一度配列用のデータクラスを確保していれば、廃棄する。
     }

以下がCDataCreate2Dクラスを生成します。
     //////test
     m_dcrt2d=new CDataCreate2D((int)inImage.width(),(int)inImage.height());//add20120421処理用配列の確保のためのクラス作成

そして、イメージデータを配列にコピーするために、CDataCreate2Dクラスのメンバー関数を呼び出します。

     m_dcrt2d->mImage2data(&inImage);

コピーした配列が処理した後にイメージデータに書き込まれているか確認するために、内部でラプラシアンフィルタを実行して、配列からイメージデータに書き戻すメンバー関数を呼び出します。
あくまでもテストです。

     m_dcrt2d->mdata2Image(&inImage);

読み込んで、画像処理をした画像をPixMapにしています。
 pixmap_in.convertFromImage(inImage);//読み込んだイメージデータをピックスマップに変換する。

後の処理は画像を表示する処理と同じです。
この結果を以下に示します。今回は原画の赤信号のみラプラシアンフィルタを実行しました。


cdatacreate2d.h
cdatacreate2d.cpp
mainwindow.h
mainwindow.cpp
ソースコード


トップページに戻る