Home > ソフトウエア | 技術 > IplImageをCArchiveで保存する

IplImageをCArchiveで保存する

この2年ほど忙しすぎて会社のHPをほったらかしにしていたのですが、WEBの更新がない会社って大丈夫だろうか?っていう時代だし、何か書かないといけないねという強迫観念に駆られまして、約2年ぶりに記事を書いております。
久しぶりの記事の内容は何にするかというのは、これもまた悩ましいところですが、世間の関心のある先端技術はよそにまかせて、自分が先日困ったことを話題に取り上げて久しぶりの記事投稿再開の1号としたいと思います。

WindowsのアプリをC++でゴリゴリ書くのは、もう時代遅れかもしれないのですが、画像を扱うアプリを書くときには、今もMFCは大事な選択しだと思うのです。
2016年の年末になった今日も、16年前とほとんど変わらないMFCを使ってWindowsアプリを書いているというのは、考えてみるとなかなかMFCすごいんじゃないかって思うんですが同意してくれる人があんまりいませんね。
日進月歩のソフトウエア技術のなかにあっても、未だに約20年前とほぼ同じ仕組みをつかって最新のWindowsでも動作するハイパフォーマンスソフトを書けるのですから私のような中年のソフト屋さんにはMFCは手放せません。

MFCを使って画像処理アプリを書いていると画像を扱うIplImageとかcv::MatとかをCArchive.Serializeしちゃいたい時がよくある訳なんですが、IplImage->imageDataをそのままWriteしちゃったりすると、ものすごいファイルが大きくなるわけです。だから多少の画像の劣化は妥協して、JPEG圧縮したうえで, CArchiveにSerializeしたいなと思うことになるんですが、いざやってみようかとおもうと、libjpeg使わないとダメかなあとか、結構めんどくさそうな感じになってくるんですね。
手軽にだれかが作ってくれたIplImageをCArchiveしてくれるコードがないかなあと探してみたりしたのですが、今の時代にMFCを使ってる人ってあんまりいないみたいで、MFC and IplImage and CArchiveなどという情報は、Google先生もズバリな答えを教えてくれません。仕方なしに、ほかの断片的な情報をつなぎ合わせて、Gdiplusの機能を使って IplImageをCArchiveに保管するコードを書いてみましたのでご紹介します。

IplImageをCArchiveに保存する関数がwrite_iplimage_with_jpeg()です。
Jpeg圧縮後のデータを保存するためのメモリーをGlobalAllocで確保していますが、生画像サイズよりは小さくなるだろうということで、生画像サイズ丸ごと確保しています。
その後にGdiplusに圧縮してもらうため、メモリーブロックからIStremハンドルを取得しています。GetEncoderClsid()は、MSDNのサンプルから頂きました。
IplImage2GdiPlusBitmap()は、IplImageをGdiplusのBitmapに変換する自作関数です。”image/jpeg”を、”image/png”にしたらPNG形式でSerializeできます。
IplImage2GdiPlusBitmap()も記事の後半に貼り付けておきます。

	//IplImageをJPEG圧縮してCArchiveに保存する
	void write_iplimage_with_jpeg(IplImage* ipl, CArchive& ar, int quality)
	{
		int w = ipl->width, h = ipl->height;
		HGLOBAL hJpegBuffWrite = ::GlobalAlloc(GMEM_MOVEABLE, w * h * 3);
		BYTE * pJpegBuffWrite = (BYTE*)::GlobalLock(hJpegBuffWrite);
		IStream*isImageWrite;
		CreateStreamOnHGlobal(hJpegBuffWrite, TRUE, &isImageWrite);/* メモリブロックからIStreamを作成 */
		CLSID jpgClsid;
		GetEncoderClsid(L"image/jpeg", &jpgClsid);

		ULARGE_INTEGER pos;
		LARGE_INTEGER dis;
		dis.QuadPart = 0;

		Bitmap* pBmp = IplImage2GdiPlusBitmap(ipl);

		Gdiplus::EncoderParameters p;
		p.Count = 1;
		p.Parameter[0].Guid = EncoderQuality;
		p.Parameter[0].Type = EncoderParameterValueTypeLong;
		p.Parameter[0].NumberOfValues = 1;
		p.Parameter[0].Value = &quality;

		pBmp->Save(isImageWrite, &jpgClsid, &p);
		delete pBmp;

		isImageWrite->Seek(dis, STREAM_SEEK_CUR, &pos);
		TRACE("after seek=%d\n", pos.QuadPart);

		BYTE* pJpegBuff = (BYTE*)::GlobalLock(hJpegBuffWrite);
		SIZE_T jpeg_length = (SIZE_T)pos.QuadPart;

		ar << jpeg_length;
		ar.Write(pJpegBuff, jpeg_length);

		isImageWrite->Release();

		::GlobalUnlock(hJpegBuffWrite);
		::GlobalFree(hJpegBuffWrite);
	}

続いて、CArchiveからIplImageを読み出すコードです。

	//CArchiveに保存されたJPEG圧縮画像をIplImageに読み出す
	IplImage* read_iplimage_with_jpeg(CArchive& ar)
	{
		//ar >>
		SIZE_T jpeg_length;
		ar >> jpeg_length;
		HGLOBAL hJpegBuff = ::GlobalAlloc(GMEM_MOVEABLE, jpeg_length);
		BYTE * pJpegBuff = (BYTE*)::GlobalLock(hJpegBuff);
		ar.Read(pJpegBuff, jpeg_length);//JPEGデータの読み込み
		::GlobalUnlock(hJpegBuff);
		IStream* isImage;
		CreateStreamOnHGlobal(hJpegBuff, TRUE, &isImage);/* メモリブロックからIStreamを作成 */
		Gdiplus::Bitmap bmp(isImage);

		IplImage* new_image = GdiPlusBitmap2IplImage(&bmp);
		TRACE("(%d,%d)\n", bmp.GetWidth(), bmp.GetHeight());

		::GlobalFree(hJpegBuff);
		return new_image;
	}

IplImageからGdiplusのBitmapに変換する関数とその逆の関数です。GdiPlusBitmap2IplImage()の戻り値のIplImage*は、使用後にReleaseするのを忘れてはいけません。

	Gdiplus::Bitmap*  IplImage2GdiPlusBitmap(IplImage* pIplImg)
	{
		if (!pIplImg)
			return NULL;

		Gdiplus::Bitmap *pBitmap = new Gdiplus::Bitmap(pIplImg->width, pIplImg->height, PixelFormat24bppRGB);
		if (!pBitmap)
			return NULL;

		Gdiplus::BitmapData bmpData;
		Gdiplus::Rect rect(0, 0, pIplImg->width, pIplImg->height);
		pBitmap->LockBits(&rect, Gdiplus::ImageLockModeWrite, PixelFormat24bppRGB, &bmpData);
		BYTE *pByte = (BYTE*)bmpData.Scan0;

		if (pIplImg->widthStep == bmpData.Stride) //likely
			memcpy(bmpData.Scan0, pIplImg->imageDataOrigin, pIplImg->imageSize);

		pBitmap->UnlockBits(&bmpData);
		return pBitmap;
	}

	IplImage* GdiPlusBitmap2IplImage(Gdiplus::Bitmap* bmp)
	{
		auto format = bmp->GetPixelFormat();
		if (format != PixelFormat24bppRGB)
			return nullptr;

		Gdiplus::Rect rcLock(0, 0, bmp->GetWidth(), bmp->GetHeight());
		Gdiplus::BitmapData bmpData;

		bmp->LockBits(&rcLock, Gdiplus::ImageLockModeRead, format, &bmpData);

		int buffSz = bmpData.Stride * bmpData.Height;
		int depth = 8, channel = 3;
		IplImage* cvImage = cvCreateImage(cvSize(rcLock.Width, rcLock.Height), depth, channel);
		const unsigned char* src = static_cast<unsigned char*>(bmpData.Scan0);
		std::copy(src, src + buffSz, cvImage->imageData);

		bmp->UnlockBits(&bmpData);
		if (bmpData.Stride<0)
			cvFlip(cvImage, cvImage);

		return cvImage;
	}

MSDNのサンプルから引っ張ってきた関数です。

	static int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
	{
		UINT  num = 0;          // number of image encoders
		UINT  size = 0;         // size of the image encoder array in bytes

		ImageCodecInfo* pImageCodecInfo = NULL;

		GetImageEncodersSize(&num, &size);
		if (size == 0)
			return -1;  // Failure

		pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
		if (pImageCodecInfo == NULL)
			return -1;  // Failure

		GetImageEncoders(num, size, pImageCodecInfo);

		for (UINT j = 0; j < num; ++j)
		{
			if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
			{
				*pClsid = pImageCodecInfo[j].Clsid;
				free(pImageCodecInfo);
				return j;  // Success
			}
		}

		free(pImageCodecInfo);
		return -1;  // Failure
	}

T.Kamata

Home > ソフトウエア | 技術 > IplImageをCArchiveで保存する

Search
Feeds
Meta

Return to page top