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.cppのvoid
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