====== 카메라 연결 ======
jPC에는 CSI(Camera Serial Interface) 포트가 내장되어 있어 영상 처리 및 비전 시스템이 필요한 응용 프로그램에 사용할 수 있습니다.
jPC 관리 프로그램에는 이러한 활용 예제 중 하나로 QR 코드 리더 기능이 포함되어 있습니다.
CSI 포트는 기본적으로 **imx708 이미지 센서**용으로 설정되어 있으며, [[https://www.raspberrypi.com/products/camera-module-3/|Raspberry Pi Camera Module 3]]로 동작을 확인하였습니다.
다른 이미지 센서를 사용하는 카메라도 지원 가능할 수 있으나, 현재는 테스트되지 않았습니다.
카메라 모듈을 CSI 포트에 **주의해서 연결하십시오.**
===== GStreamer로 카메라 테스트하기 =====
카메라 영상을 응용 프로그램에서 사용하려면 [[https://gstreamer.freedesktop.org/|GStreamer]]를 사용할 수 있습니다.
다음 명령은 로컬 디스플레이에 실시간 카메라 영상을 표시합니다.
gst-launch-1.0 libcamerasrc ! video/x-raw,width=800,height=480,framerate=30/1,format=NV12 \
! videoconvert ! queue ! fpsdisplaysink video-sink=waylandsink
===== QR 코드 스캔 예제 =====
다음 예제는 [[https://github.com/mchehab/zbar|ZBar]] 라이브러리를 사용하여 GStreamer에서 수신한 카메라 영상을 QR 코드로 분석하는 프로그램입니다.
사용자 인터페이스는 [[https://gircore.github.io/|GirCore]]를 이용하여 작성되었습니다.
{{ https://downloads.comfiletech.com/jPC/videos/jpc-qrcode-demo.mp4?476x424 }}
++++[소스 코드]|
#:package GirCore.GLib-2.0@0.6.3
#:package GirCore.GdkPixbuf-2.0@0.6.3
#:package GirCore.Gtk-4.0@0.6.3
using GdkPixbuf;
using Gio;
using Gtk;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Channels;
using Task = System.Threading.Tasks.Task;
public class Program
{
const int Width = 480;
const int Height = 320;
const int FrameSize = Width * Height * 3;
static IntPtr _scanner;
static Channel _previewChannel = Channel.CreateUnbounded();
static Channel _scanChannel = Channel.CreateUnbounded();
public static int Main(string[] args)
{
var app = Gtk.Application.New("org.example.QrDemo", ApplicationFlags.FlagsNone);
app.OnActivate += (gioApp, _) => CreateGUI((Gtk.Application)gioApp);
var status = app.RunWithSynchronizationContext(args);
return status;
}
static void CreateGUI(Gtk.Application app)
{
var window = ApplicationWindow.New(app);
window.Title = "GTK + ZBar QR Demo";
window.SetDefaultSize(Width, Height + 80);
window.SetResizable(false);
var vbox = Box.New(Orientation.Vertical, 4);
var picture = Picture.New();
picture.SetSizeRequest(Width, Height);
picture.SetHalign(Align.Center);
var label = Label.New("Waiting for QR...");
vbox.Append(picture);
vbox.Append(label);
window.Child = vbox;
window.Present();
_scanner = zbar_image_scanner_create();
StreamFramesAndScan(picture, label);
}
private static void StreamFramesAndScan(Picture picture, Label label)
{
var psi = new ProcessStartInfo
{
FileName = "gst-launch-1.0",
Arguments =
"libcamerasrc ! " +
$"video/x-raw,width={Width},height={Height},framerate=30/1,format=NV12 ! " +
"videoconvert ! " +
$"video/x-raw,format=RGB ! " +
"multipartmux boundary=frame ! " +
"fdsink fd=1",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = false,
CreateNoWindow = true
};
if (Process.Start(psi) is not Process process)
{
GLib.Functions.IdleAdd(0, () => { label.SetText("Failed to start GStreamer"); return false; });
return;
}
_previewChannel = Channel.CreateUnbounded();
_scanChannel = Channel.CreateUnbounded();
var stream = process.StandardOutput.BaseStream;
var cts = new CancellationTokenSource();
ParseFrames(stream, process, label, cts);
GeneratePreview(picture, cts);
ScanForQRCode(label, cts);
}
static void ParseFrames(Stream stream, Process process, Label label, CancellationTokenSource cts)
{
Task.Run(async () =>
{
try
{
var previewWriter = _previewChannel.Writer;
var scanWriter = _scanChannel.Writer;
while (true)
{
// Skip to frame boundary "\r\n\r\n"
while (stream.ReadByte() != '\r') { }
if (stream.ReadByte() != '\n'
|| stream.ReadByte() != '\r'
|| stream.ReadByte() != '\n')
{
continue;
}
var frame = new byte[FrameSize];
stream.ReadExactly(frame);
if (frame.Length == FrameSize)
{
await previewWriter.WriteAsync(frame, cts.Token);
await scanWriter.WriteAsync(frame, cts.Token);
}
}
}
finally
{
cts.Cancel();
_previewChannel.Writer.TryComplete();
_scanChannel.Writer.TryComplete();
}
});
}
static void GeneratePreview(Picture picture, CancellationTokenSource cts)
{
Task.Run(async () =>
{
try
{
byte[]? lastFrame = null;
await foreach (var frame in _previewChannel.Reader.ReadAllAsync(cts.Token))
{
if (ReferenceEquals(frame, lastFrame)) continue;
lastFrame = frame;
GLib.Functions.IdleAdd(0, () =>
{
try
{
var pix = Pixbuf.NewFromBytes(GLib.Bytes.New(frame), Colorspace.Rgb, false, 8, Width, Height, Width * 3);
picture.SetPixbuf(pix);
}
catch { }
return false;
});
}
}
catch (OperationCanceledException) { }
});
}
static void ScanForQRCode(Label label, CancellationTokenSource cts)
{
Task.Run(async () =>
{
try
{
byte[]? lastFrame = null;
await foreach (var frame in _scanChannel.Reader.ReadAllAsync(cts.Token))
{
if (ReferenceEquals(frame, lastFrame)) continue;
lastFrame = frame;
var text = ScanQrFromRgb(frame);
if (!string.IsNullOrEmpty(text))
GLib.Functions.IdleAdd(0, () => { label.SetText($"QR Code: {text}"); return false; });
}
}
catch (OperationCanceledException) { }
});
}
readonly static uint FourCC = 'Y' | ((uint)'8' << 8) | ((uint)'0' << 16) | ((uint)'0' << 24);
private static string? ScanQrFromRgb(ReadOnlySpan rgb)
{
// Convert RGB to Grayscale
var gray = new byte[Width * Height];
for (int i = 0; i < rgb.Length; i += 3)
{
int y = (int)((rgb[i] * 0.299) + (rgb[i + 1] * 0.587) + (rgb[i + 2] * 0.114));
gray[i / 3] = (byte)(y < 0 ? 0 : y > 255 ? 255 : y);
}
IntPtr image = zbar_image_create();
zbar_image_set_format(image, FourCC);
zbar_image_set_size(image, Width, Height);
IntPtr dataPtr = Marshal.AllocHGlobal(gray.Length);
try
{
Marshal.Copy(gray, 0, dataPtr, gray.Length);
zbar_image_set_data(image, dataPtr, (UIntPtr)gray.Length, IntPtr.Zero);
if (zbar_scan_image(_scanner, image) <= 0)
{
zbar_image_destroy(image);
return null;
}
IntPtr symbol = zbar_image_first_symbol(image);
string? result = symbol != IntPtr.Zero ? Marshal.PtrToStringAnsi(zbar_symbol_get_data(symbol)) : null;
zbar_image_destroy(image);
return result;
}
finally
{
Marshal.FreeHGlobal(dataPtr);
}
}
const string ZBarLib = "libzbar.so.0";
[DllImport(ZBarLib)]
private static extern IntPtr zbar_image_scanner_create();
[DllImport(ZBarLib)]
private static extern IntPtr zbar_image_create();
[DllImport(ZBarLib)]
private static extern void zbar_image_destroy(IntPtr image);
[DllImport(ZBarLib)]
private static extern void zbar_image_set_format(IntPtr image, uint fourcc);
[DllImport(ZBarLib)]
private static extern void zbar_image_set_size(IntPtr image, uint width, uint height);
[DllImport(ZBarLib)]
private static extern void zbar_image_set_data(IntPtr image, IntPtr data, UIntPtr length, IntPtr cleanup);
[DllImport(ZBarLib)]
private static extern int zbar_scan_image(IntPtr scanner, IntPtr image);
[DllImport(ZBarLib)]
private static extern IntPtr zbar_image_first_symbol(IntPtr image);
[DllImport(ZBarLib)]
private static extern IntPtr zbar_symbol_get_data(IntPtr symbol);
}
++++
[[..:index|JPC (임베디드 리눅스 Panel PC)]]