[C#, openCVSharp] 회전된 PCB 불량탐지를 위한 SURF
요즘 갑작스레 PCB 기판 불량 탐지하는 프로젝트를 한다고 해서 이것저것 준비중인데, 그 중 하나가 바로 컨베이어벨트에 아무렇게나 던져져있는 기판의 불량을 찾는 것이란다...킁;
일반적으로 2D 불량탐지는 차연산을 통해 하기 때문에 비교영상과의 정확한 매칭이 필수인데, 아무렇게나 던져진 오브젝트라니....ㅠㅠ
일단 연구계획관련 문서를 만들기 위해 회전된 오브젝트를 어떻게 바로 세울까 고민하다가 귀차니즘이 발동하여 SURF로 특징 잡고, 특징을 기반으로 회전시키는 방법을 사용하기로 했다.
물론 이 방법은 좀 느리다보니(내 데스크탑으로 약 1초 소요) 어디까지나 '이러이러한 기술을 보유하고 있다' 하고 가라로 문서 작성할때나 사용할 것이고, 연구일정이 잡히면 더 좋은 방법을 생각해 봐야겠다.ㅠ
개발 언어는 C#, 라이브러리는 openCVSharp을 사용했고, 뭐 알만한 사람은 소스만 봐도 아시겠지만
opencvsharp 홈페이지(https://code.google.com/p/opencvsharp/) 예제를 그대로 가져다 썼다.ㅋㅋ
그래도 회전시키는 소스 (//////////으로 주석처리된 부분)는 직접 추가... -_-;
아래는 과정과 결과 사진
그림 1. 미리 찍어둔 비교하고자 하는 사진
그림 2. 촬영한 오브젝트 사진
그림 3. SURF 로 영상 비교
그림 4. 비교사진(그림1)의 각도와 위치에 맞게 Perspective Transform한 오브젝트 사진(그림2)
아래는 소스.
(뭐.. 내가 짠 소스도 아니고... 추가한 부분은 회전을 위해 Perspective Transform 한 것밖에..
이마저도 openCVSharp 예제소스 퍼와서 사용 ㅋㅋㅋㅠㅠㅠ 아 이러다 가뜩이나 구질구질한 코딩실력 나락으로 빠질듯ㅋㅋ)
public SURFSample()
{
IplImage objx = Cv.LoadImage("pcb.jpg");
IplImage obj = Cv.CreateImage(objx.Size, BitDepth.U8, 1);
IplImage image = Cv.CreateImage(objx.Size, BitDepth.U8, 1);
IplImage dst = Cv.CreateImage(objx.Size, BitDepth.U8, 1);
Cv.Copy(obj, dst);
Cv.CvtColor(objx, obj, ColorConversion.BgrToGray);
using (CvCapture cap = CvCapture.FromCamera(0))
using (CvWindow w = new CvWindow("Processing Image"))
using (CvWindow m = new CvWindow("Result Image"))
{
while (CvWindow.WaitKey(10) < 0)
{
using (IplImage imagex = cap.QueryFrame())
Cv.CvtColor(imagex, image, ColorConversion.BgrToGray);
using (CvMemStorage storage = Cv.CreateMemStorage(0))
using (IplImage objColor = Cv.CreateImage(obj.Size, BitDepth.U8, 3))
using (IplImage correspond = Cv.CreateImage(new CvSize(image.Width, obj.Height + image.Height), BitDepth.U8, 1))
{
Cv.CvtColor(obj, objColor, ColorConversion.GrayToBgr);
Cv.SetImageROI(correspond, new CvRect(0, 0, obj.Width, obj.Height));
Cv.Copy(obj, correspond);
Cv.SetImageROI(correspond, new CvRect(0, obj.Height, correspond.Width, correspond.Height));
Cv.Copy(image, correspond);
Cv.ResetImageROI(correspond);
CvSeq<CvSURFPoint> objectKeypoints, imageKeypoints;
CvSeq<float> objectDescriptors, imageDescriptors;
Stopwatch watch = Stopwatch.StartNew();
{
CvSURFParams param = new CvSURFParams(500, true);
Cv.ExtractSURF(obj, null, out objectKeypoints, out objectDescriptors, storage, param);
Console.WriteLine("Object Descriptors: {0}", objectDescriptors.Total);
Cv.ExtractSURF(image, null, out imageKeypoints, out imageDescriptors, storage, param);
Console.WriteLine("Image Descriptors: {0}", imageDescriptors.Total);
}
watch.Stop();
Console.WriteLine("Extraction time = {0}ms", watch.ElapsedMilliseconds);
watch.Reset();
watch.Start();
CvPoint[] srcCorners = new CvPoint[4]{
new CvPoint(0,0), new CvPoint(obj.Width,0), new CvPoint(obj.Width, obj.Height), new CvPoint(0, obj.Height)
};
CvPoint[] dstCorners = LocatePlanarObject(objectKeypoints, objectDescriptors, imageKeypoints, imageDescriptors, srcCorners);
if (dstCorners != null)
{
CvPoint2D32f[] pnt = new CvPoint2D32f[4];
double[] dist = new double[4];
for (int i = 0; i < 4; i++)
{
CvPoint r1 = dstCorners[i % 4];
CvPoint r2 = dstCorners[(i + 1) % 4];
Cv.Line(correspond, new CvPoint(r1.X, r1.Y + obj.Height), new CvPoint(r2.X, r2.Y + obj.Height), CvColor.Black);
dist[i] = Math.Sqrt(((r1.X - r2.X) * (r1.X - r2.X)) + ((r1.Y - r2.Y) * (r1.Y - r2.Y)));
if (i == 0)
{
pnt[0] = new CvPoint2D32f(Convert.ToDouble(r1.X), Convert.ToDouble(r1.Y));
pnt[1] = new CvPoint2D32f(Convert.ToDouble(r2.X), Convert.ToDouble(r2.Y));
}
else if (i == 2)
{
pnt[3] = new CvPoint2D32f(Convert.ToDouble(r1.X), Convert.ToDouble(r1.Y));
pnt[2] = new CvPoint2D32f(Convert.ToDouble(r2.X), Convert.ToDouble(r2.Y));
}
}
///////////////////////////////
CvPoint2D32f[] dstPnt = new CvPoint2D32f[4];
dstPnt[0] = new CvPoint2D32f(0.0f, 0.0f);
dstPnt[1] = new CvPoint2D32f(639.0f, 0.0f);
dstPnt[2] = new CvPoint2D32f(0.0f, 479.0f);
dstPnt[3] = new CvPoint2D32f(639.0f, 479.0f);
using (CvMat mapMatrix = Cv.GetPerspectiveTransform(pnt, dstPnt))
{
Cv.WarpPerspective(image, dst, mapMatrix, Interpolation.Linear | Interpolation.FillOutliers, CvScalar.ScalarAll(100));
}
///////////////////////////////
}
int[] ptpairs = FindPairs(objectKeypoints, objectDescriptors, imageKeypoints, imageDescriptors);
for (int i = 0; i < ptpairs.Length; i += 2)
{
CvSURFPoint r1 = Cv.GetSeqElem<CvSURFPoint>(objectKeypoints, ptpairs[i]).Value;
CvSURFPoint r2 = Cv.GetSeqElem<CvSURFPoint>(imageKeypoints, ptpairs[i + 1]).Value;
Cv.Line(correspond, r1.Pt, new CvPoint(Cv.Round(r2.Pt.X), Cv.Round(r2.Pt.Y + obj.Height)), CvColor.White);
}
//for (int i = 0; i < objectKeypoints.Total; i++)
//{
// CvSURFPoint r = Cv.GetSeqElem<CvSURFPoint>(objectKeypoints, i).Value;
// CvPoint center = new CvPoint(Cv.Round(r.Pt.X), Cv.Round(r.Pt.Y));
// int radius = Cv.Round(r.Size * (1.2 / 9.0) * 2);
// Cv.Circle(objColor, center, radius, CvColor.Red, 1, LineType.AntiAlias, 0);
//}
watch.Stop();
Console.WriteLine("Drawing time = {0}ms", watch.ElapsedMilliseconds);
Cv.NamedWindow("Object", WindowMode.AutoSize);
Cv.NamedWindow("Object Correspond", WindowMode.AutoSize);
Cv.ShowImage("Object Correspond", correspond);
Cv.ShowImage("Result Image", obj);
Cv.ShowImage("Processing Image", image);
Cv.ShowImage("Object", dst);
Cv.WaitKey(0);
Cv.DestroyWindow("Object");
Cv.DestroyWindow("Object Correspond");
}
}
}
}