jPC에는 CSI(Camera Serial Interface) 포트가 내장되어 있어 영상 처리 및 비전 시스템이 필요한 응용 프로그램에 사용할 수 있습니다.
jPC 관리 프로그램에는 이러한 활용 예제 중 하나로 QR 코드 리더 기능이 포함되어 있습니다.
CSI 포트는 기본적으로 imx708 이미지 센서용으로 설정되어 있으며, Raspberry Pi Camera Module 3로 동작을 확인하였습니다.
다른 이미지 센서를 사용하는 카메라도 지원 가능할 수 있으나, 현재는 테스트되지 않았습니다.
카메라 모듈을 CSI 포트에 주의해서 연결하십시오.
카메라 영상을 응용 프로그램에서 사용하려면 GStreamer를 사용할 수 있습니다.
다음 명령은 로컬 디스플레이에 실시간 카메라 영상을 표시합니다.
gst-launch-1.0 libcamerasrc ! video/x-raw,width=800,height=480,framerate=30/1,format=NV12 \ ! videoconvert ! queue ! fpsdisplaysink video-sink=waylandsink
다음 예제는 ZBar 라이브러리를 사용하여 GStreamer에서 수신한 카메라 영상을 QR 코드로 분석하는 프로그램입니다.
사용자 인터페이스는 GirCore를 이용하여 작성되었습니다.
#: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<byte[]> _previewChannel = Channel.CreateUnbounded<byte[]>(); static Channel<byte[]> _scanChannel = Channel.CreateUnbounded<byte[]>(); 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<byte[]>(); _scanChannel = Channel.CreateUnbounded<byte[]>(); 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<byte> 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); }