Magick++経由でインターネット上の画像やGIF画像等をOpenCVに読み込む

OpenCVWindows bitmap, JPEG, PNG等のフォーマットの画像を読み込むことができるが、割と一般的なフォーマットであるGIF画像を直接読み込むことができない。
また、インターネット上の画像ファイルを直接読み込むこともできない。
ので、今回は様々なフォーマットに対応しているImageMagickの、C++APIであるMagick++経由でインターネット上の画像やGIF画像等をOpenCVのIplImageに突っ込むことを考える。
ImageMagickやMagick++のインストールは各自の環境に応じてググってもらうとして、以下のページを参考に、Magick::Imageに読み込んでメモリをIplImageに渡せば、別フォーマットやローカルのファイルに保存することなく読み込めた。
Magick::Image Class - ImageMagick
ImageMagick • View topic - ImageMagick + Opencv

////////////////////////////////////////////////////////////////////
// ReadfromMagick++.cpp by @Luigitefu
// Magick++経由でインターネット上の画像やGIF画像等をOpenCVに読み込む
////////////////////////////////////////////////////////////////////
#include <string>
#include <cv.h>
#include <highgui.h>
#include <Magick++.h>

using namespace std; 
using namespace Magick; 

//Magick++で読み込んでIplImageを返す
IplImage* myReadImage(const string str){
  Image *magicImage= new Image(str); 
  int width= magicImage->size().width();
  int height = magicImage->size().height();
  IplImage* cvImage = cvCreateImage(cvSize(width,height),IPL_DEPTH_8U, 4);

  //Iplimageのメモリに書き出し
  magicImage->write(0,0, width, height, "BGRA", MagickCore::CharPixel, cvImage->imageData);

  delete magicImage;
  return cvImage;
}
//てすと
int main(int argc, char* argv[]){
  IplImage *img =  myReadImage("http://www.st-hatena.com/users/Lu/Luigitefu/profile.gif" ); //Magick++経由で読み込み
    //OpenCVで何か処理
  cvSaveImage("out.png",img);
  cvReleaseImage(&img);
  return 0;
}

ちなみに、以下のページによると、ImageMagickはインターネット上のファイルは一旦一時ファイルに保存してるようなので、OpenCVで直接読めるファイルなら、Magick++経由せず、他の手段でローカルにDLして開いても速度等は変わらんかも?

ちなみに strace で追ってみたところ、/tmp/magick-xxxxxxxx と言う一時ファイルを作り、自分で inet socket を開いてファイルを取得しているらしい。

今日のトリビア - ImageMagick は http を話せる - World Wide Walker

twitter アイコンから or OpenCVで Hybrid images 生成

twitter アイコンで Hybrid images が作れるページを公開しました。
20110306225252
http://luigitefu.xrsp.net/cgi-bin/hybridtwicon/

・Hybrid images とは

Hybrid images とは人間の視覚特性を利用し、(近視 or 縮小 or 遠くから見る)場合と、(not近視 or 拡大 or 近くから見る)場合で違って見える画像のことです。
MITで考案され、以下のページと論文に多数の具体例や原理が紹介されています。
   Hybrid Images @ MIT
   Hybrid Images - A. Oliva, A. Torralba, P.G. Schyns, ACM Transactions on Graphics, vol. 25-3, pages 527-530, 2006.

今回は2つの画像を入力とすることで、縮小したときと、拡大したときに、それぞれの画像が強く見えるような画像を生成しています。
An example of hybrid images

それぞれの画像の低周波成分(ぼかした画像)と、高周波成分(エッジ部分)だけを組み合わせるとHybrid images が生成されます。
具体的な処理としては、周波数領域において、以下のαブレンドのような計算を行っています。ただし、ここでGはガウシアンカーネルです。
Output = Input1・G + Input2・(1-G) 
このガウシアンカーネルのパラメータ(標準偏差)を調節することで、各画像の強さや、どの程度の距離の人にどういった画像を見せたいか、といった調節が可能です。画像のエッジの強さ等によっても適切なパラメータが変わってくると思います。

twitterアイコンだけでなく、任意の画像で Hybrid imagesを生成したい場合は、以下のページが参考になると思います(MATLABOctave が必要になりますが)。
   hybrid imagesの作り方 - デー
ちなみに、ここで紹介されているインデックスとイカ娘で作られたHybrid imageは必見じゃなイカ

OpenCV で Hybrid images

ついでに、C++ / OpenCV による Hybrid images を生成するプログラムのソースコードを以下に晒しておきます。
かなり古臭い書き方をしているので、多分OpenCV1.0以上であれば動くと思います。

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HybridImages.cpp  by @Luigitefu
//
// 2つの入力画像からHybrid imagesを生成します.
//
// Hybrid imagesの詳細については以下のWebページと論文を参照してください.
//    http://cvcl.mit.edu/hybridimage.htm
//    Hybrid Images - A. Oliva, A. Torralba, P.G. Schyns, ACM Transactions on Graphics, vol. 25-3, pages 527-530, 2006. 
//
// コンパイルには,OpenCVが必要です.
// OpenCVによるDCTに関しては,以下のWebページを参考にしています.
//    http://opencv.jp/sample/discrete_transforms.html 
//    http://opencv.jp/opencv-1.0.0/document/opencvref_cxcore_discrete.html
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <math.h>
#include <cv.h>
#include <highgui.h>

// OpenCV ライブラリ読み込み
#pragma comment(lib,"cv.lib")
#pragma comment(lib,"cxcore.lib")
#pragma comment(lib,"highgui.lib")


/////////////////////////
////  パラメータ類   
/////////////////////////
#define SIGMA 7.5            // ガウシアンカーネルの標準偏差パラメータ.これが大きいとINPUT1がより強く見える
#define SIDE_MAX 400        // INPUT1のどちらか1辺のサイズがこれより大きければリサイズ
#define INPUT1 "input1.png" // 縮小したときに強く見える画像
#define INPUT2 "input2.png" // 拡大したときに強く見える画像
#define OUTPUT "output.png" // 出力されるHybrid images


////////////////////////////////////
//// ガウシアンカーネル作成
////////////////////////////////////
void myGauss(
	CvMat* gau1,
	CvMat* gau2,
	const float sig
	){
		float gaui,gaud;
		int xs,ys,x,y;
		float xm,ym;
		int cx,cy;
		CvMat *tmp = 0;
		CvMat q1stub, q2stub;
		CvMat q3stub, q4stub;
		CvMat d1stub, d2stub;
		CvMat d3stub, d4stub;
		CvMat *q1, *q2, *q3, *q4;
		CvMat *d1, *d2, *d3, *d4;
		float ss2=sig*sig*2;

		xs=gau1->width;
		ys=gau1->height;
		xm=(float)((float)xs/2.0);
		ym=(float)((float)ys/2.0);
		for(x=0;x<xs;x++){
			for(y=0;y<ys;y++){
				gaud = (xm-x)*(xm-x)+(ym-y)*(ym-y);
				gaui = exp(-gaud/ss2);
				gau1->data.fl[(gau1->width*y+x)*2] = gaui;
				gau1->data.fl[(gau1->width*y+x)*2+1] = gaui;
				gau2->data.fl[(gau2->width*y+x)*2] = 1-gaui;
				gau2->data.fl[(gau2->width*y+x)*2+1] = 1-gaui;
			}
		}

		////////////////////////////////////////////////////////////
		// カーネルの象限を入れ替える処理
		///////////////////////////////////////////////////////////
		tmp = cvCreateMat (ys / 2, xs / 2,CV_32FC2  );
		cx=(int)xm;
		cy=(int)ym;

		q1 = cvGetSubRect (gau1, &q1stub, cvRect (0, 0, cx, cy));
		q2 = cvGetSubRect (gau1, &q2stub, cvRect (cx, 0, cx, cy));
		q3 = cvGetSubRect (gau1, &q3stub, cvRect (cx, cy, cx, cy));
		q4 = cvGetSubRect (gau1, &q4stub, cvRect (0, cy, cx, cy));
		d1 = cvGetSubRect (gau1, &d1stub, cvRect (0, 0, cx, cy));
		d2 = cvGetSubRect (gau1, &d2stub, cvRect (cx, 0, cx, cy));
		d3 = cvGetSubRect (gau1, &d3stub, cvRect (cx, cy, cx, cy));
		d4 = cvGetSubRect (gau1, &d4stub, cvRect (0, cy, cx, cy));
		cvCopy (q3, tmp, 0);
		cvCopy (q1, q3, 0);
		cvCopy (tmp, q1, 0);
		cvCopy (q4, tmp, 0);
		cvCopy (q2, q4, 0);
		cvCopy (tmp, q2, 0);
		q1 = cvGetSubRect (gau2, &q1stub, cvRect (0, 0, cx, cy));
		q2 = cvGetSubRect (gau2, &q2stub, cvRect (cx, 0, cx, cy));
		q3 = cvGetSubRect (gau2, &q3stub, cvRect (cx, cy, cx, cy));
		q4 = cvGetSubRect (gau2, &q4stub, cvRect (0, cy, cx, cy));
		d1 = cvGetSubRect (gau2, &d1stub, cvRect (0, 0, cx, cy));
		d2 = cvGetSubRect (gau2, &d2stub, cvRect (cx, 0, cx, cy));
		d3 = cvGetSubRect (gau2, &d3stub, cvRect (cx, cy, cx, cy));
		d4 = cvGetSubRect (gau2, &d4stub, cvRect (0, cy, cx, cy));
		cvCopy (q3, tmp, 0);
		cvCopy (q1, q3, 0);
		cvCopy (tmp, q1, 0);
		cvCopy (q4, tmp, 0);
		cvCopy (q2, q4, 0);
		cvCopy (tmp, q2, 0);
}


//////////////////////////////////////////
//// 1チャンネルでの Hybrid images 生成
//////////////////////////////////////////
void HybridImages1ch(
	CvMat* src1,
	CvMat* src2, 
	CvMat* dft_A,
	CvMat* dft_B, 
	const CvMat* gau1, 
	const CvMat* gau2, 
	const CvMat* srcIm )
{
	// 入力画像と虚数配列をマージして複素数平面を構成
	CvMat* complex1 = cvCreateMat( src1->rows, src1->cols, CV_32FC2 ); 
	CvMat* complex2 = cvCreateMat( src1->rows, src1->cols, CV_32FC2 );
	cvMerge(src1, srcIm, NULL, NULL,complex1);
	cvMerge(src2, srcIm, NULL, NULL,complex2);

	CvMat tmp;
	// 複素数平面をdft_A, dft_Bにコピーし,残りの行列右側部分を0で埋めた後,離散フーリエ変換を行う
	cvGetSubRect( dft_A, &tmp, cvRect(0,0,complex1->cols,complex1->rows));
	cvCopy( complex1, &tmp );
	if(dft_A->cols > complex1->cols){
		cvGetSubRect( dft_A, &tmp, cvRect(complex1->cols,0,dft_A->cols - complex1->cols,complex1->rows));
		cvZero( &tmp );
	}
	cvDFT( dft_A, dft_A, CV_DXT_FORWARD, complex1->rows );

	
	cvGetSubRect( dft_B, &tmp, cvRect(0,0,complex1->cols,complex1->rows));
	cvCopy( complex2, &tmp );
	if(dft_A->cols > complex1->cols){
		cvGetSubRect( dft_B, &tmp, cvRect(complex1->cols,0,dft_B->cols - complex1->cols,complex1->rows));
		cvZero( &tmp );
	}
	cvDFT( dft_B, dft_B, CV_DXT_FORWARD, complex1->rows );

	// 周波数領域において,ガウシアンカーネルGを用いて以下の処理を行う
	// Output = Input1・G + Input2・(1-G)
	cvMul(dft_A,gau1,dft_A);
	cvMul(dft_B,gau2,dft_B);
	cvAdd(dft_A,dft_B,dft_A);

	// 離散フーリエ逆変換し,実数成分をsrc1に,虚数成分をsrc2に格納
	cvDFT( dft_A, dft_A, CV_DXT_INV_SCALE, src1->rows ); // 上部のみを計算する
	cvGetSubRect( dft_A, &tmp, cvRect(0,0,src1->cols,src1->rows) );
	cvCopy( &tmp, complex1 );
	cvSplit(complex1,src1,src2,0,0);
	cvReleaseMat(&complex1);
	cvReleaseMat(&complex2);
}


//////////////////////////////
////  Hybrid images 生成
//////////////////////////////
void HybridImages(
	CvMat* src1,
	const CvMat* src2, 
	const float sig )
{
	//行列確保
	int dft_M = cvGetOptimalDFTSize( src1->height ); 
	int dft_N = cvGetOptimalDFTSize( src1->width  ); 
	CvMat* src1b = cvCreateMat( src1->rows, src1->cols, CV_32FC1 ); 
	CvMat* src1g = cvCreateMat( src1->rows, src1->cols, CV_32FC1 ); 
	CvMat* src1r = cvCreateMat( src1->rows, src1->cols, CV_32FC1 ); 
	CvMat* src2b = cvCreateMat(src2->rows, src2->cols, CV_32FC1 ); 
	CvMat* src2g = cvCreateMat(src2->rows, src2->cols, CV_32FC1 ); 
	CvMat* src2r = cvCreateMat(src2->rows, src2->cols, CV_32FC1 ); 
	CvMat* srcIm = cvCreateMat(src2->rows, src2->cols, CV_32FC1 ); 
	CvMat* dft_A = cvCreateMat( dft_M, dft_N, CV_32FC2 ); 
	CvMat* dft_B = cvCreateMat( dft_M, dft_N, CV_32FC2 ); 
	CvMat* dft_tmp = cvCreateMat( dft_M, dft_N, CV_32FC1 ); 
	CvMat* gau1 = cvCreateMat( dft_M, dft_N, CV_32FC2 );
	CvMat* gau2 = cvCreateMat( dft_M, dft_N, CV_32FC2 );
	myGauss(gau1,gau2,sig);
	cvZero(srcIm);

	//各チャンネルごとに処理
	cvSplit(src1,src1b,src1g,src1r,0);
	cvSplit(src2,src2b,src2g,src2r,0);
	HybridImages1ch(src1b, src2b, dft_A, dft_B, gau1, gau2,srcIm);
	HybridImages1ch(src1g, src2g, dft_A, dft_B, gau1, gau2,srcIm);
	HybridImages1ch(src1r, src2r, dft_A, dft_B, gau1, gau2,srcIm);
	cvMerge(src1b, src1g, src1r, NULL,src1);

	//メモリの解放
	cvReleaseMat(&src1b);
	cvReleaseMat(&src1g);
	cvReleaseMat(&src1r);
	cvReleaseMat(&src2b);
	cvReleaseMat(&src2g);
	cvReleaseMat(&src2r);
	cvReleaseMat(&srcIm);
	cvReleaseMat(&dft_A);
	cvReleaseMat(&dft_B);
	cvReleaseMat(&gau1);
	cvReleaseMat(&gau2);
}

/////////////////////////////////////////
//// 画像を読み込んでHybrid imagesを保存
/////////////////////////////////////////
int HybridImages_main(
	const char* fname1,
	const char* fname2,
	const char* outname,
	float sig )
{
	IplImage *img1,*img2,*result,*dst1,*dst2, *res,img_hdr;
	int width,height;
	CvMat istub1, *src1,istub2,*src2;

	//INPUT1
	if((img1=cvLoadImage(fname1 ,CV_LOAD_IMAGE_COLOR ))==0){
		return -1;
	}
	if(img1->width > img1->height){
		if(img1->width > SIDE_MAX){
			width=SIDE_MAX;
			height = SIDE_MAX* img1->height / img1->width;
		}else{
			width =img1->width;
			height = img1->height;		
		}	
	}else{
		if(img1->height > SIDE_MAX){
			height=SIDE_MAX;
			width = SIDE_MAX* img1->width / img1->height;
		}else{
			width =img1->width;
			height = img1->height;		
		}
	}
	dst1 =cvCreateImage(cvSize(width,height),IPL_DEPTH_8U, 3);
	cvResize(img1,dst1,CV_INTER_CUBIC);
	cvReleaseImage(&img1);
	img1 = cvCreateImage(cvSize(width,height), IPL_DEPTH_32F, 3);
	cvConvertScale(dst1, img1);
	src1 = cvGetMat(img1, &istub1);
	cvReleaseImage(&dst1);
	
	//INPUT2
	if( (img2 = cvLoadImage(fname2, CV_LOAD_IMAGE_COLOR ) )==0){
		return -1;
	}
	dst2 =cvCreateImage(cvSize(width,height),IPL_DEPTH_8U, 3);
	cvResize(img2,dst2,CV_INTER_CUBIC); 
	cvReleaseImage(&img2);
	img2 = cvCreateImage(cvSize(width,height), IPL_DEPTH_32F, 3);
	cvConvertScale(dst2, img2);
	src2 = cvGetMat(img2, &istub2);
	cvReleaseImage(&dst2);

	//Hybrid images 生成処理を呼び出し,結果画像を保存
	HybridImages(src1, src2, sig);
	result = cvCreateImage(cvSize(width,height), IPL_DEPTH_8U, 3);
	res = cvGetImage(src1, &img_hdr);
	cvConvertScaleAbs(res, result);
	if( (cvSaveImage(outname,result))==0){
		return -1;
	}
	return 1;
}


int main(int argc, char* argv[]){
	HybridImages_main(INPUT1,INPUT2,OUTPUT,SIGMA);
	return 1;
}

一方通行さん風の口調変換を作ってみた

とある魔術の禁書目録」の裏主人公こと、一方通行さんっぽい口調(一方通行語?)になる変換フォームと、ブックマークレットを作ってみました。



・基本的な変換

一方通行さん口調、最大の特徴は、小さい「ぁぃぅぇぉ」と、「ん」が全てカタカナになることです。

例:「ぁぃぅぇぉん」→「ァィゥェォン」

これを単純に置換するだけでも、多少一方通行さんっぽくなるのですが、
普通の文章を、より一方通行さんらしくするため、現代仮名遣い表記の長音もカタカナにしてみました。
お段の仮名の長音の場合には、通常、「お」のかわりに「う」を添えるのですが、
一方通行さんは「ォ」を用いるようなので、それに準拠しました。

例:「はあ・・・どうしてこうなった」→「はァ・・・どォしてこォなった」

長音符を用いた長音については、普通に使っていた気がするので、とりあえずそのままにしておきました。

また、一部特殊な置換も行っています。

例:「木原君」→「木ィィィ原くゥゥゥゥゥゥゥゥン!!」

分かち書き(単語分割)の利用

長音の変換において、複数単語にまたがった母音の重なりを誤変換することがあったため、
工藤拓氏のTinySegmenterにより、分かち書きを行った上で上記の置換を行いました。
具体的には、次のような場合に誤変換を回避できているかと思います。

入力文

こんな下らない物を作ってないで、研究を進めてはどうですか、とミサカはあなたに現実を突きつけます。

分かち書き無し

こンな下らない物を作ってないで、研究を進めてはどォですか、とミサカはァなたに現実を突きつけます。

分かち書き有り

こンな下らない物を作ってないで、研究を進めてはどォですか、とミサカはあなたに現実を突きつけます。

・その他

大した使い道があるのかわかりませんが、自己責任でご利用ください。

ブックマークレット版は微妙に仕様が違います。
まだ誤変換する場合があったり、タイトルが微妙だったり、他にも特徴を追加できそうだったり、と改良の余地はあるので、気が向いたら更新するかもしれません。

Ubuntu 8.04にアップグレード

アップデート・マネージャでUbuntuを7.10→8.04にアップグレードした。
とりあえずcompizが一旦動かなくなった。
2chの8.04開発版のスレ(参照)によるとノートPCでatiradeonだと動かないようにするコードがあるようで、/usr/bin/compizの268-279をコメントアウトすれば一応動いた。
だが、以前はcompizconfig-settings-managerをいれていればシステム→設定→外観の設定→視覚効果のところからcompizの詳細設定ができたが、できなくなっていた。とりあえず視覚効果を有効にして、システム→設定→
Advanced Desktop Effects Settingsで設定できた。

あとは何か気づいたらそのうち追記するかも

仮想マシンを実際に使った感想

実際に両方の仮想マシンをノートPC、デスクトップともに導入して多少使った感想を書いてみる。

VMware

配布されているUbuntu仮想マシンではToolsは最初に起動して最小化されるが、これを間違えて閉じてしまうとToolsの機能が使えなくなってしまい、実行しなおす必要がある。


Toolsにより、ゲスト⇔ホスト間のドラッグ&ドロップでのファイルのやり取りができるということだったが、自分の環境ではできなかった。ただし、ファイルのやり取りに関してはフォルダの独自共有が比較的容易であり、また、Windowsネットワーク上の共有フォルダへのアクセスも可能だったため、大きな問題はない。


ウィンドウサイズと解像度の自動リサイズは一応有効だがたまに不安定。グラフィックカードは独自にエミュレートされたもので、ビデオメモリなどの設定はできないようだった。


気になったのが、起動しただけでは実際にはメモリの割り当ては行われていないようだったこと。ホストOS側でタスクマネージャでメモリの使用量を見ていたが、仮想マシンに設定したメモリよりも増加量がはるかに小さく、実際に使用するときに確保しているのだろうか。


VirtualBox

VMwareにはできなかった機能として、ビデオメモリの容量を設定することができる。ただし、VMwareと同様にビデオカードは独自のエミュレートされたもので、実際のグラボ用のドライバなどは入れることができない。


xserver-xglをインストールすれば、仮想OS上でもCompizで3Dデスクトップ効果を使うことができた!(VMwareではできなかった)
ただ、当然ながらデュアルブートで起動したUbuntuの3D効果に比べてかなり重いものだった。快適に3Dデスクトップ効果を利用したい場合は、廃スペックでもない限り、素直にデュアルブートしたほうがいいと思う。


以下細かい点
・ネットワーク環境に関しては、何故か速度が遅い、Windowsネットワークが見れないなど、良好とはいえなかった。

・起動するだけで設定した以上のメモリを必要とし、VMwareよりもCPU使用率が大きかった。

・ホスト⇔ゲスト間のファイルのやりとりが面倒。

・設定関連のファイルはWindowsだとDocuments and Settingsの中に作られるため、スナップショットを取るとそこに大きな容量のファイルが作られてしまう。

・ウィンドウサイズと解像度の自動リサイズはできるらしいけど有効にならなかった。

で、どうするか

VirtualBoxはメモリがかなり必要になるため、メモリが512MBのノートPCではVMwareを利用することにした。また、デスクトップでは性能にそれなりの余裕があり、VirtualBoxのが多少快適に動いたのでVirtualBoxを利用することにした。

仮想マシンを導入

仮想マシンを導入した理由

今まで大学では基本的にUbuntuで生活していたんだけど、どうも研究室ではWindowsの方が良さそうなので、Windowsに切り替えることにした。それでもLinuxの環境がなくなるのは困るかもしれないので仮想マシンでホストOSとしてWindows、ゲストOSとしてUbuntuを入れることにした。ついでに家のデスクトップ(Vista)にも入れることにした。VistaだとCygwinのXが不安定でgnuplotとかが使えなかったから。

Ubuntu仮想マシン

Ubuntuでは、VMware用の仮想マシンVirtualBox用の仮想マシンが配布されているのでそれを使えば楽。

VMwareの特徴

VMwareはVMwarePlayerとVMwareServerがフリーで使える。
Serverはフリーだが個人情報の入力とユーザー認証が必要。仮想マシンの作成、実行が可能。

Serverにはグラフィック性能向上やマウスの統合、コピー&ペーストの共有などが可能なVMwareTools(ゲストOS側にインストールする)が同梱されている。Playerにも2.xならToolsが入ってるという話をみかけたがどうすればいいのかはわからなかった。また、PlayerでもServerのインストーラーからVMwareToolsをとりだしてゲストOSに入れることができる模様(ライセンス的に問題ないらしいが気になる場合は調べたほうがいいかも)。ただし、配布されているUbuntu仮想マシンにはすでにToolsが入っていた。


Playerは仮想マシンを新しく作ることはできず、ほかのVMwareで作成された仮想マシンを実行することしかできない。ただ、メモリの容量くらいであれば変えることはできるし、vmxファイルを書き換えることで多少融通は利く。また、仮想マシンの実行だけならServerよりPlayerのが軽いので、配布されているVMware用の仮想マシンを使う場合はPlayerを使えばよいと思う。


環境のバックアップをとることができるSnapShot機能はServerにはあり、Playerにはないが、どうしてもバックアップがとりたければ仮想マシンのファイルごとコピーすればいいと思う。


学科で借りているノートPCには製品版のVMwareWorkstationが入っていたのだがバージョンが古く、配布されているUbuntu仮想マシンを使うことができなかったのでアンインストールしてPlayerを入れた。

VirtualBoxの特徴

VirtualBoxも個人利用ならフリー。VirtualBox仮想マシンの新規作成、設定、実行ができる。また、VMwareToolsのようなVirtualBox Guest Additionsがあるが、これも配布されてるUbuntu仮想マシンにはすでに入ってたようだ。
あと、SnapShotが取れるなど、機能自体はVMwareServerとよく似ていると感じた。