반응형

장비제어 프로그램의 기본은 통신입니다. 

연결된 디바이스에 따라 통신 인터페이스도 다양합니다. 그중에서도 통신의 가장 기본은 RS232 시리얼 통신 입니다. 

일반적으로 시리얼 통신은 속도도 느리고 많은 데이타를 처리하는 방식은 아니지만 경우에 따라서는 꽤 큰 데이타도 주고 받을 때가 있습니다. 작은 데이타라면 굳이 비동기 방식을 사용하지 않아도 되겠지만 큰 용량의 데이타를 처리할 때는 비동기 방식을 사용해야 할 필요도 있어서 테스트 삼아 한번 구현해 보았습니다. 

 

비동기 프로그래밍을 인터넷에 찾아 보면 대부분 웹 상에서 데이타를 받을때를 많이 설명합니다. 

제 블로그는 아무래도 장비제어를 위주로 설명을 하니 시리얼 통신을 예로 코드를 작성하였습니다. 

 

1. 외부 연결 디바이스 클래스 : 이 코드는 가상의 디바이스를 예를 들어 코딩한 것입니다. 

public class Devices
{
    private System.IO.Ports.SerialPort commPort ;

    public Devices(System.IO.Ports.SerialPort commPort)
    {
        this.commPort = commPort;
    }
    //-------------------------------------------------------------
    public int readData(ref char[] data)    // 디바이스의 데이타를 불러오기 위한 메서드 입니다. 
    {
        commPort.Write("Send your Data.");  // 데이타 송신 요청(이건 디바이스 마다 틀립니다.) 
        Thread.Sleep(5000);                 // 수신 대기 (모든 데이타가 수신될 때까지 대기)
        var receiveCount = commPort.Read(data, 0, data.Length);  // 수신된 데이타를 버퍼로 이동 

        return receiveCount;
    }
}

위의 ReadData 함수에서 Thread.Sleep(5000) 을 한 이유는 데이타 양이 많아서 모든 데이타가 수신될때 까지 오래 기다려야 하는 상황을 만들었습니다. 

 

2. MainForm

   * Main 화면에 Button 하나와 Label 하나를 올려 놓습니다. 

   * Main Form의 Load 이벤트를 구현합니다. 

   * Button 의 click 이벤트를 구현합니다. 

   * 구현된 Main 클래스 

public partial class frmMain : Form
{
    System.IO.Ports.SerialPort Comm5 = new System.IO.Ports.SerialPort();
    Devices device = new Devices(Comm5);

    public frmMain()
    {
        InitializeComponent();
    }

    private void frmMain_Load(object sender, EventArgs e)
    {
        // 통신 시리얼 포트 정의 
        Comm5.PortName = "COM5";
        Comm5.BaudRate = 9600;
        Comm5.Parity = System.IO.Ports.Parity.None;
        Comm5.StopBits = System.IO.Ports.StopBits.One;

        Comm5.Open();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        // 버튼을 클릭하면 디바이스에서 데이타를 수신합니다. 
        char[] data = new char[20];
        label1.Text = (await Task.Run(() => device.readData(ref data))).ToString();
    }
}

* private async void button1_Click(object sender, EventArgs e)   : 버튼 클릭 이벤트에 async 를 붙여 줍니다. 

* Task.Run 을 이용하여 await 를 붙여서 데이타 수신함수를 호출 합니다. 

 

* 비동기 방식을 사용하지 않으면 Thread.Sleep(5000)  이 발생되는 동안 UI 가 멈추게 됩니다. 

* 하지만 위와 같이 비동기 방식을 사용하면 UI 가 멈추지 않고 계속 동작이 되고 수신된 데이타가 다 들어 오면 결과가 Label에 출력이 됩니다. 

반응형
반응형

마쓰루 사의 Cool Muscle 제품의 통신 제어를 위한 클래스 코드 입니다. 

시리얼을 통해 모터를 제어하는 제품 입니다. 

public class CCoolMuscle
{
    public enum MOTOR_DIRECT { CW, CCW }

    private SerialPort m_ComPort;
    private int m_uiSpeed;
    private int m_uiAcceleration;

    public CCoolMuscle(SerialPort ComPort)
    {
        m_ComPort = ComPort;
        Timeout = 60;
    }

    public int Speed
    {
        set
        {
            m_uiSpeed = value;
            string message = string.Format("S.1={0}\r", m_uiSpeed);
            m_ComPort.Write(message);
        }
    }

    public int Acceleration
    {
        set
        {
            m_uiAcceleration = value;
            string message = string.Format("A.1={0}\r", m_uiAcceleration);
            m_ComPort.Write(message);
        }
    }

    public uint Timeout { set; get; } = 60;     // 60 sec

    public void A_Move(int Position)
    {
        string message = string.Format("P.1={0},^\r", Position);
        m_ComPort.Write(message);
    }

    public void R_Move(int Distance, MOTOR_DIRECT Direct)
    {
        string message;

        if (Direct == MOTOR_DIRECT.CW) message = string.Format("P+.1={0},^\r", Distance);
        else message = string.Format("P-.1={0},^\r", Distance);

        m_ComPort.Write(message);
    }

    public void Set_Zero_Position()
    {
        m_ComPort.Write("|2\r");
    }

    public int Get_Position(out int Position)
    {
        int result = 0;
        Position = 0;

        m_ComPort.Write("?96\r");
        Thread.Sleep(100);

        try
        {
            string str = m_ComPort.ReadLine(); 

            if (str.Length == 0) result = -1;
            else
            {
                Position = int.Parse(str.Remove(0, str.IndexOf('=') + 1));
            }
        }
        catch
        {
            Position = 0;
            result = -2;
        }

        return result;
    }

    public int Check_Motor_Stop()
    {
        int result;

        m_ComPort.Write("?99\r");

        try
        {
            string str = m_ComPort.ReadLine();

            if (str.Contains("Ux.1=8") == true) result = 0;    // Motor is Stop
            else result = 1;    // Motor is moving
        }
        catch
        {
            result = -1;
        }

        return result;
    }

    public void Motor_Stop()
    {
        m_ComPort.Write("]\r");
        m_ComPort.Write("]\r");
    }

    public void Move_Zero_Position()
    {
        m_ComPort.Write("|1\r");
    }

    public void Return_Origin_Position()
    {
        // 원점 복귀, 원점의 위치를 새로 갱신 
        m_ComPort.Write("|\r");
    }
}
반응형
반응형

엠에프씨코리아 업체의 Digital MFC 제품인 VIC - D 시리즈의 통신 제어를 위한 클래스 코드 입니다. 

시리얼 통신을 통해 유량을 제어하고 현재 유량값을 읽어 올수 있습니다.

 

 

public class CVIC_DSeries
{
    /*******************************************************************************
    * CLASS: CVIC_DSeries
    * Version : 1.0.0
    * MFC (Flow Controller) Program  (Manufactured by MFC Korea)
    * Programed by Sihwan Kim
    * Copyright (C) 2019 Photondays, Inc.  All rights reserved.
    * PROTOCOL   :  START, ADDRESS, COMMAND, DATA TYPE, DATA, CHECKSUM, END   (ASCII 형식) 
    * Bytes Num. :    1       2        2          2     0~64      2      1 
    *******************************************************************************/

    private SerialPort m_ComPort;
    private byte[] m_Address = new byte[2];
    private float m_MaxFlow;

    public float MaxFlow
    {
        get { return m_MaxFlow; }
    }

    public CVIC_DSeries(SerialPort ComPort, byte Address)
    {
        m_ComPort = ComPort;
        m_Address = ByteToHexAscii(Address);
    }

    private byte[] ByteToHexAscii(byte value)       // 1바이트를 2바이트 아스키로 변환 
    {
        string strHex = value.ToString("X2");
        byte[] StrByte = Encoding.UTF8.GetBytes(strHex);

        return StrByte;
    }

    private byte[] CheckSum(byte[] Data, int Count)
    {
        byte[] result = new byte[2];
        byte checksum = Data[0];

        for (int loop = 1; loop < Count; loop++)
        {
            checksum = (byte)(checksum ^ Data[loop]);
        }

        result = ByteToHexAscii(checksum);

        return result;
    }

    public bool Initialize()
    {
        bool result = true ;
        byte[] bTemp = new byte[2];
        byte[] send_buffer = new byte[] { 0x3a, m_Address[0], m_Address[1], 0x32, 0x31, 0x30, 0x30, 0x33, 0x39, 0x0d };
        byte[] Receive_Buff = new byte[50];

        bTemp = CheckSum(send_buffer, 7);
        send_buffer[7] = bTemp[0];
        send_buffer[8] = bTemp[1];

        m_ComPort.Write(send_buffer, 0, 10);
        Thread.Sleep(200);
       
        try
        {
            m_ComPort.Read(Receive_Buff, 0, 50);

            if (Receive_Buff[0] == 0x3A && Receive_Buff[2] == m_Address[1]) result = true;
            else result = false;
        }
        catch
        {
            result = false;
        }
        
        return result;
    }

    public bool Serial_Mode()
    {
        bool result = true;
        byte[] bTemp = new byte[] { 0x00, 0x00 };
        byte[] send_buffer = new byte[] { 0x3A, m_Address[0], m_Address[1], 0x35, 0x38, 0x30, 0x32, 0x30, 0x31, 0x33, 0x38, 0x0D };
        byte[] Receive_Buff = new byte[50];

        bTemp = CheckSum(send_buffer, 9);
        send_buffer[9] = bTemp[0];
        send_buffer[10] = bTemp[1];

        m_ComPort.Write(send_buffer, 0, 12);
        Thread.Sleep(50);

        try
        {
            m_ComPort.Read(Receive_Buff, 0, 50);
        }
        catch { }

        return result;
    }

    public bool Set_Flow_Point(int FlowValue)
    {
        bool result = false;
        int index = 7;
        float percentage;
        byte[] bTemp = new byte[2];
        byte[] send_buffer = new byte[] { 0x3A, m_Address[0], m_Address[1], 0x30, 0x31, 0x30, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d};
        byte[] Receive_Buff = new byte[50];

        percentage = ((float)FlowValue * 100.0f) / m_MaxFlow;          // 유량설정은 퍼센트로 한다. 
        byte[] dByte = BitConverter.GetBytes(percentage);       // IEEE754 형식의 float

        for (int loop = 3; loop > -1; loop--)
        {
            bTemp = ByteToHexAscii(dByte[loop]);
            send_buffer[index++] = bTemp[0];
            send_buffer[index++] = bTemp[1];
        }

        bTemp = CheckSum(send_buffer, 15);

        send_buffer[15] = bTemp[0];
        send_buffer[16] = bTemp[1];   

        m_ComPort.Write(send_buffer, 0, 18);
        Thread.Sleep(100);

        try
        {
            m_ComPort.Read(Receive_Buff, 0, 50);   // 라즈베리파이는 데이타가 들어오지 않으면 예외를 발생시킨다.

            if (Receive_Buff[0] == 0x3A && Receive_Buff[17] == 0x0D)
            {
                result = true;
            }
        }
        catch
        {
            result = false;
        }

        return result;
    }

    public int Read_Flow_Value(ref float flowValue)
    {
        int result = 0;
        byte[] send_buffer = new byte[] { 0x3A, m_Address[0], m_Address[1], 0x30, 0x33, 0x30, 0x30, 0x33, 0x39, 0x0D };
        byte[] bcheck = CheckSum(send_buffer, 7);
        send_buffer[7] = bcheck[0];
        send_buffer[8] = bcheck[1];
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

        m_ComPort.Write(send_buffer, 0, send_buffer.Count());
        sw.Start();

        try
        {
            while (true)
            {
                Thread.Sleep(10);

                if (m_ComPort.BytesToRead > 0)
                {
                    Thread.Sleep(10);
                    byte[] Receive_Buff = new byte[m_ComPort.BytesToRead + 2];

                    m_ComPort.Read(Receive_Buff, 0, m_ComPort.BytesToRead);

                    if (Receive_Buff[0] == 0x3A && Receive_Buff[17] == 0x0D)
                    {
                        byte[] bTemp = new byte[8];
                        Array.Copy(Receive_Buff, 7, bTemp, 0, 8);

                        try
                        {
                            var percentage = FromHexString(Encoding.Default.GetString(bTemp));      // IEEE754 -> float 
                            flowValue = (percentage * m_MaxFlow) / 100.0f;

                            if (flowValue <= 0) flowValue = 0f;
                        }
                        catch
                        {
                            flowValue = 0f;
                            result = -2;
                        }
                    }

                    break;
                }

                var time = (int)(sw.ElapsedMilliseconds / 1000);
                if (time > 5)           //  Timeout 5sec 시간 경과 
                {
                    result = -1;
                    break;  
                }
            }
        }
        finally 
        { 
            sw.Stop(); 
        }

        return result;
    }

    public int Get_Max_Flow()
    {
        int result = 0;

        byte[] bTemp = new byte[8];
        byte[] send_buffer = new byte[] { 0x3A, m_Address[0], m_Address[1], 0x33, 0x33, 0x30, 0x30, 0x33, 0x39, 0x0D };
        byte[] Receive_Buff = new byte[50];                                     

        byte[] bcheck = CheckSum(send_buffer, 7);
        send_buffer[7] = bcheck[0];
        send_buffer[8] = bcheck[1];

        m_ComPort.Write(send_buffer, 0, send_buffer.Count());
        Thread.Sleep(100);

        try
        {
            m_ComPort.Read(Receive_Buff, 0, 50);

            if (Receive_Buff[0] == 0x3A && Receive_Buff[17] == 0x0D)
            {
                Array.Copy(Receive_Buff, 7, bTemp, 0, 8);

                try
                {
                    m_MaxFlow = FromHexString(Encoding.Default.GetString(bTemp));      // IEEE754 -> float 
                }
                catch
                {
                    m_MaxFlow = -999.99f;
                    result = -2;
                }
            }
        }
        catch
        {
            m_MaxFlow = -999.99f;
            result = -1;
        }

        return result;
    }
    private float FromHexString(string s)
    {
        var i = Convert.ToInt32(s, 16);
        var bytes = BitConverter.GetBytes(i);
        return BitConverter.ToSingle(bytes, 0);
    }
}
반응형
반응형

지난 글에서 영상의 일부 영역을 이미지로 저장하는 방법에 대해 기술하였습니다.

이번에는 저장된 이미지를 패턴으로 등록하여 화면상에서 동일한 패턴을 패턴매칭을 통해 찾는 코드를 추가 합니다. 

 

1. 지난 4회에 걸친 카메라 제어 및 테스트에 대한 전체 코드를 올려 놓습니다.

CameraControl.zip
0.46MB

 

2. 아래 2개의 멤버 함수를 추가합니다. 

   * 첫번째 메서드는 내부 메모리에 등록되어 있는 패턴을 이용하는 함수 입니다. 

   * 두번째 메서드는 저장된 패턴 파일을 불러와서 매칭에 사용하는 함수 입니다. 

public class CameraThread
{
    private Mat patternFrame = new();

    public bool PatternSearch(int MatchRate, ref OpenCvSharp.Point offset, ref double SearchRate)
    {
        bool result = false;
        double rate = (double)(MatchRate / 100);   // matching rate 0 ~ 1 

        using (Mat matchResult = new())
        {
            Cv2.MatchTemplate(sourceFrame, patternFrame, matchResult, TemplateMatchModes.CCoeffNormed);   // 이미지 템플릿 매치
            Cv2.MinMaxLoc(matchResult, out double minval, out double maxval, out OpenCvSharp.Point minloc, out OpenCvSharp.Point maxloc);

            if (maxval >= rate)  // 타겟 이미지랑 유사 정도 1에 가까울 수록 같음
            {
                offset = maxloc;             // 패턴을 찾은 위치값
                SearchRate = maxval * 100;   // 패턴 매칭율

                result = true;
            }
        }

        return result;
    }
    //-------------------------------------------------------------------------------------
    public bool PatternSearch(int MatchRate, string patternFile, ref OpenCvSharp.Point offset, ref double SearchRate)
    {
        bool result = false;
        double rate = (double)(MatchRate / 100);   // matching rate 0 ~ 1 

        using (Mat pattern = new (patternFile))  // Pattern 파일을 읽는다. 
        using (Mat matchResult = new ())
        {
            Cv2.MatchTemplate(sourceFrame, pattern, matchResult, TemplateMatchModes.CCoeffNormed);   // 이미지 템플릿 매치
            Cv2.MinMaxLoc(matchResult, out double minval, out double maxval, out OpenCvSharp.Point minloc, out OpenCvSharp.Point maxloc);

            if (maxval >= rate)  // 타겟 이미지랑 유사 정도 1에 가까울 수록 같음
            {
                offset = maxloc;   // 패턴을 찾은 위치값
                SearchRate = maxval * 100 ;  // 패턴 매칭율

                result = true;
            }
        }

        return result;
    }
    //-------------------------------------------------------------------------------------
}

 

반응형
반응형

OpenCVSharp 3번째로 영상을 캡쳐하는 방법을 설명합니다. 

 

1. 지난 포스팅에서 Line Generator 를 그리는 방법을 설명을 했는데 Line Generator 를 이용하여 이미지의 특정 영역을 설정하고 설정된 영역을 이미지로 저장하는 코드를 추가 합니다. 

 

2. 화면 전체를 캡쳐하고 싶으시면 화면 전체를 영역을 설정하시면 됩니다. 

 

3. 아래 2개 함수를 지난번 클래스에 추가 합니다. 

public class CameraThread
{
    public bool PatternCapture(String Filename, Rect rect)
    {
        bool result;

        var cutChangeImage = sourceFrame.Clone(rect);     // cut Image                                                     

        result = Cv2.ImWrite(Filename, cutChangeImage);      // save Image

        return result;
    }
    //-------------------------------------------------------------------------------------
    public bool PatternCapture(ref Bitmap image, Rect rect)
    {
        bool result = false ;
        var cutChangeImage = sourceFrame.Clone(rect);     // cut Image                                                     

        image = BitmapConverter.ToBitmap(cutChangeImage);

        if (image != null) result = true;

        return result;
    }
}

 

* public bool PatternCapture(String Filename, Rect rect) : 설정된 영역을 파일로 저장하는 함수 

* public bool PatternCapture(ref Bitmap image, Rect rect) : 설정된 영역을 이미지 파라메터로 넘기는 함수 

 

반응형
반응형

지난 포스팅에 이어 이번에는 출력되는 영상에 Line을 그려주는 코드를 넣어보도록 하겠습니다. 

 

1. Main 화면에 지난번에 이어  HScrollBar 2개(min:0, max:640), VScrollBar 2개(min:0, max:480) 를 올립니다. 

   * 각 ScrollBar 의 Maximum 크기는 화면의 크기와 동일하게 설정합니다. 

 

2. TextBox 4 개의 올려 줍니다. 각각의 Name 속성을 다음과 같이 설정합니다. 

   * textOffsetX, textOffsetY, textSizeX, textSizeY

 

3. CheckBox 를 1개 올려 줍니다. Name 속성은 checkLineDisplay 로 설정합니다. 

 

4. 지난 포스팅에 기술한 카메라 제어 클래스에 아래의 코드를 추가해 줍니다. 

public class CameraThread
{

    public int displayLineX1 { get; set; }
    public int displayLineX2 { get; set; }
    public int displayLineY1 { get; set; }
    public int displayLineY2 { get; set; }
    public bool displayLine { get; set; } = true;
//-------------------------------------------------------------------------------------
    public void Run()
    {
        capture.Open(cameraNumber);

        using (Mat displayFrame = new())
        {
            while (isCameraRunning)
            {
                capture.Read(sourceFrame);

                if (!sourceFrame.Empty())
                {
                    Cv2.CopyTo(sourceFrame, displayFrame);

                    if (displayLine == true) DrawLine(displayFrame);
                    UpdateFrame(BitmapConverter.ToBitmap(displayFrame));
                }
            }
        }

        capture.Release();
        GC.Collect();
    }
    //-------------------------------------------------------------------------------------
    private void DrawLine(Mat displayFrame)
    {
        Cv2.Line(displayFrame, 0, displayLineY1, displaySize.X, displayLineY1, Scalar.Yellow, 1, LineTypes.AntiAlias);
        Cv2.Line(displayFrame, 0, displayLineY2, displaySize.X, displayLineY2, Scalar.Yellow, 1, LineTypes.AntiAlias);
        Cv2.Line(displayFrame, displayLineX1, 0, displayLineX1, displaySize.Y, Scalar.Yellow, 1, LineTypes.AntiAlias);
        Cv2.Line(displayFrame, displayLineX2, 0, displayLineX2, displaySize.Y, Scalar.Yellow, 1, LineTypes.AntiAlias);
    }
    //-------------------------------------------------------------------------------------
}

 

5. 스크롤바 4개의 scroll 이벤트 함수로 Line_Scroll을 모두 동일하게 설정합니다. 

 

6. Main 화면의 코드를 추가 합니다. 

private void checkLineDisplay_CheckedChanged(object sender, EventArgs e)
{
    cameraThread.displayLine = checkLineDisplay.Checked;
}
//--------------------------------------------------------------------------------
private void Line_Scroll(object sender, ScrollEventArgs e)
{
    if (sender is ScrollBar scroll)
    {
        var Size   = new System.Drawing.Point();
        var Offset = new System.Drawing.Point();

        switch (int.Parse(scroll.Tag.ToString()))
        {
            case 0: cameraThread.displayLineX1 = scroll.Value; break;
            case 1: cameraThread.displayLineX2 = scroll.Value; break;
            case 2: cameraThread.displayLineY1 = scroll.Value; break;
            case 3: cameraThread.displayLineY2 = scroll.Value; break;
        }

        Size.X = Math.Abs(cameraThread.displayLineX2 - cameraThread.displayLineX1);
        Size.Y = Math.Abs(cameraThread.displayLineY2 - cameraThread.displayLineY1);

        Func<int, int, int> Compare = (int x, int y) => { if (x < y) return x; else return y; };

        Offset.X = Compare(cameraThread.displayLineX1, cameraThread.displayLineX2);
        Offset.Y = Compare(cameraThread.displayLineY1, cameraThread.displayLineY2);

        textOffsetX.Text = Offset.X.ToString();
        textOffsetY.Text = Offset.Y.ToString();

        textSizeX.Text = Size.X.ToString();
        textSizeY.Text = Size.Y.ToString(); 
    }
}
반응형
반응형

장비제어 프로그램에서 빠질수 없는 부분이 카메라 제어 입니다. 기존에 C++ Builder를 이용하여 장비제어를 했을때도 OpenCV를 사용했지만 OpenCV 버젼이 올라가고 클래스 기반으로 라이브러리가 변경되면서 C++ Builder의 구조적인 문제로 인해 더이상 상위 버젼의 OpenCV 릃 사용할 수가 없었고, C#으로 개발툴을 변경하면서 OpenCVSharp을 적용해 보았습니다. 이번장에서는 기본적으로 카메라 영상을 화면에 출력하는 방법에 대해 구현합니다. 

 

카메라 영상 제어를 위한 클래스를 구현했고 Thread 로 영상을 화면에 출력하도록 했습니다.

 

1. Winform 프로젝트를 하나 만듭니다. 

2. nuget 에서 openCVSharp 관련 컨트롤을 등록시켜 줍니다.

OpenCVSharp 사용을 위한 컨트롤 등록
OpenCVSharp 사용을 위한 컨트롤 등록

3. MainForm 에 pictureBox 1개(640 x 480)와 Button 2개(start, stop) 을 올려 줍니다.

   *  pictureBox의 크기는 카메라의 해상도에 맞추어 조정하시면 됩니다. 

 

4. 아래는 구현된 카메라 클래스 입니다. 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace CameraControl
{
    public class CameraThread
    {
        private delegate void SafeCallDelegate(Bitmap frame);

        readonly private VideoCapture capture = new VideoCapture();
        readonly private Mat sourceFrame = new();
        private OpenCvSharp.Point displaySize;

        public int cameraNumber { get; set; } = 0;
        public bool isCameraRunning { get; set; } = false;

        public PictureBox display;
        public PictureBox Display
        {
            set
            {
                this.display = value;
                this.displaySize.X = display.Width;
                this.displaySize.Y = display.Height;
            }
        }
        //-------------------------------------------------------------------------------------
        public CameraThread(PictureBox pictureBox, int cameraNumber)
        {
            this.display = pictureBox;

            displaySize.X = pictureBox.Width;
            displaySize.Y = pictureBox.Height;

            capture.FrameWidth = displaySize.X;
            capture.FrameHeight = displaySize.Y;

            this.cameraNumber = cameraNumber;
        }
        //-------------------------------------------------------------------------------------
        public Int32 Initialize()
        {
            Int32 result = 1;
            capture.Open(cameraNumber);

            if (capture.IsOpened()) result = 0;

            return result;
        }
        //-------------------------------------------------------------------------------------
        public void Run()
        {
            capture.Open(cameraNumber);    // 연결된 카메라를 Open 합니다. 

            using (Mat displayFrame = new())  // 영상 출력을 위한 Mat 생성 
            {
                while (isCameraRunning)
                {
                    capture.Read(sourceFrame);  // 카메라에서 영상 Source Frame 으로 읽어옵니다.

                    if (!sourceFrame.Empty())
                    {
                        Cv2.CopyTo(sourceFrame, displayFrame);   // source frame의 영상을 출력 영상 frame 롤 복사합니다. 
                        UpdateFrame(BitmapConverter.ToBitmap(displayFrame));  // 출력영상을 화면에 보여 줍니다.
                    }
                }
            }

            capture.Release();
            GC.Collect();
        }
        //-------------------------------------------------------------------------------------
        private void UpdateFrame(Bitmap frame)
        {
            if (display != null)
            {
                if (display.InvokeRequired)
                {
                    var d = new SafeCallDelegate(UpdateFrame);
                    display.Invoke(d, new object[] { frame });
                }
                else
                {
                    display.Image = frame;
                }
            }
        }
        //-------------------------------------------------------------------------------------
   }
}

 

 * 영상제어 클래스에서 source frame 와 display frame 을 분리한 이유는 source frame 에서 영상에 대한 제어를 활용하고 그 결과를 display frame를 통해 출력하기 위해 별도로 분리했습니다. 

 

5. 위에 구현된 클래스를 이용하여 메인 화면에 영상을 출력합니다.

using OpenCvSharp;
using System;
using System.Windows.Forms;

namespace CameraControl
{
    public partial class Form1 : Form
    {
        private Thread thread;
        readonly private CameraThread cameraThread;

        public Form1()
        {
            InitializeComponent();

            cameraThread = new CameraThread(pictureBox1, 0);  // 0번 카메라 출력 
            cameraThread.Initialize();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            cameraThread.isCameraRunning = true;

            thread = new Thread(new ThreadStart(cameraThread.Run));
            thread.Start();
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            if (thread.IsAlive)
            {
                cameraThread.isCameraRunning = false;
            }
        }
    }
}

 

반응형
반응형

Ocean Optics 사의 스펙트로메타 사용을 위해 제공되는 OmniDriver 를 C#으로 제어하기 위한 코드 입니다. OmniDriver 의 경우 JAVA 로 개발되었고 타 언어에서는 이것을 사용하기 위해 Wrapper 용 DLL 을 별도로 제공합니다. 

 

1. OmniDriver 를 설치하시면 C:\Program Files\Ocean Optics\OmniDriverSPAM\OOI_HOME 폴더에 NETOmniDriver-NET40.DLL 파일이 있습니다. 이 파일을 참조로 추가하신 후 사용하면 됩니다. 

   * 참고로 OmniDriver 설치 후 제공되는 매뉴얼에는 NETOmniDriver.DLL 이라고 명시되어 있는데 이 파일이 아니고 위에 써 있는 파일입니다.

   * 또한 참조 추가시에 프로젝트 참조로 하시지 마시고 COM 참조에서 찾아 보기를 통해 해당 파일을 찾아서 등록하셔야 합니다. 

Omni Driver 참조 추가 방법
Omni Driver 참조 추가 방법

2. 위에 처럼 등록하시면 솔루션 탐색기에서는 아래와 같이 종속성의 어셈블리안에 등록이 됩니다. 이렇게 참조추가가 안돼고 다른 위치에 등록이 되면 사용이 안돼더라고요.

OmniDriver 참조 추가
OmniDriver 참조 추가

3. 아래는 OmniDriver 를 제어하기 위한 클래스 코드 입니다. 

   * 이 클래스의 경우 한개의 SpectroMeter 를 사용하는 전제로 코드를 만들었습니다.

   * 여러개의 SpectroMeter 를 사용할 경우 코드를 수정해서 사용하시든 아니면 아래 클래스를 사용하고 객체를

      SpectroMeter 갯수 만큼 만들어 사용하시면 되겠습니다. 

using OmniDriver;

namespace OmniDriver
{
    public struct CalData
    {
        public double IntegTime = 1000;     // unit = [usec]
        public double[] dark;
        public double[] Reference;

        public CalData(int PixelCount)
        {
            dark = new double[PixelCount];
            Reference = new double[PixelCount];
        }
    }
    //-------------------------------------------------------------------------------------------------------
    public struct SpectroMeterParams
    {
        public int IntegrationTime { get; set; } = 1000;    // unit = [usec]
        public int BoxcarWidth { get; set; } = 3;
        public int ScansToAverage { get; set; } = 2;
        public int CorrectForElectricalDark { get; set; } = 0;   // 전기적 노이즈 제거 여부 : 0 or 1 
        public SpectroMeterParams() { }
        public SpectroMeterParams(int IntegTime, int BoxcarWidth, int ScanToAverage, int CorrectForElectricalDark)
        {
            this.IntegrationTime = IntegTime;
            this.BoxcarWidth = BoxcarWidth;
            this.ScansToAverage = ScanToAverage;
            this.CorrectForElectricalDark = CorrectForElectricalDark;
        }
    }
    //-------------------------------------------------------------------------------------------------------
    public class CSpectroMeter
    {
        private readonly OmniDriver.NETWrapper Wrapper;
        private readonly int Index;
        private CalData CalibrationData;

        private int _PixelCount;
        public int PixelCount { get { return _PixelCount; } }
        public string Serial { get { return Wrapper.getSerialNumber(Index); } }
        public int IntegrationTime
        {
            set { Wrapper.setIntegrationTime(Index, value); }
            get { return Wrapper.getIntegrationTime(Index); }
        }
        public int BoxcarWidth
        {
            set { Wrapper.setBoxcarWidth(Index, value); }
            get { return Wrapper.getBoxcarWidth(Index); }
        }
        public int ScansToAverage
        {
            set { Wrapper.setScansToAverage(Index, value); }
            get { return Wrapper.getScansToAverage(Index); }
        }
        public int CorrectForElectricalDark
        {
            set { Wrapper.setCorrectForElectricalDark(Index, value); }
            get { return Wrapper.getCorrectForElectricalDark(Index); }
        }
        public CSpectroMeter() 
        {
            Wrapper = new OmniDriver.NETWrapper();
            Index = 0;
        }
        //---------------------------------------------------------------------------------------------------
        public CSpectroMeter(OmniDriver.NETWrapper wrapper, int index)
        {
            this.Wrapper = wrapper;
            this.Index = index;
        }
        //---------------------------------------------------------------------------------------------------
        public string getSerialNumber()
        {
            var serial = Wrapper.getSerialNumber(Index);
            return serial;
        }
        //---------------------------------------------------------------------------------------------------
        public bool connect()
        {
            bool result = false;

            var count = Wrapper.openAllSpectrometers();
            if (count >= 0)
            {
                result = true;
                _PixelCount = Wrapper.getNumberOfDarkPixels(Index);
                CalibrationData = new CalData(_PixelCount);
            }

            return result;
        }
        //---------------------------------------------------------------------------------------------------
        public void getSpectrum(ref double[] Wavelength, ref double[] Spectrum)
        {
            Spectrum = Wrapper.getSpectrum(Index);        // 스펙트로메타에서 측정된 RAW Intensity 값 (Y 축)
            Wavelength = Wrapper.getWavelengths(Index);   // 스펙트로메타의 파장 데이타 (X 축)
        }
        //---------------------------------------------------------------------------------------------------
        public void getIrradianceData(ref double[] Wavelength, ref double[] Irradiance)
        {
            getSpectrum(ref Wavelength, ref Irradiance);

            for(int loop=0; loop < Irradiance.Length; loop++) 
            {
                if (Params.CorrectForElectricalDark == 0) Irradiance[loop] = Irradiance[loop] - CalibrationData.dark[loop];

                Irradiance[loop] = (Irradiance[loop] * CalibrationData.Reference[loop]) * (Params.IntegrationTime / CalibrationData.IntegTime);
            }
        }
        //---------------------------------------------------------------------------------------------------
        public void setSpectrometerParams(SpectroMeterParams Params)
        {
            Wrapper.setIntegrationTime(Index, Params.IntegrationTime);
            Wrapper.setScansToAverage(Index, Params.ScansToAverage);
            Wrapper.setBoxcarWidth(Index, Params.BoxcarWidth);
            Wrapper.setCorrectForElectricalDark(Index, Params.CorrectForElectricalDark);
        }
        //---------------------------------------------------------------------------------------------------
        public void close()
        {
            Wrapper.closeSpectrometer(Index);
        }
        //---------------------------------------------------------------------------------------------------
        public virtual bool readCalibrationFile(string filename) 
        {
            bool result = true;

            if (!File.Exists(filename)) result = false;
            else
            {
                using (StreamReader sr = new StreamReader(filename))
                {
                    int index = 0;
                    string[] split;
                    string line;

                    line = sr.ReadLine();  // Integration Time of reference sample
                    if (line is not null)
                    {
                        split = line.Split(',');
                        CalibrationData.IntegTime = double.Parse(split[1]);
                    }

                    sr.ReadLine();  // Header

                    while ((line = sr.ReadLine()) != null)
                    {
                        split = line.Split(',');
                        CalibrationData.dark[index] = double.Parse(split[1]);
                        CalibrationData.Reference[index] = double.Parse(split[2]);

                        index++;
                    }
                }
            }

            return result;
        }
        //---------------------------------------------------------------------------------------------------
    }
}
반응형

+ Recent posts