4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

D言語Advent Calendar 2023

Day 23

JetsonでD言語をやる

Posted at

JetsonでD言語

JetsonはNVIDIAが出している組み込み向けのシングルボードコンピュータです。ARMプロセッサに加え、GPUやDLA、PVAといったアクセラレータが搭載されており、AIや画像処理といったアプリケーションを高速に処理することができます。以下の図はJetson Orinの Technical Briefからの引用です。

image.png

Ubuntuが動作するので、D言語は公式のインストールスクリプトで入れることができます。

curl -fsS https://dlang.org/install.sh | bash -s ldc
source ~/dlang/ldc-1.35.0/activate

Screenshot from 2023-12-31 14-32-35.png

簡単ですね。

前述の通り、Jetsonには様々なアクセレラータが搭載されていていて、VPIを使うことでアクセスすることができます。そこでD言語でVPIを使ってみることにします。

D言語でVPI

VPI(Vision Programming Interface。https://docs.nvidia.com/vpi/)とはNVIDIA GPU搭載のコンピュータやJetson向けに画像処理機能を提供するライブラリです。日本語ではこちらの記事などが参考になると思います。

VPIはC APIとPython APIが提供されています。C APIが提供されていることは、理屈上C APIを参照することができる任意の言語で利用可能なはず。D言語コンパイラはC言語をコンパイルすることができるので、VPIをD言語から利用することは難しくないと思います。というわけで、VPIをD言語から呼び出すサンプルプロジェクトを作りました。

dubコマンド1つでビルドと実行が可能です。画像を入力として、エッジ画像を作成します。使用するアクセラレータを3種類から選択できます。入力画像と、実行結果の出力画像を並べたものがこちらです。

Screenshot from 2023-12-16 03-24-36.png

ビルドの流れ

dub.sdlに記述していますが、ビルドは以下の流れで行っています。

  1. VPIのC APIをincludeするヘッダーファイル(include/vpi.h)をプリプロセッサにかけ、マクロ展開後のファイル(include/vpi.iファイル)を生成する。これはdub.sdlのpreBuildCommandsに記述した内容です。
  2. それとDソースと一緒にビルドする。

D言語コンパイラはCコード(*.cファイル)をビルドできますが、ヘッダーのみを使いたい場合はプリプロセッサにかける処理の追加が必要なようです。また、この仮定でマクロが展開されてしまうので、マクロで定義した定数はconst定数で置き換えてあげる必要がありました。このあたりは、こちらの動画を参考にしています。

処理の流れ

プログラムは/opt/nvidia/vpi2/samples/01-convolve_2dにあるC++のサンプルを模擬しています。3x3の畳み込みを行うプログラムで、前述の通りバックエンドを3つ選ぶことができます。選択肢はCPUとCUDA、それからPVAです。

畳み込みを行う部分はただVPIの呼ぶだけなので、元々のサンプルから大きく変わることはないと思います。

void main(string[] args)
{
	VPIImage image;
	VPIImage imageRGB;
	VPIImage gradient;
	VPIStream stream;
	VPIBackend backend;

	try
	{
		if (args.length != 3)
		{
			throw new Exception(format!"Usage: %s <cpu|pva|cuda> <input image>"(args[0]));
		}

		auto strBackend = args[1];
		auto strInputFilename = args[2];

		Image inputImage;
		inputImage.loadFromFile(strInputFilename, LAYOUT_GAPLESS | LAYOUT_VERT_STRAIGHT | LOAD_NO_ALPHA);
		if (inputImage.isError())
		{
			throw new Exception(inputImage.errorMessage.to!string);
		}

		switch (strBackend)
		{
		case "cpu":
			backend = VPI_BACKEND_CPU;
			break;
		case "cuda":
			backend = VPI_BACKEND_CUDA;
			break;
		case "pva":
			backend = VPI_BACKEND_PVA;
			break;
		default:
			new Exception(
				"Backend '" ~ strBackend ~ "' not recognized, it must be either cpu, cuda or pva.");
		}

		checkStatus(vpiStreamCreate(0, &stream));
		scope (exit)
			vpiStreamDestroy(stream);

		checkStatus(imageCreateWrapperGamut(inputImage, 0, &imageRGB));
		scope (exit)
			vpiImageDestroy(imageRGB);

		checkStatus(vpiImageCreate(inputImage.width, inputImage.height, VpiImageFormatU8, 0, &image));
		scope (exit)
			vpiImageDestroy(image);

		checkStatus(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, imageRGB, image, null));

		checkStatus(vpiImageCreate(inputImage.width, inputImage.height, VpiImageFormatU8, 0, &gradient));

		float[3 * 3] kernel = [1, 0, -1, 0, 0, 0, -1, 0, 1];

		checkStatus(vpiSubmitConvolution(stream, backend, image, gradient, kernel.ptr, 3, 3, VPI_BORDER_ZERO));

		checkStatus(vpiStreamSync(stream));

		{
			VPIImageData outData;
			checkStatus(vpiImageLockData(gradient, VPI_LOCK_READ, VpiImageBufferHostPitchLinear, &outData));
			scope (exit)
				checkStatus(vpiImageUnlock(gradient));

			assert(outData.bufferType == VpiImageBufferHostPitchLinear);

			Image outputImage;
			outputImage.createViewFromData(outData.buffer.pitch.planes[0].data, outData.buffer.pitch.planes[0].width, outData
					.buffer.pitch.planes[0].height, PixelType.l8, outData
					.buffer.pitch.planes[0].pitchBytes);

			outputImage.saveToFile("edges_" ~ strBackend ~ ".png");
		}

	}
	catch (Exception e)
	{
		writeln(e.msg);
		return;
	}
}

問題は画像の入出力です。
C++のサンプルではOpenCVを使って画像の読み込みを行っていました。D言語にはないので、別のライブラリを使用します。なんでも良かったのですが、「image」で検索して一番上に出てきたgamutを使ってみました。

作る必要のある構造体は最終的にVPIImageになります。main関数のimageRGBに格納されるものです。これを、/opt/nvidia/vpi2/include/vpi/detail/OpenCVUtils.hppを見ながら何となく作ってみたのがこちら。

VPIStatus imageCreateWrapperGamut(ref Image gam, ulong flags, VPIImage* img)
{
	VPIImageData imgData;

	VPIStatus status = fillImageData(gam, &imgData);
	if (status != VPI_SUCCESS)
	{
		return status;
	}

	return vpiImageCreateWrapper(&imgData, null, flags, img);
}

VPIStatus fillImageData(ref Image gam, VPIImageData* imgData)
{
	assert(imgData);
	if (!gam.hasData)
	{
		return VpiErrorInvalidArgument;
	}
	VPIImageFormat fmt = ToImageFormatFromGamut(gam.type);
	if (fmt == VpiImageFormatInvalid)
	{
		return VpiErrorInvalidArgument;
	}

	imgData.bufferType = VpiImageBufferHostPitchLinear;

	imgData.buffer.pitch.format = fmt;
	imgData.buffer.pitch.numPlanes = 1;
	imgData.buffer.pitch.planes[0].width = gam.width;
	imgData.buffer.pitch.planes[0].height = gam.height;
	imgData.buffer.pitch.planes[0].pitchBytes = gam.pitchInBytes;
	imgData.buffer.pitch.planes[0].data = cast(void*) gam.allPixelsAtOnce().ptr;
	imgData.buffer.pitch.planes[0].pixelType = vpiImageFormatGetPlanePixelType(fmt, 0);

	return VPI_SUCCESS;
}

VPIImageFormat ToImageFormatFromGamut(PixelType type)
{
	switch (type)
	{
	case PixelType.rgb8:
		return VpiImageFormatBGR8;
	default:
		return VpiImageFormatInvalid;
	}
}

最低限しか作っていないですが、以下のような流れです。

  1. VPIImageDataを作る。これは画像の大きさなどのプロパティと、画像データが格納されているポインタを保持する(fillImageData)。
  2. vpiImageCreateWrapper関数でVPIImageDataを元にVPIImageを作る。(imageCreateWrapperGamut。ポインタは外部から与えられることもあるからWrapperを作るのかなと想像)

これで画像の入出力を含む、VPIの呼び出しができるようになりました。D言語でVPIを触りたい時に活用できると思います。

(ちなみに、formatVpiImageFormatRGB8 を入れたら何か動かなかったです。何故だろう)

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?