ScottPlot (Growing Data - #2)

Software/C#|2023. 2. 16. 13:23
반응형

지난 포스팅에서 ScottPlot 의 Growing Data 사용 방법에 대해 기술했습니다.  그러나 기본적으로 제공하는 사용방법이 불편하기도 할 뿐더러 코드도 길고 메모리 Overflow 의 위험성도 있어서 사용 방법을 변경해서 기술해 보았습니다. 

 

* 데이타 버퍼로 배열을 사용하지 않고 List 를 사용하여 메모리 누수를 예방했습니다. 

* 지난번의 문제점으로 지적된 X 축의 증가 Step을 조정할 수 없는 단점을 제거하였습니다.

* 코드도 훨씬 간결해 졌습니다. 

 

1. 데이타저장을 위한 클래스를 하나 구현해 놓습니다. 지난번과는 다르게 데이타 갯수를 알려주는 Index 가 없어졌고 X축의 값을 저장하기 위한 버퍼가 추가 되었습니다. 

public class MeasuredData
{
    public readonly List<double> XAxis;    // X축 데이타 저장 버퍼
    public readonly List<double> YAxis;     // Y축 데이타 저장 버퍼

    public MeasuredData()
    {
        XAxis = new List<double>();
        YAxis = new List<double>();
    }

    public void Reset()
    {
        XAxis.Clear();
        YAxis.Clear();
    }
}

 

2. 타이머를 이용하여 랜덤으로 데이타를 생성하게 하였습니다. 

  * 기본 기능과 다른 점은 X축의 간격을 임의로 넣을 수 있다는 점 입니다. 

  * 코드량도 작아지고 사용도 편리해 졌습니다. 

public partial class Form1 : Form
{
    private readonly  MeasuredData measuredData = new MeasuredData();
    private Random random = new Random();
    private double XValue = 0;      // X 축의 값을 임의로 만들기 위해 임시로 만든 변수 입니다. (실제로는 측정 상황에서의 값이 되겠네요)

    public Form1()     //  지난번과 다르게 생성자에서 특별히 구현해 줄게 없습니다. 
    {
        InitializeComponent();     
    }
    //---------------------------------------------------------------------------------------------------
    private void timer1_Tick(object sender, EventArgs e)
    {
        formsPlot1.Plot.Clear();    // graph 출력을 클리어 합니다. 

        measuredData.XAxis.Add(XValue);                              // X 축 데이타 입력
        measuredData.YAxis.Add(random.NextDouble());       // Y 축 데이타 입력  

        formsPlot1.Plot.AddScatterLines(measuredData.XAxis.ToArray(), measuredData.YAxis.ToArray()); // List 를 Array 로 변경하여 그래프 출력

        formsPlot1.Render();    // 그래프 출력 화면 갱신

        XValue += 0.3;    
    }

    private void button1_Click(object sender, EventArgs e)
    {
        measuredData.Reset();      // 그래프 데이타 초기화

        timer1.Enabled = true;    // 타이머 시작 
    }
    //---------------------------------------------------------------------------------------------------
}

 

3. 출력 결과 화면 입니다. 

Growing Data 출력결과 (X축 추가)
Growing Data 출력결과 (X축 추가)

그런데 매번 그래프를 갱신하면서 그리는 것이라 속도면에서는 어떨지 잘모르겠네요.. 그런데 ScottPlot 가 그리는 속도가 꽤 빠르긴 합니다. 크게 속도를 요하는 것이 아니라면 이 방법도 괜찮아 보입니다. 

반응형

댓글()

ScottPlot (Growing Data - #1)

Software/C#|2023. 2. 16. 11:40
반응형

C# 으로 개발툴을 변경하면서 고민중에 하나가 자주 사용하는 차트 컴포넌트를 어떻게 할 것인가 였는데 ScottPlot 컴포넌트를 발견하고는 유레카를 외쳤습니다.. 무료인데다 기능도 아주 뛰어나고 사용성에 있어서도 만족사용 하게 되었습니다.. 무엇보다도 Multi Axis 을 많이 사용하는 제 입장에서는 너무 반가운 컴포넌트 였습니다. 

하지만 딱 한가지 아쉬운 부분이 시간의 경과에 따라 데이타가 늘어나는 그래프를 그릴 경우가 많이 있는데 특히 번인장비의 경우 시간에 따른 샘플의 특성 변화를 그래프로 실시간 보여주게 되는데 이게 ScottPlot 에서는 좀 간단하지가 않습니다. 

이번 장에서는 기본적으로 ScottPlot 에서 Growing Data를 그리는 방법을 코딩합니다. ScottPlot 예제에서 나오는 방법을 조금 수정해서 코딩하였습니다. 

 

 

1. 먼제 아래와 같이 측정데이타를 저장할 Class를 하나 만들어 줍니다.

public class MeasuredData
{
    public readonly double[] YAxis;    // 실제 측정데이타가 저장될 배열
    public int index { get; set; } = 0;    // 측정된 데이타의 갯수에 대한 index

    public MeasuredData()
    {
        YAxis = new double[10000];  // 배열의 갯수는 측정될 데이타의 최대 갯수보다 크게 설정 합니다. ( 이부분이 좀 문제)
    }
}

 

2. 타이머를 이용하여 주기적으로 데이타가 랜덤으로 생성되도록 코드를 작성하여 테스트 했습니다.

public partial class Form1 : Form
{
    private readonly  MeasuredData measuredData = new MeasuredData();
    private Random random = new Random();
    private readonly ScottPlot.Plottable.SignalPlot SignalPlot;  // 출력 데이타의 시그날 위치를 제어하게 됩니다. 
    //---------------------------------------------------------------------------------------------------
    public Form1()
    {
        InitializeComponent();
        SignalPlot = formsPlot1.Plot.AddSignal(measuredData.YAxis);  // 실제 화면에 표시될 시그날 정보와 생성된 시스날을 맵핑합니다. 
        SignalPlot.MaxRenderIndex = 0;                                                  // 출력될 시그날의 초기 위치를 설정(이걸 안하면 10000개의 데이타가 다 보입니다.)
        formsPlot1.Plot.SetAxisLimits(0, 10, -2, 2);     // 그래프 출력 Limit 설정  

        formsPlot1.Refresh();   // 이 부분을 안하면 ScottPlot 그래프 화면에 오류메시지가 보입니다. 
    }
   //---------------------------------------------------------------------------------------------------
    private void timer1_Tick(object sender, EventArgs e)      // 타이머 이벤트( 1초에 한번씩 데이타 생성하도록 했습니다.)
    {
        double currentRightEdge = formsPlot1.Plot.GetAxisLimits().XMax;   // 현재 그래프의 최대 X축의 값을 가져 옵니다. 
        if (measuredData.index > currentRightEdge)           // X축의 최대값이 데이타 갯수보다 작으면
        {
            formsPlot1.Plot.SetAxisLimits(xMax: currentRightEdge + 10);    // X 축의 최대값을 측정데이타 보다 10 step 많게 설정합니다. 
        }

        measuredData.YAxis[measuredData.index] = random.NextDouble();    // 랜덤으로 만들어진 값을 측정 버퍼에 넣습니다. 
        SignalPlot.MaxRenderIndex = measuredData.index++ ;                        // 그래프 화면에서 측정된 데이타 갯수 만큼만 그려 줍니다.  

        formsPlot1.Render();                                                                               // 그래프 화면 갱신 
    }
   //---------------------------------------------------------------------------------------------------
    private void button1_Click(object sender, EventArgs e)
    {
        timer1.Enabled = true;    // 타이머 시작 
    }
}

 

3. 실제 화면 에서는 아래와 같이 출력이 됩니다. 

Growing Data 출력 결과
Growing Data 출력 결과

Growing Data 출력 결과

4. 이 Growing Data 츨력은 아쉬운 부분이 좀 있습니다.

 

  (1) 이것저것 사용이 좀 번거롭다

  (2)  X 축의 증감부분을 선택할 수 없다는 것입니다. (이 부분이  가장 아쉽네요.)  무조건 1씩 증가 입니다.

  (3) 버퍼의 크기를 미리 설정해 놓아야 하는데 이게 잘못해서 실제 데이타 보다 적게 설정해 놓으면 overflow 문제가 발생할 수 있습니다.        ScottPlot 매뉴얼에도 이 부분이 명시되어 있는데 overflow 문제에 대한 오류처리를 잘 설계하거나 또는 버퍼를 충분히 설정해야 합니다.

 

 

반응형

댓글()

C# Multi Thread를 이용한 다중 Client 접속 Server

Software/C#|2023. 2. 11. 12:44
반응형

C#에서 TCP/IP 를 이용한 서버 동작을 위한 클래스를 구현했습니다.  이 서버 클래스는 다중 클라이언트 접속을 허용하도록 제작하였고 클라이언트가 접속을 하면 Thread를 생성하여 개별 클라이언트와 통신하도록 구현되어 있습니다.

이번 구현은 클라이언트와 ehco 기능으로 구현되어 있어서 클라이언트에서 서버에 접속 후 메시지를 보내면 클라이언트로 보낸 메시지가 반송되도록  코딩되어 있습니다. 

클라이언트 메시지에 대한 동작 구현은 아래 코드에서 ehco 기능의 함수 부분을 수정하시면 됩니다. 

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Server
{
    public class ServerEventArgs : EventArgs
    {
        public string text { get; }    // 파라메터로 넘겨 줄 데이타

        public ServerEventArgs(string text)   // 생성자에서 변경된 Text 정보를 넘겨받는다.
        {
            this.text = text;
        }
    }
    //------------------------------------------------------------------------------------------------------------
    public delegate void ServerEventHandler(object sender, ServerEventArgs e);
    //------------------------------------------------------------------------------------------------------------
     public class ClientHandle
    {
        private Socket socket;
        private NetworkStream? networkstream = null;
        private StreamReader? streamReader = null;
        private StreamWriter? streamWriter = null;
        //--------------------------------------------------------------------------------------------------------
        public ClientHandle(Socket client)
        {
            this.socket = client;
            networkstream = new NetworkStream(client);

            streamReader = new StreamReader(networkstream!, Encoding.GetEncoding("utf-8"));
            streamWriter = new StreamWriter(networkstream!, Encoding.GetEncoding("utf-8")) { AutoFlush = true };
        }
        //--------------------------------------------------------------------------------------------------------
        public void Run()
        {
            string? receiveData;        // 데이타 수신이 null 이 될수 있으므로 null 참조 가능으로 선언한다. 

            while (true)
            {
                receiveData = streamReader!.ReadLine();     // NetworkStream 으로 들어오는 데이타의 끝은 CR, LF 가 필요하다. 
                                                            // CR LF 가 없으면 버퍼가 overflow 될때 까지 함수 내부에서 빠져 나오지 못한다. 
                if (receiveData is null) break;             // Client 연결이 끊어지면 null 이 리턴된다. 

                WriteClientData(receiveData);  // Echo 기능 입니다. 다은 특별한 동작이 필요하면 이코드를 삭제하고 구현하시면 됩니다. 
            }

            DisConnect();       // Client 연결과 관련된 부분을 모두 닫아 버린다. 
        }
        //--------------------------------------------------------------------------------------------------------
        public void DisConnect()
        {
            streamReader?.Close();
            streamWriter?.Close();
            networkstream?.Close();

            socket.Close();
        }
        //--------------------------------------------------------------------------------------------------------
        public void WriteClientData(string Message)
        {
            streamWriter?.WriteLine(Message);
        }
        //--------------------------------------------------------------------------------------------------------
    }
    //------------------------------------------------------------------------------------------------------------
    public class MultiClientServer
    {
        private TcpListener? server = null;
        public event ServerEventHandler? OnClientConnected = null;
        public event ServerEventHandler? OnServerClosed = null;

        public string? IpAddress { set; get; } = null;
        public int Port { set; get; } = 5000;
        //--------------------------------------------------------------------------------------------------------
        public MultiClientServer()
        {
            this.IpAddress = GetLocalIP();
            Enable();
        }
        //--------------------------------------------------------------------------------------------------------
        public MultiClientServer(int port)
        {
            this.IpAddress = GetLocalIP();
            this.Port = port;

            Enable();
        }
        //--------------------------------------------------------------------------------------------------------
        private static string GetLocalIP()
        {
            IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
            string ip = string.Empty;

            foreach (var address in host.AddressList)
            {
                if (address.AddressFamily == AddressFamily.InterNetwork)
                {
                    ip = address.ToString();
                    break;
                }
            }

            return ip;
        }
        //--------------------------------------------------------------------------------------------------------
        public bool Enable()
        {
            if (this.IpAddress == null) { return false; }
            else
            {
                IPEndPoint local = new IPEndPoint(IPAddress.Parse(IpAddress), Port);
                server = new TcpListener(local);

                return true;
            }
        }
        //--------------------------------------------------------------------------------------------------------
        public void Stop()
        {
            server?.Stop();     // server 동작을 종료한다. 
        }
        //--------------------------------------------------------------------------------------------------------
        public void Start()
        {
            if (server is not null)
            {
                server.Start();              // Server 동작             

                Thread connect = new Thread(WaitClientConnect);  // Client 접속 대기, Data 수신
                connect.Start();         
            }
        }
        //--------------------------------------------------------------------------------------------------------
        private void WaitClientConnect()
        {
            while (true)
            {
                try
                {
                    Socket? client = server?.AcceptSocket();   // Client  접속을 기다린다. 

                    if (client is not null)
                    {
                        var ipClient = ((IPEndPoint)client.RemoteEndPoint!).ToString();  // 접속된 Client의 IP 확인 

                        if (OnClientConnected is not null) OnClientConnected(this, new ServerEventArgs(ipClient));   // Client 연결에 대한 이벤트 함수 실행 

                        Thread connectedClient = new(new ThreadStart(new ClientHandle(client).Run));  // 접속된 Client 의 객체를 생성 Thread 를 사용하여 동작 수행
                        connectedClient.Start();
                    }
                }
                catch(Exception ex)         // server 기능을 stop 하게되면 예외가 발생한다. 
                {
                    if(OnServerClosed is not null)
                    {
                        OnServerClosed(this, new ServerEventArgs(ex.ToString()));  // server 기능 멈충에 대한 이벤트 발생 
                    }

                    break;
                }
            }
        }
        //--------------------------------------------------------------------------------------------------------
    }
}

 

사용방법    

public partial class Form1 : Form
{
    private MultiClientServer server = new MultiClientServer(5000) ; //로컬 IP는 자동으로 찾고 Port 5000 으로 서버 생성

    public Form1()
    {
        InitializeComponent();
        server.OnClientConnected += Server_OnClientConnected;  // 클라이언트가 접속되었을 경우 발생하는 이벤트
    }

    private void Server_OnClientConnected(object sender, ServerEventArgs e)
    {
      // 클라이언트가 연결되면 클라이언트 IP 를 화면에 보여 줍니다.
        textClientIP.Invoke(() => { textClientIP.Text = e.text; });  
    }

    private void button1_Click(object sender, EventArgs e)
    {
        server.Start();  // 서버 동작 시작 
    }
    private void button3_Click(object sender, EventArgs e)
    {
        server.Stop();  // 서버 동작 종료
    }
}
반응형

댓글()

bitArray 를 사용한 Bit Field 기능 구현 - C#

Software/C#|2022. 12. 29. 11:13
반응형

C 언어에는  공용체를 이용한 Bit Field 기능이 있어서 꽤 유용하게 사용했었는데 C#에는 Bit Field 기능이 따로 없어 C#에서 제공되는 bitArray 를 이용하여 Class로 구현해 보았습니다. 

Bit Field 의 경우 입,출력 제어를 위한 I/O Board  제어용으로  요긴하게 사용이 될수 있습니다.

class Bit_Field
{
    private uint byteCount = 0;
    private BitArray bitArray  ;
    //---------------------------------------------------------------------------
    public Bit_Field(uint ByteCount)
    {
        bitArray = new BitArray((int)ByteCount * 8);
        byteCount = ByteCount;
    }
    //---------------------------------------------------------------------------
    public byte Read(uint index)
    {
        if(index >= byteCount) throw new IndexOutOfRangeException();
        else
        {
            byte[] result = new byte[byteCount];
            bitArray.CopyTo(result, 0);
            return result[index];
        }
    }
    //---------------------------------------------------------------------------
    public void Write(uint index, byte data)
    {
        if(index >= byteCount) throw new IndexOutOfRangeException();
        else
        {
            int offset = (int)index * 8 ;

            for(int loop=0 ; loop < 8 ; loop++)
            {
                bitArray[loop + offset] = (data & 0x01) == 1 ? true : false ; 
                data >>= 1;
            }
        }
    }
    //---------------------------------------------------------------------------
    public void SetAll(bool value)
    {
        bitArray.SetAll(value) ;
    }
    //---------------------------------------------------------------------------
    public bool this[int index]
    {
        set => bitArray[index] = value;
        get => bitArray[index];
    }  
}

 

사용방법은 아래와 같이 하시면 됩니다. 

 

Bit_Field bit_Field = new Bit_Field(2);  // 2* 8 = 16bit 용 데이타 

//----- 특정 비트에 값을 넣고 바이트값 불러올 경우 ------------

bit_Field[0] = true;         // 0  비트 값을 설정 
bit_Field[1] = true;         // 1  비트 값을 설정 
bit_Field[2] = true;         // 2  비트 값을 설정 
bit_Field[15] = true;        // 15 비트 값을 설정 

byte lower  = bit_Field.Read(0);   // 하위  8비트 리드 
byte higher = bit_Field.Read(1);   // 상위  8비트 리드 

결과
lower = 0x07
higher = 0x80


//--------- 바이트값을 bit field 에 넣고 값을 비트로 확인할 경우 
bit_Field.Write(0, 0x12) ;
bit_Field.Write(0, 0x34) ;

bool ch1 = bit_Field[0] ;
bool ch2 = bit_Field[1] ;
bool ch3 = bit_Field[2] ;
bool ch16 = bit_Field[15] ;
반응형

댓글()

C#에서 Excel 제어를 위한 클래스 구현

Software/C#|2022. 12. 29. 09:56
반응형

계측장비에서 측정된 데이타를 Excel 파일로 저장하기 위한 Class 구현 

using Microsoft.Office.Interop.Excel ;    
namespace ExcelControl
{
    public class CExcelControl : IDisposable
    {
        private Microsoft.Office.Interop.Excel.Application? application = null;
        private Workbook? workBook = null;
        private Worksheet? workSheet = null;
        public bool Visible { get; set; } = false ;

        public CExcelControl(bool visible)
        {
            this.Visible = visible ;
        }

        public bool Create()
        {
            bool result = true;

            try
            {
                application = new Microsoft.Office.Interop.Excel.Application();  //Excel 프로그램 실행
                application.Visible = this.Visible ;                             //Excel 화면 띄우기 옵션
                workBook = application.Workbooks.Add();
            }
            catch (Exception)
            {
                result = false;
            }

            return result;
        }

        public void addSheet(string sheetName)
        {
            if (workBook != null)
            {
                workSheet = workBook.Worksheets.Add(After: workBook.Worksheets.Item[workBook.Worksheets.Count]);
                workSheet.Name = sheetName;
            }
        }

        public bool saveExcelSheep(string fileName)
        {
            bool result = true;

            if (workBook != null)
            {
                try
                {
                    workBook.SaveAs(fileName, XlFileFormat.xlWorkbookDefault);
                }
                catch (Exception)
                {
                    result = false;
                }
            }

            return result;
        }

        public void writeSheet(Action<Worksheet> Information, string sheetName)
        {
            if (workBook != null)
            {
                workSheet = workBook.Worksheets.Item[sheetName];
                if (workSheet != null)
                {
                    Information(workSheet);
                }
            }
        }

        public void Dispose()
        {
            workBook!.Close(true);   
            application!.Quit();

            if(workBook != null) Marshal.ReleaseComObject(workBook) ;
            if(application != null) Marshal.ReleaseComObject(application) ;
        }  
    }
}

사용방법

   

private void btnOpen_Click(object sender, EventArgs e)
{
    using(CExcelControl excel = new CExcelControl(true))  // 엑셀 객체 생성
    {
        excel.Create() ;                                  //  Excel 프로그램 실행 
        excel.writeSheet(writeSheetHeader, "Sheet1") ;    // Sheet1 에 데이타 저장 
        excel.saveExcelSheep("d:\test.xlsx") ;            // Excel 파일 저장 
    }
}

private void writeSheetHeader(Worksheet workSheet)
{
    workSheet.Cells[1, 1] = "Data1";
    workSheet.Cells[1, 2] = "Data2";
    workSheet.Cells[1, 3] = "Data3"; 
}
반응형

댓글()

OpenCVSharp을 이용한 LD Bar 의 NFP 알고리즘

Software/C#|2022. 12. 28. 22:38
반응형

고출력 레이저 다이오드 모듈에 사용되는  LD Bar의 NFP(Near Field Pattern) 특성을 측정하기 위하여 OpenCVSharp 을 이용한 클래스를 만들었습니다. 

 

1. public class EmitterInform : LD Bar 에 있는 한개의 Emitter 정보를 저장하기위한 클래스 입니다.

2. public class NFP_Process : Emitter 정보를 찾고 처리하기위한 클래스 입니다. 

  (1) LD Bar 에 있는 각각의 Emitter 정보를 찾는다. 

  (2) 각 Emitter 의 위치 좌표값 정보

  (3) 각 Emitter 의 Intensity (밝기) 정보 

3. Emitter 정보 Search 알고리즘 : 아래 이미지는 테스트 과정에서 임의로 화면에 표시한것입니다. 실제 알고리즘 적용 단계에서는 아래 이미지가 보이지는 않습니다. 

  (1) 카메라로 LD Bar의 광출력 영상을 촬영한다. 흑백 카메라를 사용하는 것이 좋으며 칼라 카메라를 사용했을 경우 영상을 흑백으로 변환하여야 합니다.

LD Bar를 카메라로 찍은 모습

  (2) Cv2.FindContours 함수를 사용하여 각각의 Emitter의 외곽 정보를 찾는다.  

  (3) 이렇게 찾은 개별 Emitter에 대해 Cv2.Moments 함수를 이용하여 Emitter의 중심점 좌표를 찾는다. 

빨간색 점이 각 Emitter의 중심좌표이다.

  (4) Contour 정보와 중심점 좌표를 이용하여 각 Emitter의 Mask 이미지를 생성한다. 

  (5) 생성된 Mask 이미지와 소스 이미지를 이용하여 각각의 Emitter에 대해 평균값을 계산하여 Intensity를 구한다. 

  (6) 저장된 List는 Emitter의 순서가 뒤죽박죽이기 때문에 위치의 X값을 기준으로 Sort를 해야 한다.

저장된 NFP 정보

4. 구현된 클래스

public class EmitterInform     // Emitter 정보를 저장하기 위한 클래스 
{
    public System.Drawing.Point Position { get; set; }     // Emitter 의 위치 좌표
    public double Intensity { get; set; }               // Emitter 의 밝기 

    public EmitterInform(System.Drawing.Point Position, double Intensity)
    {
        this.Position = Position;
        this.Intensity = Intensity;
    }
}
//----------------------------------------------------------------------------------------------------
public class NFP_Process
{
    private Mat sourceImage;
    private int ThresholdRangeMin = 10;
    private int ThresholdRangeMax = 255;

    //----------------------------------------------------------------------------------------------------
    public NFP_Process()
    {
        sourceImage = new Mat();
    }
    //----------------------------------------------------------------------------------------------------
    public void imageLoad(string fileName)
    {
        sourceImage = Cv2.ImRead(fileName, ImreadModes.Grayscale);
    }
    //----------------------------------------------------------------------------------------------------
    public void imageLoad(string fileName, PictureBox pictureBox)
    {
        sourceImage = Cv2.ImRead(fileName, ImreadModes.Grayscale);
        pictureBox.Image = BitmapConverter.ToBitmap(sourceImage);
    }
    //----------------------------------------------------------------------------------------------------
    public void setThresholdRange(int Min, int Max)
    {
        if (Max > Min)
        {
            if (Min > 0 || Min < 256) ThresholdRangeMin = Min;
            if (Max > 0 || Max < 256) ThresholdRangeMax = Max;
        }
    }
    //----------------------------------------------------------------------------------------------------
    public int SearchEmitters(List<EmitterInform> emittersInforms)
    {
        int Count = 0;  // emitter 갯수  
        Mat binaryImage = new Mat();

        Cv2.Threshold(sourceImage, binaryImage, ThresholdRangeMin, ThresholdRangeMax, ThresholdTypes.Binary);   // Source Image를 이진 이미지로 변환한다. 
        Cv2.FindContours(binaryImage, out OpenCvSharp.Point[][] contour, out HierarchyIndex[] hierarchy, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple);  // Emitters 의 외형을 추출한다. 

        emittersInforms.Clear();

        if (contour.Length > 0)     // emitter를 정상적으로 찾았을 경우 
        {
            foreach (var emitter in contour)
            {
                Moments mmt = Cv2.Moments(emitter);     // emitter의 모멘트 정보를 추출

                int cx = (int)(mmt.M10 / mmt.M00);   // emitter 중심점의 X 좌표
                int cy = (int)(mmt.M01 / mmt.M00);   // emitter 중심점의 Y 좌표

                if (cx > 0 && cy > 0)
                {
                    Mat mask = Mat.Zeros(sourceImage.Height, sourceImage.Width, MatType.CV_8UC1);  // 마스크 이미지를 초기화 한다. 
                    Cv2.DrawContours(mask, contour, Count, Scalar.White, 2, LineTypes.Link4, hierarchy);  // Emitter 의 외형을 그린다. 
                    Cv2.FloodFill(mask, new OpenCvSharp.Point(cx, cy), Scalar.White);  // mask 이미지에서 Emitter 외형의 내부를 채운다. 

                    Scalar value = Cv2.Mean(sourceImage, mask);  // 소스 이미지에서 마스킹된 emitter의 Pixel 값들의 평균을 구한다. 

                    emittersInforms.Add(new EmitterInform(new System.Drawing.Point(cx, cy), Math.Round(value.Val0, 3)));  // emitter 정보를 저장한다. 
                    Count++;
                }
            }

            emittersInforms.Sort((InformA, InformB) => InformA.Position.X.CompareTo(InformB.Position.X));     // searching 된 emitter들을 X 좌표값을 기준으로 sorting한다. 
        }

        return Count;
    }
    //----------------------------------------------------------------------------------------------------
}

5. 사용방법 

 

private void button2_Click(object sender, EventArgs e)
{

    NFP_Process nfp_Process = new NFP_Process();    //class 객체 생성

    List<EmitterInform> emitterInforms= new List<EmitterInform>();  // Emitter 의 정보가 저장될 List
    nFP_Process.imageLoad("nfp_image.png");  // 저장된 이미지 loading

    int count = nfp_Process .SearchEmitters(emitterInforms);  // NFP search
    dataGridView1.DataSource = emitterInforms;  // 써칭된 정보를 dataGridView 에 출력 
}

 

6. 기타 

  (1)저장된 영상의 상태에 따라 ThresholdRangeMin, ThresholdRangeMax 값을 조정하면 더 정확한 정보를 추출할 수 있다,

반응형

댓글()