일본 Contec 사의 GPIB 통신 카드인 GP-IB(PCI)F 를 사용하기 위한 Class 입니다. C++ 로 제작되어서 제공되는 DLL 라이브러리를 사용합니다. Contec 사에서는 C#을 위한 별도의 라이브러리는 제공을 안하고 DLL을 사용한 예제 코드만 제공합니다.
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] ;
고출력 레이저 다이오드 모듈에 사용되는 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 값을 조정하면 더 정확한 정보를 추출할 수 있다,