프로그래밍 언어/C#
C# RGB 출력, YUV420 -> RGB로 출력
EHOzlO
2020. 9. 1. 16:17
Raspberry Pi Zero W에 카메라 이미지를 PC에 전송하여 출력하는 것을 작업하고 있다.
그런데.... Raspberry Pi 에서 큰 문제가 생겼다...
자료를 찾아보니 대부분 OpenCV를 사용하여 데이터를 보내길래
OpenCV를 깔아봤는데.... 'Illegal instruction'이라고 에러가 떠서 동작이 안된다.
아무리 삽질을 해봐도 해결 방법을 못 찾았다ㅠㅠ;;
그래서 다른 방법을 찾던 중, 라즈베리 기본 설치 프로그램 중에
raspistill, raspivid, raspiyuv, raspividyuv를 사용해서 데이터를 보낼 방법을 찾았다.
라즈베리에서 카메라 실행은
raspividyuv -w 480 -h 320 -fps 5 -t 0 -l -o tcp://192.168.0.125:7777
이 명령 사용
YUV420 포멧으로 raw 데이터 보냄.
그래서 C#에서 화면에 그려줄때
480x320 사이즈로
한 프레임당 230400 bytes 이다.
0~153600 까지는 Y 값
153600~192000 까지는 U 값
192000~230400 까지 V 값
으로 구성되어 있다.
소스 ProcDrawingBuf() 함수에 38400 은 V값과 U 값 크기이다.
곳곳에 마이크 녹음이랑 사운드 출력도 있다....
--- MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Resources; // test.jpg
using System.IO.Ports; // SerialPort
using System.Windows.Threading; // DispatcherPriority
using System.Threading; // Thread
using NAudio.Wave;
using System.Drawing; // Bitmap
using System.IO;
using System.Net.Sockets; // TcpClient
using System.Net;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace YUVRGB_Ctrl
{
///
/// MainWindow.xaml에 대한 상호 작용 논리
///
public partial class MainWindow : System.Windows.Window
{
private SerialPort sp; // COM 포트
private bool portOpen; // 포트 열림 여부
private WaveIn sourceStream = null; // 녹음 핸들?
private Thread t1; // 네트워크 스트림 thread
private Thread t2; // 비트맵 thread
private string destIP; // 원격지 IP
private bool ipOpen; // 원격지 열기 닫기 여부
private TcpClient tc; // 원격지 주소
private NetworkStream stream; // 원격지 스트림
private byte[] streamBuf = new byte[230400];
private byte[] drawingBuf = new byte[230400];
public MainWindow()
{
InitializeComponent();
sp = null;
portOpen = false;
t1 = null;
ipOpen = false;
// 임시 이미지 그리기
//BitmapImage drawImg = new BitmapImage(new Uri(@"C:\Users\root\Documents\Visual Studio 2017\Projects\KLabs RGControl\KLabs RGControl\test.jpg"));
BitmapImage drawImg = new BitmapImage(new Uri("test.jpg", UriKind.Relative));
imgTest.Source = drawImg;
imgTest.Visibility = Visibility.Hidden;
// 키보드 처리 인터럽트 추가
this.KeyDown += new KeyEventHandler(FuncKeyDown);
}
// 키보드 처리
private void FuncKeyDown(object sender, KeyEventArgs e)
{
switch(e.Key)
{
case Key.Escape:
this.Close();
break;
}
}
// 메인 윈도우창 이동
private void Window_MouseLeftBtnDown(object sender, RoutedEventArgs e)
{
Cursor = Cursors.SizeAll;
this.DragMove();
}
private void Window_MouseLeftBtnUp(object sender, RoutedEventArgs e)
{
Cursor = Cursors.Arrow;
}
// exit 버튼
private void BtnExit_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
// connect 버튼
private void BtnConnect_Click(object sender, RoutedEventArgs e)
{
// 연결되어 있으면 기존 연결 끊기
if (ipOpen == true)
{
// 스트림과 TcpClient 객체 닫기
stream.Close();
tc.Close();
ipOpen = false;
}
Window1 infoWindown = new Window1();
infoWindown.OnChildDataEvent += new Window1.OnChildDataHandler(GetDataFromChild);
infoWindown.ShowDialog();
}
// 카메라, 센서 연결.
private void GetDataFromChild(string destIP, string comPort)
{
// COM 포트 열기
//OpenComport(comPort);
// IP 카메라 연결
OpenCam(destIP);
}
// COM 포트 열기
private void OpenComport(string destCom)
{
Console.WriteLine(destCom);
// COM 연결 상태 확인
if(sp == null)
{
try
{
// 1. SerialPort 클래스 객체 생성
sp = new SerialPort();
// 2. SerialPort 포트 셋팅 설정
sp.PortName = destCom;
sp.BaudRate = (int)115200;
sp.DataBits = 8;
sp.Parity = Parity.None;
sp.StopBits = StopBits.One;
sp.Handshake = Handshake.None;
sp.DataReceived += new SerialDataReceivedEventHandler(SerialPort_DataReceived);
// 3. 시리얼포트 오픈
sp.Open();
portOpen = true;
}
catch
{
MessageBox.Show("COM 포트 열기 에러");
}
}
}
// COM 포트에서 읽은 데이터 처리
public void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if ((portOpen == true) && (sp.BytesToRead >= 0))
{
try
{
var stBulider = new StringBuilder();
stBulider.Append(sp.ReadLine());
//buf = sp.ReadLine();
if (stBulider[0] == '0')
{
stBulider.Append(sp.ReadLine());
stBulider.Append(sp.ReadLine());
stBulider.Append(sp.ReadLine());
stBulider.Append(sp.ReadLine());
/*
buf += sp.ReadLine();
buf += sp.ReadLine();
buf += sp.ReadLine();
buf += sp.ReadLine();
*/
//buf = buf.Replace("\r", " ");
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate
{
labelSensorStatus.Content = stBulider.ToString();
}));
//this.labelSerial.Content = buf;
}
}
catch
{ }
}
}
// IP 카메라 연결
private void OpenCam(string destIP2)
{
destIP = destIP2;
Console.WriteLine(destIP2);
imgTest.Visibility = Visibility.Visible;
labelNetworkStatus.Visibility = Visibility.Hidden;
// 카메라 데이터 스레드 처리
/*
if(t1 == null)
{
t1.Abort();
}
*/
int aTmp;
t1 = new Thread(new ThreadStart(ProcCamData));
t1.Start();
}
// IP 카메라 데이터 처리
private void ProcCamData()
{
byte[] outbuf = new byte[15000];
int nbytes;
int total;
int i, f;
string output;
string sTime = System.DateTime.Now.ToString("mm:ss.ffffff");
TcpClient tc = new TcpClient("192.168.0.125", 7777);
NetworkStream stream = tc.GetStream();
total = 0;
i = 0;
f = 0;
while(true)
{
nbytes = stream.Read(outbuf, 0, outbuf.Length);
// 한 프레임(230400) 처리
if (total + nbytes >= 230400)
{
Array.Copy(outbuf, 0, streamBuf, total, 230400 - total);
Array.Copy(streamBuf, drawingBuf, 230400);
Array.Copy(outbuf, 230400 - total, streamBuf, 0, nbytes - (230400 - total));
if(t2 == null)
{
t2 = new Thread(new ThreadStart(ProcDrawingBuf));
t2.Start();
}
else if ((t2.ThreadState & ThreadState.Stopped) == ThreadState.Stopped)
{
t2 = new Thread(new ThreadStart(ProcDrawingBuf));
t2.Start();
}
total = nbytes - (230400 - total);
f++;
}
else
{
Array.Copy(outbuf, 0, streamBuf, total, nbytes);
total += nbytes;
}
i++;
//output = Encoding.ASCII.GetString(outbuf, 0, nbytes);
//Console.WriteLine($"{i} : {nbytes} ");// bytes: {output}");
if (nbytes == 0)
{
break;
}
}
Console.WriteLine($"total : {i} {f}");
Console.WriteLine($"Start time : {sTime}");
Console.WriteLine($"End time : {System.DateTime.Now.ToString("mm:ss.ffffff")}");
stream.Close();
tc.Close();
}
private void ProcDrawingBuf()
{
Bitmap bitmap = new Bitmap(480, 320);
BitmapData bmpData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, 480, 320), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
int k, uv;
int bY, bU, bV;
k = 0;
uv = 0;
unsafe
{
byte* ptr = (byte*)bmpData.Scan0.ToPointer();
for (int i = 0; i < 320; i++)
{
for (int j = 0; j < 480; j++)
{
if (uv >= 38400)
{
break;
}
bU = drawingBuf[320 * 480 + uv] - 128;
bV = drawingBuf[320 * 480 + 38400 + uv] - 128;
bY = drawingBuf[k];
// R
if (bV < 0)
ptr[bmpData.Stride * i + 3 * j + 2] = (byte)bY;
else
ptr[bmpData.Stride * i + 3 * j + 2] = (byte)(bY + (2.0790 * bV));
// G
if (bU < 0)
ptr[bmpData.Stride * i + 3 * j + 0] = (byte)bY;
else
ptr[bmpData.Stride * i + 3 * j + 0] = (byte)(bY + (1.4075 * bU));
// B
ptr[bmpData.Stride * i + 3 * j + 1] = (byte)(bY - (0.237633 * bV) - (0.337633 * bU));
k++;
if (j % 2 == 0)
{
uv++;
}
}
if (i % 2 == 0)
{
uv -= 240;
}
}
bitmap.UnlockBits(bmpData);
}
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate
{
imgTest.Source = BitmapToImageSource(bitmap);
}));
bitmap.Dispose();
}
public Bitmap ScreenCapture()
{
// 주화면의 크기 정보 읽기
int width = (int)SystemParameters.PrimaryScreenWidth;
int height = (int)SystemParameters.PrimaryScreenHeight;
Bitmap scrbmp = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage(scrbmp))
{
g.CopyFromScreen(0, 0, 0, 0, scrbmp.Size, CopyPixelOperation.SourceCopy);
}
// Image에 캡처한 이미지를 뿌려주기 위해 Bitmap을 BitmapImage로 변환한다.
using (MemoryStream memory = new MemoryStream())
{
scrbmp.Save(memory, ImageFormat.Bmp);
memory.Position = 0;
BitmapImage bitmapimage = new BitmapImage();
bitmapimage.BeginInit();
bitmapimage.StreamSource = memory;
bitmapimage.CacheOption = BitmapCacheOption.OnLoad;
bitmapimage.EndInit();
imgTest.Source = bitmapimage;
}
return scrbmp;
}
BitmapImage BitmapToImageSource(Bitmap bitmap)
{
using (MemoryStream memory = new MemoryStream())
{
bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);
memory.Position = 0;
BitmapImage bitmapimage = new BitmapImage();
bitmapimage.BeginInit();
bitmapimage.StreamSource = memory;
bitmapimage.CacheOption = BitmapCacheOption.OnLoad;
bitmapimage.EndInit();
return bitmapimage;
}
}
// 마이크 녹음 초기화
public void RecodeMic()
{
sourceStream = new NAudio.Wave.WaveIn();
sourceStream.BufferMilliseconds = 50;
sourceStream.DeviceNumber = 0;
sourceStream.WaveFormat = new NAudio.Wave.WaveFormat(4800, NAudio.Wave.WaveIn.GetCapabilities(0).Channels);
sourceStream.DataAvailable += new EventHandler(sourceStream_DataAvailable);
sourceStream.StartRecording();
}
// 마이크 녹음 데이터 처리
private void sourceStream_DataAvailable(object sender, NAudio.Wave.WaveInEventArgs e)
{
byte[] encoded = e.Buffer;
int i;
float multiplier = 12.0f; // Gain
for (i = 0; i < encoded.Length; i = i + 2)
{
Int16 sample = BitConverter.ToInt16(encoded, i);
sample = (Int16)(sample * multiplier);
byte[] sampleBytes = BitConverter.GetBytes(sample);
encoded[i] = sampleBytes[0];
encoded[i + 1] = sampleBytes[1];
}
//encoded.CopyTo(recordingStream2, encoded.Length);
}
}
}
--- 실행화면
- Raspberry pi zero w
- PC