機械学習基礎理論独習

誤りがあればご指摘いただけると幸いです。数式が整うまで少し時間かかります。リンクフリーです。

勉強ログです。リンクフリーです
目次へ戻る

【VS2022】OpenCVメモ【C++】

cv::Mat を CImage に変換

cv::Mat を直接 DC に描画できないので、CImage に変換する関数を作成しました。
CImage にさえなってくれれば、こっちのもんです。

BOOL ConvertMatToCImage(const cv::Mat& mat, CImage& cimg)
{
	if (mat.empty()) return FALSE;

	cv::Mat mat8U;
	if (mat.type() == CV_8UC1) {
		cv::cvtColor(mat, mat8U, cv::COLOR_GRAY2BGR); // CV_8UC3 に変換
	}
	else if (mat.type() == CV_8UC3) {
		mat8U = mat;
	}
	else {
		return FALSE; // 非対応型
	}

	cimg.Destroy();
	if (FAILED(cimg.Create(mat8U.cols, mat8U.rows, 24))) {
		return FALSE;
	}

	BYTE* pDstBits = (BYTE*)cimg.GetBits();
	int dstPitch = cimg.GetPitch();

	for (int y = 0; y < mat8U.rows; ++y)
	{
		BYTE* pDstRow = pDstBits + y * dstPitch;
		const cv::Vec3b* pSrcRow = mat8U.ptr<cv::Vec3b>(y);
		memcpy(pDstRow, pSrcRow, mat8U.cols * 3); // 高速化(1行まとめてコピー)
	}

	return TRUE;
}

cv::findContours()

cv::findContours() は輪郭を取得するメソッドですが、私が思っていた通りに動いてくれませんでした。
対象の Mat

4つの輪郭に仕分けられた Mat

私としては輪郭が重複して欲しくなかったので、重複している点を除去してみたが一番外側の点が完全一致しているわけではなく
内側の点群だけを取得するのは難しそう。
なので、点列をたどるのは自前で実装するしかないと思っている。

とはいえ、輪郭抽出としては優秀なので、輪郭抽出した結果を Mat に描画してその点列を自前でたどろうと思っている。

輪郭を1つずつ画像ファイルで保存する関数を作ったので貼っておきます。

void saveContours(std::vector<std::vector<cv::Point>> contours, cv::Size size, int thickness, std::string head) {
	// 色定義(8色)
	std::vector<cv::Scalar> colors = {
		cv::Scalar(255, 0, 0),     // 青
		cv::Scalar(0, 255, 0),     // 緑
		cv::Scalar(0, 0, 255),     // 赤
		cv::Scalar(255, 255, 0),   // シアン
		cv::Scalar(255, 0, 255),   // マゼンタ
		cv::Scalar(0, 255, 255),   // 黄
		cv::Scalar(128, 128, 128), // グレー
		cv::Scalar(255, 255, 255)  // 白
	};

	// 輪郭ごとに色を変えて描画
	for (size_t i = 0; i < contours.size(); ++i) {
		// カラー描画用画像(3チャンネル)
		cv::Mat output(size, CV_8UC3, cv::Scalar(0, 0, 0));
		int colorIndex = static_cast<int>(i % colors.size());
		cv::drawContours(output, contours, static_cast<int>(i), colors[colorIndex], thickness);
		std::string output_name = head + std::to_string(i) + ".png";
		cv::imwrite(output_name, output);
	}
}

細線化


OpenCVで細線化するにはOpenCV-contrib の導入が必要が必要です。
導入が面倒だったのでググったら、動くコードがあったので紹介します。
2値画像の細線化にコードと説明があります。そのまま動きます。
アルゴリズム自体単純なので、興味のある人は調べてみてください。

cv:Mat を DC に表示

MFCでいうところの CWnd に cv:Mat をそのままでは表示できません。
そこで cv::Mat を CImage に変換してDraw 関数で表示してみるとなんかおかしいです。
結論としては等倍の場合は BitBlt で表示しました。非等倍の場合でも StretchBlt の方がなんかいいような気がしています。
また、非等倍なら CImage を経由せずに StretchDIBits での表示ができます。
サンプルプログラムの画像の位置やサイズが変数になっていたりなかったりするので、それについては適宜対応してください。

// BitBltCImage
// Renders a CImage object onto a target HDC using BitBlt.
// This function performs a 1:1 pixel transfer (no scaling or interpolation).
// It acquires the source DC from the image, copies the pixel data to the destination DC,
// and releases the image DC to prevent resource leaks.

void BitBltCImage(HDC hDC, const CImage& image) {
    // Acquire the source device context from the CImage
    HDC hdcSrc = image.GetDC();

    // Perform a bit-block transfer from the image DC to the target DC
    BitBlt(
        hDC,                     // Destination device context
        0, 0,                    // Destination coordinates (top-left)
        image.GetWidth(),        // Width of the image
        image.GetHeight(),       // Height of the image
        hdcSrc,                  // Source device context (from CImage)
        0, 0,                    // Source coordinates (top-left)
        SRCCOPY                  // Raster operation: direct copy
    );

    // Release the image's device context to avoid GDI resource leaks
    image.ReleaseDC();
}
// CImage を StretchBlt で表示するサンプル

// ソースDC取得
HDC hdcSrc = image.GetDC();

// StretchBltで描画
SetStretchBltMode(hdcDest, BLACKONWHITE);
StretchBlt(
	hdcDest,
	targetRect.left, targetRect.top,
	targetRect.Width(), targetRect.Height(),
	hdcSrc,
	0, 0, image.GetWidth(), image.GetHeight(),
	SRCCOPY
);

// DC解放(重要)
image.ReleaseDC();
// CImage を StretchDIBitsで表示するサンプル
// ※BGR にする必要があるので CV_8UC1 の場合変換しています。
void RenderMatToDC(HDC hdc, const cv::Mat& mat, const CRect& rect)
{
	// 前提チェック:空画像・描画領域無効
	if (mat.empty()) return;
	const int dstWidth = rect.Width();
	const int dstHeight = rect.Height();
	if (dstWidth <= 0 || dstHeight <= 0) return;

	// 表示用に8bit BGRへ変換(StretchDIBitsはCV_8UC3を想定)
	cv::Mat mat8U;
	if (mat.type() == CV_8UC1) {
		cv::cvtColor(mat, mat8U, cv::COLOR_GRAY2BGR); // CV_8UC3 に変換
	}
	else if (mat.type() == CV_8UC3) {
		mat8U = mat;
	}
	else {
		return; // 非対応型
	}

	// BITMAPINFO 構造体の初期化(24bit BGR)
	BITMAPINFO bmi = {};
	bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmi.bmiHeader.biWidth = mat8U.cols;
	bmi.bmiHeader.biHeight = -mat8U.rows; // 上から下に描画
	bmi.bmiHeader.biPlanes = 1;
	bmi.bmiHeader.biBitCount = 24;
	bmi.bmiHeader.biCompression = BI_RGB;

	// 描画実行
	StretchDIBits(
		hdc,
		rect.left, rect.top,
		dstWidth, dstHeight,
		0, 0,
		mat8U.cols, mat8U.rows,
		mat8U.data,
		&bmi,
		DIB_RGB_COLORS,
		SRCCOPY
	);
}
目次へ戻る