前回紹介したアフィン変換を使えば、2D画像だけでなく3D画像の拡大・縮小・回転・移動をも行うことができます。こういった三次元の座標変換は、3Dゲームや、医用断層画像の分野ではなくてはならない技術です。ここでは、前回紹介した「アフィン変換と補間で画像を拡大縮小回転移動」のC#プログラムを3次元用に改良したものを紹介します。
画像の座標変換で、2次元と3次元で考え方に大きく変わるところはほとんどありませんが、計算量が大幅に上昇します。(次数が1個増えるので当然ですが・・・)
プログラムの面からみると、線形補間のところを大きく変える必要があります。
2次元画像のときは、下のように3つの式で補間をしていましたが、

2次元画像の例

3次元画像で考えると、線形補間に7つの式が必要になります。

3次元画像の例

○実行例
下図は、3次元画像を立体的に表示した画像です。
アフィン変換(X軸を45度回転)と線形補間により左の図が右の図のようになります。

赤線のところで輪切りにした画像で見ると次のようなになります。
通常

45度回転

こちらがC#プログラムです。
アフィン変換のクラスは省略していますが、下のほうのリンクからダウンロードできます。
// C#言語による3D画像を拡大縮小回転移動
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace Image3DGeometryTransform
{
class Program
{
static void Main(string[] args)
{
int imageNum = 256;
// ファイル名格納
List<string> fileNameList = new List<string>();
for (int i = 0; i < imageNum; i++)
{
fileNameList.Add(
@"D:\ellipse_data\細い円\ellipse_" + i + ".bmp");
Console.WriteLine("outellipse_" + i + ".bmp : loaded");
}
// 画像読み込み
byte[,,] data3d = LoadByteImage3D(fileNameList);
// 画像サイズ取得
int width = data3d.GetLength(0);
int height = data3d.GetLength(1);
int depth = data3d.GetLength(2);
// アフィン変換インスタンス
Affine affine = new Affine();
// 座標変換
byte[,,] outdata = new byte[width, height, depth];
for (int i = 0; i < depth; i++)
{
for (int j = 0; j < height; j++)
{
for (int k = 0; k < width; k++)
{
double cx;
double cy;
double cz;
// 3次元画像中心のX軸を10度回転
// 平行移動
affine.SetMatrixTranslate(
-width / 2, -height / 2, -depth / 2);
affine.ConvertCoordinate(
k, j, i, out cx, out cy, out cz);
// 45度回転
affine.SetMatrixXRotate(45);
affine.ConvertCoordinate(
cx, cy, cz, out cx, out cy, out cz);
// 平行移動
affine.SetMatrixTranslate(
width / 2, height / 2, depth / 2);
affine.ConvertCoordinate(
cx, cy, cz, out cx, out cy, out cz);
// 変換した座標の線形補間からピクセル値を求める
outdata[k, j, i] =
Interpolation3D(data3d, cx, cy, cz);
}
}
}
// ファイル保存
List<string> saveFileNameList = new List<string>();
for (int i = 0; i < imageNum; i++)
{
saveFileNameList.Add("outellipse_" + i + ".bmp");
Console.WriteLine("outellipse_" + i + ".bmp : write");
}
SaveBitmap3D(
saveFileNameList, outdata, width, height, imageNum);
}
/// <summary>
/// Bitmap複数枚ロードしbyte[,,]配列で返す
/// </summary>
static byte[,,] LoadByteImage3D(List<string> fileNameList)
{
// 画像サイズ取得
Bitmap bitmap = new Bitmap(fileNameList[0]);
int width = bitmap.Width;
int height = bitmap.Height;
byte[, ,] data3d = new byte[width, height, fileNameList.Count];
// 画像読み込み
for (int i = 0; i < fileNameList.Count; i++)
{
bitmap = new Bitmap(fileNameList[i]);
// bitmapクラスの画像ピクセル値を配列に挿入
for (int j = 0; j < height; j++)
{
for (int k = 0; k < width; k++)
{
data3d[k, j, i] = bitmap.GetPixel(k, j).B;
}
}
}
return data3d;
}
/// <summary>
/// byte2次元配列からbitmapオブジェクトに変換
/// </summary>
static Bitmap ConvertFromDoubleToBitmap(byte[,] data, int x, int y)
{
byte max = byte.MinValue;
byte min = byte.MaxValue;
for (int i = 0; i < x; i++)
{
for (int j = 0; j < y; j++)
{
if (max < data[i, j])
{
max = data[i, j];
}
if (min > data[i, j])
{
min = data[i, j];
}
}
}
Bitmap bitmap = new Bitmap(x, y);
for (int i = 0; i < x; i++)
{
for (int j = 0; j < y; j++)
{
if (max != min)
{
int cor =
(int)(
(double)(data[i, j] - min) /
(double)(max - min) * 255.0);
System.Drawing.Color color =
System.Drawing.Color.FromArgb(cor, cor, cor);
bitmap.SetPixel(i, j, color);
}
}
}
return bitmap;
}
/// <summary>
/// 画像の保存
/// </summary>
public static void SaveBitmap(
string fileName, byte[,] data, int x, int y)
{
Bitmap bitmap = ConvertFromDoubleToBitmap(data, x, y);
bitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Bmp);
}
/// <summary>
/// 3次元画像の保存
/// </summary>
public static void SaveBitmap3D(
List<string> fileNameList, byte[, ,] data3d, int x, int y, int z)
{
for (int i = 0; i < z; i++)
{
byte[,] data2d = new byte[x, y];
for (int j = 0; j < y; j++)
{
for (int k = 0; k < x; k++)
{
data2d[k, j] = data3d[k, j, i];
}
}
SaveBitmap(fileNameList[i], data2d, x, y);
}
}
/// <summary>
/// 3次元データの線形補間
/// </summary>
static byte Interpolation3D(
byte[, ,] data, double x, double y, double z)
{
// 画像サイズ取得
int width = data.GetLength(0);
int height = data.GetLength(1);
int depth = data.GetLength(2);
// dataの外なら0を返す
if (x < 0 || x >= width - 1 ||
y < 0 || y >= height - 1 ||
z < 0 || z >= depth - 1)
{
return 0;
}
// 隣接座標取得
int x1 = (int)x;
int x2 = (int)x + 1;
int y1 = (int)y;
int y2 = (int)y + 1;
int z1 = (int)z;
int z2 = (int)z + 1;
double s1 =
(data[x2, y1, z1] - data[x1, y1, z1]) *
(x - x1) + data[x1, y1, z1];
double s2 =
(data[x2, y2, z1] - data[x1, y2, z1]) *
(x - x1) + data[x1, y2, z1];
double s12 = (s2 - s1) * (y - y1) + s1;
double s3 =
(data[x2, y1, z2] - data[x1, y1, z2]) *
(x - x1) + data[x1, y1, z2];
double s4 =
(data[x2, y2, z2] - data[x1, y2, z2]) *
(x - x1) + data[x1, y2, z2];
double s34 = (s2 - s1) * (y - y1) + s1;
double sp = (s34 - s12) * (z - z1) + s12;
if (sp > 255)
{
return 255;
}
else if (sp < 0)
{
return 0;
}
else
{
return (byte)sp;
}
}
}
}
ソースコードは、以下のURLから
http://colnagow61s.me.land.to/Image3DGeometryTransform.zip
サンプルデータは容量が大きいため添付していません。
各自用意してください。
| 詳解 画像処理プログラミング C言語で実装する画像処理アルゴリズムのすべて | |
![]() | 昌達 慶仁 ソフトバンククリエイティブ 2008-03-27 売り上げランキング : 5054 おすすめ平均 ![]() とてもわかりやすくて、多くのアルゴリズムを網羅しています。Amazonで詳しく見る |
| 独習C# 第2版 | |
![]() | NRIラーニングネットワーク株式会社 矢嶋 聡 株式会社テック・インデックス 翔泳社 2007-04-20 売り上げランキング : 34373 おすすめ平均 ![]() 買って良かった GUIアプリケーションの習得は別の本でAmazonで詳しく見る |
この記事のトラックバックURL
http://thorshammer.blog95.fc2.com/tb.php/253-7b914bc7
この記事にトラックバックする(FC2ブログユーザー)
この記事にトラックバックする(FC2ブログユーザー)
この記事へのトラックバック




GUIアプリケーションの習得は別の本で