현실감각 0% :: '분류 전체보기' 카테고리의 글 목록 (12 Page)

컴퓨터 관련 2011. 11. 10. 14:17

[C#, openCVSharp] SURF를 이용한 PCB 쇼트-서킷(short circuit) 검출




저번 포스팅에 썼던 SURF를 이용하여 수작업으로 조립하는 사이드키 납땜부분의 쇼트-서킷(이하 쇼트) 검출 프로그램 개발!





순서는...

일단 입력영상을 SURF를 이용하여 원근변환(Perspective Transform) 적용 후





검출영역을 ROI로 설정하고, Otsu 알고리즘으로 이진화 한 후에 레이블링해서 레이블 개수로 판별-_-;
뭐 아직 연구개발 초창기니까-_-;;;;;;

 


이놈은 쇼트 난 영상 결과. 맨 아래 깔끔한 영상은 비교영상이다.



이녀석은 양품


귀찮아서 대충 레이블링으로 쇼트 여부를 검출하였지만, 모폴로지 연산과 컨투어, 컨벡스헐 등을 잘 조합하면 훨씬 좋은(그래봤자 openCV에서 전부 제공하는 기본 영상처리지만...ㅋㅋㅋㅋ) 성능을 보일것으로 판단된다.

최신 논문들도 좀 보고 계속 공부해야하는데 단순한 영상처리 소스 몇개 조합해서 하루하루 대충 수습하는 중..ㅠㅠ 과장님 죄송합니다.ㅠㅠ SQMS도 해야하고, 자재관리하는 프로그램도 만들어야하고, 인사관리시스템 급여부분도 만들어야하고, 각종 버그도 수정해야하고... 시간이 없어요.ㅠㅠ흑흐그흐그그휴ㅜㅠ

소스는 저번 포스팅에 있는 SURF 소스에 ROI 설정부분과 레이블링 소스만 추가(-_-;;)



컴퓨터 관련 2011. 10. 14. 19:53

[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");
                    }
                }
            }
        }



컴퓨터 관련 2011. 9. 7. 10:44

기계학습(Machine Learning) 이란?



※ 이 글은 컴퓨터 인공지능에 관심있는 고등학생 및 대학생, 그리고 대학교때 공부 지지리 안하다가 얼떨결에 대학원 들어와서 피똥싸는 석사과정 1,2학기를 대상으로 쓴 글입니다. 전문적인 내용을 알고자 하시는 분은 웹 브라우저를 살포시 접어두시고 관련 논문을 읽어보시기 바랍니다.
※ 본 글의 내용은 특정 자료를 보면서 쓴 글이 아니며 따라서 잘못된 지식/정보가 포함되어 있을 수 있습니다. 혹시 잘못된 내용이나 의문사항 있으시면 이메일 또는 덧글로 남겨주시기 바랍니다.
※ 본 글은 상업적인 용도가 아닌 이상 누구나 사용할 수 있으며, 원문 출처를 표기한다면 수정, 배포, 인쇄 등 어떠한 행위를 하여도 전혀 관심 없습니다.
※ 편의를 위해 본문은 높임체를 쓰지 않았습니다.



1. 기계학습 알고리즘이란?

아이들이 처음으로 사과를 먹고 '사과는 달콤하다'라는 정보를 알았다고 하자. 그러면 그 아이는 사과파이나, 사과잼 등 아직 먹어보지 않았지만 사과로 만든 여러 음식 또한 달콤할 것이라고 판단 할 것이다. 이와 같이, 컴퓨터 역시 이런저런 것들을 가르치면 그와 관련된, 하지만 직접적으로 가르쳐주지 않은 것들에 대해 답 할수 있다. 즉 학습 데이터를 바탕으로 입력데이터를 분류, 인식 할 수 있게 하는 알고리즘이 기계학습 알고리즘이다.

기계학습 알고리즘은 크게 학습영역과 인식영역으로 나뉜다. 아래의 서브챕터는 학습영역과 인식영역에 대해 간단히(?) 설명한 것이다.

1-1. 학습영역
이러한 기계학습은 일반적으로 학습영역과 인식영역으로 나뉜다. 우선 학습영역은 사람으로 비유를 들자면 아무것도 모르는 어린아이에게 몇가지 정보를 알려줘서 그에 대한 정보를 머릿속에 담게 하는 역할을 한다. 그림 1은 학습의 구조를 간단한 예를 통해 표현한 것이다.



그림 1. 학습과정의 예


우선 컴퓨터(혹은 유아)가 과일이나 채소에 대해 아무것도 모른다고 가정하겠다. 그림 1의 맨 위의 것들은 과일과 채소에 대해 가르치기 위해 준비한 학습 데이터이다. 각각의 학습데이터에는 맛과 색깔에 대한 두가지 정보를 가지고 있다. 이러한 정보들을 이/공학에서는 특징벡터(Feature Vector)라고 표현한다.(특징정보가 2개면 2차원 벡터가 되고, 10개면 10차원 벡터, 1000개면 1000차원 벡터가 되는 것이다.)
아무튼 이렇게 준비한 정보들을 학습영역에 때려박는다. 여기서는 우선 기계학습에 대해 설명하는 중이기 때문에 학습영역의 내부 구조는 추후에 설명하도록 하겠다.(이 학습영역의 내부구조에 따라 인공신경망, SVM 등 알고리즘이 달라진다.)
아무튼 이렇게 학습이 끝나고 나면 컴퓨터(혹은 유아)는 학습결과 영역과 같이 과일과 채소를 정의할 것이다.

과일이란? 빨간색 또는 노란색의 달콤한 것들.
채소란? 초록색 또는 보라색의 달콤하지 않은 것들.



1-2. 인식영역
인식과정은 학습한 데이터(머릿속에 저장된 정보)를 가지고 새로운 데이터가 입력되었을 경우 이미 학습한 정보를 이용하여 판별하는 역할을 한다. 1-1의 학습과정을 통해 컴퓨터(혹은 유아)는 과일과 채소에 대해 알게 되었다. 이와 같이 학습이 완료되면 이제부터는 학습된 정보를 바탕으로 인식을 수행할 수 있다. 그림 2를 보자.



그림 2. 인식과정의 예


그림 2는 인식과정을 보여준다. 아마 학습과정인 그림 1과 큰 차이를 느끼지 못할 것이다. 단 차이가 있다면, 입력데이터의 분류가 무엇인지 모른다는 것과, 학습영역이 분류영역으로 교체되었고, 분류영역에 학습데이터의 데이터베이스가 연결되었다는 점 정도이다. 실제로 학습과정과 인식과정은 거의 차이가 없다.(프로그램 코드상으로도 큰 차이가 없다.)

우선 입력데이터의 분류를 ?로 한 이유는, 이것들이 과일인지 채소인지 모른다는 가정하에 분류하기 위해서이다.

그리고 분류영역, 이것의 구조는 학습영역과 거의 유사하다고 할 수 있다.
단 학습영역은 분류결과가 학습데이터의 분류결과와 일치(또는 근사)할 때까지 반복해서 계산해서 가중치(가중치가 무엇인지 모른다면 일단은 '분류를 결정하는 열쇠' 정도라고만 이해하고 넘어가자)를 갱신하는데 비해, 분류영역은 해당 가중치를 학습과정에서 계산하여 저장한 학습 데이터베이스에서 바로 가져다 쓴다는 차이가 있다.

분류에 필요한 학습데이터란 바로 위에서 말한 가중치들의 집합이다. 가중치에 대해 간단히 설명하자면, '과일은 달고 빨강, 또는 노랑색, 채소는 달지 않고 초록색 또는 보라색이다' 라는 정보를 분류할 수 있게 해주는 수치라고 판단하면 된다.

즉 학습영역에서 산출된 학습결과 가중치를 이용하여 분류를 수행하면 귤과 망고는 달고 노란색이기 때문에 과일, 배추는 달지 않고 초록색이기 때문에 채소로 분류한다.



1-3. 특징벡터
특징벡터는 위에서도 설명했지만, 학습 또는 분류하고자 하는 객체의 특징을 의미한다. 벡터라는 말을 사용하는 이유는, 특징이 1개 뿐이라면 이 특징을 수치화 시켰을 때, 예를 들면 달콤하다는 '0', 달콤하지 않다는 '1'과 같이, 하나의 숫자, 즉 스칼라값으로 표현 가능하지만, 2개 이상의 특징을 수치화 시키면 하나의 숫자로 표현되지 않기 때문에, 2개 이상의 숫자의 집합이라 할 수 있는 벡터라고 하는 것이다.

특징벡터는 기계학습 알고리즘을 어떤 것을 사용하느냐와 관계없이 객체를 학습/분류하는데 가장 큰 영향을 미친다. 그 이유는 아래 그림 3을 보면 알 수 있다.




그림 3. 올바르지 않은 특징벡터의 사용으로 인한 오인식 결과


우리가 알고 있기로는 단호박은 채소이다. 하지만 맛과 색이라는 두가지 특징벡터를 사용한다면 분류결과는 단호박의 경우 그림 3의 분류결과와 같이 과일로 분류된다. 이는 맛과 색이 과일과 채소를 나누는 결정적인 특징이 아니라는 얘기다. 따라서 과일과 채소를 구분하기 위해서는 맛과 색이 아닌 또다른 특징이 필요하다. 뿐만 아니라 오히려 위의 맛과 색은 과일과 채소를 나누는데 있어 전혀 도움되지 않는 특징이 될 수도 있다. 일반적으로는 특징의 개수가 많을수록 좋지만, 필요 이상으로 특징이 많으면 일부 특징은 잡음 역할을 해서 정확한 학습/인식에 악영향을 미치기도 한다.

그럼 맛과 색이 아닌 다른 어떠한 특징이 좋을까? 아마 이 글을 읽는 대부분의 사람들이 나무의 열매는 과일, 나무에서 열리지 않는 열매가 채소라는 점은 다 알고 있을 것이다. 해당 특징을 이용하면 1차원 특징벡터만으로도 상당히 우수한 학습/인식 결과를 나타낼 수 있다. 이처럼 특징 중에서 정말 무언가를 결정하는데 크리티컬한 특징들이 있다. '어떠한 기계학습 알고리즘을 사용하는지'에 대한 문제도 중요하지만 더욱 중요한 것은 '어떠한 특징을 사용하는지'에 관한 것이다.
다시한번 사람으로 예를 들자면, 기계학습 알고리즘을 선택하는 것은 어떤 방식으로 공부하느냐를 결정하는 것이고, 특징을 선택하는 것은 무엇을 공부하느냐를 선택하는 것이라고 할 수 있다.
집에서 공부하는 것보다는 학원, 또는 독서실에서 공부하는 것이 보다 효율이 좋겠지만, 영어시험을 보기 위한 공부라면 학원 독서실에서 중국어를 공부한 사람보다는 집에서 영어를 공부한 사람의 성적이 더 좋은 것은 당연한 것이다.
인공신경망 또는 다른 학습 알고리즘을 공부하기 전에 이와 같이 특징이 학습/인식에 미치는 영향이 얼마나 중요한지에 대해 한번쯤은 알고 넘어가는 것이 좋을 것이다.




기회가 된다면 다음 포스팅때에는 기계학습 알고리즘 중 하나를 선택해서 보다 자세하게 포스팅 하도록 하겠습니다.