root@bskyvision: ~#
방명록
태그
RSS

<닫기>

3,562,156/3,854/1,660

프로필사진
수많은 소음 속에서 신호를 찾아가는 bskyvision입니다.


<닫기>

  • 꼬장이이(가) 07.25에 작성한 댓글: test.

<닫기>

(전체 글) 검색 결과 857건
  • SIFT (Scale Invariant Feature Transform)의 원리 (105)
    늦게나마 SIFT에 매료된 후 2004년에 나온 논문[2]을 비롯한 여러 자료들을 살펴보던 와중에, [1]에 포스팅 된 글과 [3]에 한글로 한 대학생이 SIFT에 대해 정리한 PDF 파일이 개인적으로 이해하는데 큰 도움이 되었습니다. 아직 완벽하게 이해된 것은 아니지만 나름대로 최선으로 정리해보겠습니다. 조금 길긴하지만 끝까지 인내심을 갖고 읽어나가신다면 무언가 얻으실 수 있을 것입니다. 혹시 궁금점이 있으시거나 틀린 내용이 있거나 이 글이 도움이 되셨다면, 댓글을 남겨주시거나 공감을 눌러주세요! SIFT (Scale-Invariant Feature Transform)은 이미지의 크기와 회전에 불변하는 특징을 추출하는 알고리즘입니다. 서로 다른 두 이미지에서 SIFT 특징을 각각 추출한 다음에 서로 가..
  • [3D 비전] 호롭터(horopter)와 파눔융합역(Panum's fusion area)
    ▶호롭터와 파눔융합역이란? 호롭터와 파눔융합역은 모두 양안 비젼(두 눈을 사용해서 세상을 바라봄)에서 흔히 사용되는 용어들입니다. 우리의 두 눈은 수평으로 일정한 거리를 두고 분리되어 있습니다. 그래서 두 눈이 바라보는 장면은 거의 같지만 수평적으로 약간 다릅니다. 이 다름(시차, disparity)을 통해 뇌는 깊이에 대한 정보를 얻습니다. 호롭터는 관찰자로부터 동일한 거리에 위치한 점들을 잇는 곡선을 의미합니다. 즉, 두 눈으로부터 같은 거리에 위치하여 디스패리티가 존재하지 않는 점들을 이은 곡선입니다. 따라서 호롭터 위에 있는 점들은 깊이감 없이 융합됩니다. 그리고 파눔융합역은 두 망막 이미지들이 하나로 융합될 수 있는 호롭터 주변의 구역을 말합니다. 호롭터 위를 제외한 나머지 파눔융합역에서는 디스..
  • [3D 비전] optic flow software를 활용해서 디스패리티 맵 산출하기 (matlab)
    optic flow software 매트랩 소스코드는 [1]에서 다운로드 받으실 수 있습니다. 그리고 이 소프트웨어와 관련된 논문은 [2]입니다. [3] 논문에서 이 소프트웨어를 활용해서 디스패리티 맵을 산출한 것을 따라서 저도 디스패리티 맵을 산출해보려고 합니다. 소프트웨어를 통해 산출된 모션 벡터들에서 수평 성분을 수평 디스패리티로 사용했습니다. ▶ 소스코드 테스트 그림 1은 테스트에 사용된 이미지들[4]과 ground truth 디스패리티 맵과 산출된 디스패리티 맵을 보여줍니다. 밝을 수록 카메라로부터 가깝고, 어두울 수록 멀다는 것을 의미합니다. 이 소프트웨어를 활용해서 산출한 디스패리티 맵이 상당히 거리감을 잘 나타냄을 그림을 통해 알 수 있습니다. [1] http://ps.is.tue.mpg...
  • 코딩/matlab - 2017.05.16
    [MATLAB] grape-based visual saliency (GBVS)로 saliency 맵 산출하기 (6)
    GBVS는 visual saliency 모델 중 하나입니다. visual saliency 모델은 이미지 내 시각적으로 중요한 부분들이 어딘지를 나타내주는 알고리즘입니다. GBVS에 대한 이론적 설명은 링크를 참고해주세요. 그리고 GBVS의 매트랩 소스코드는 [1]에서 받을 수 있습니다. 또한 GBVS에 대한 original 논문은 [2]입니다. ▶ 소스코드 테스트 소스코드를 다운 받으시면 폴더 내에 readme.txt에 어떻게 사용하는지 구체적으로 자세히 나와있습니다. 혹시 잘 모르시겠다면 댓글 달아주세요. 네 장의 사진의 GBVS를 통해 얻은 saliency 맵은 그림1에 있습니다. saliency 맵에서 밝은 부분일 수록 시각적으로 중요하다고 판단한 부분입니다. 첫번째 빨간 차가 있는 사진을 제외하고..
  • 코딩/C, C++ - 2017.05.16
    [Learn opencv by examples] 3. 기본적인 드로잉 예제들
    오늘은 http://opencvexamples.blogspot.com/p/learning-opencv-functions-step-by-step.html에 있는 세번째 예제를 따라해보도록 하겠습니다. 3. Basic drawing examples 1) 선 그리기 소스코드부터 보시죠. #include #include #include using namespace cv; int main() { // Create black empty images Mat image = Mat::zeros(400, 400, CV_8UC3); // 세로, 가로 각각 400, 400 사이즈의 검정 이미지를 만든다. // Draw a line line(image, Point(15, 20), Point(300, 350), Scalar(0,..
  • 코딩/C, C++ - 2017.05.12
    [Learn opencv by examples] 2. 카메라로부터 비디오 캡쳐하기
    오늘은 http://opencvexamples.blogspot.com/p/learning-opencv-functions-step-by-step.html에 있는 두번째 예제를 공부해보겠습니다! 2. Capture video from camera 카메라를 작동시켜 촬영된 비디오를 캡쳐하는 방법입니다. 우선 코드부터 보시죠. 위 링크에서 소개하고 있는 예제에 좀 더 덧붙여 캡쳐한 비디오를 그레이영상으로 전환한 것도 함께 출력하도록 코드를 변경했습니다. #include "opencv2/opencv.hpp" using namespace cv; int main(int, char**) { VideoCapture cap(0); // open the default camera if (!cap.isOpened()) // ..
  • 코딩/C, C++ - 2017.05.12
    [Learn opencv by examples] 1. 이미지 불러오고, 전시하고, 저장하기
    당분간 http://opencvexamples.blogspot.com/p/learning-opencv-functions-step-by-step.html에 있는 초보자들을 위한 예제들을 따라하면서 opencv를 공부하려고 합니다. 총 13개의 예제로 구성되어 있습니다. 참고로 영문사이트입니다. 오늘은 첫번째 예제를 공부해보겠습니다. :D 1. Load, Display and Save an image 첫번째 예제는 이미지를 불러와서 전시하고 저장하는 방법을 담고 있습니다. 우선 코드를 아래에 첨부하겠습니다. 저는 opencv 3.2.0 버전과 visual studio 2017을 사용했습니다. 여기서 주요한 함수들을 살펴보겠습니다. 먼저 imread는 이미지를 불러오는 함수입니다. 저는 tsuL.png를 불러..
2017-05-23 13:12:24

늦게나마 SIFT에 매료된 후 2004년에 나온 논문[2]을 비롯한 여러 자료들을 살펴보던 와중에, [1]에 포스팅 된 글과 [3]에 한글로 한 대학생이 SIFT에 대해 정리한 PDF 파일이 개인적으로 이해하는데 큰 도움이 되었습니다. 아직 완벽하게 이해된 것은 아니지만 나름대로 최선으로 정리해보겠습니다. 조금 길긴하지만 끝까지 인내심을 갖고 읽어나가신다면 무언가 얻으실 수 있을 것입니다. 혹시 궁금점이 있으시거나 틀린 내용이 있거나 이 글이 도움이 되셨다면, 댓글을 남겨주시거나 공감을 눌러주세요! 

 

SIFT (Scale-Invariant Feature Transform)은 이미지의 크기와 회전에 불변하는 특징을 추출하는 알고리즘입니다. 서로 다른 두 이미지에서 SIFT 특징을 각각 추출한 다음에 서로 가장 비슷한 특징끼리 매칭해주면 두 이미지에서 대응되는 부분을 찾을 수 있다는 것이 기본 원리입니다. 즉, 그림 1과 같이 크기와 회전은 다르지만 일치하는 내용을 갖고 이미지에서 동일한 물체를 찾아서 매칭해줄 수 있는 알고리즘입니다. 

 

그림 1. SIFT로 이미지 매칭한 예

 

위에 두 이미지에서 동일한 부분인 책을 찾아서 매칭해준 것을 확인할 수 있습니다. 중요한 것은 이미지의 크기도 다르고, 책의 회전? 방향도 다르고, 다른 물체들에 조금씩 가려져 있는데도 일치되는 부분을 잘 찾아서 매칭해주었습니다. 이것이 바로 SIFT의 장점입니다. SIFT는 크기나 회전에 불변하는 특징을 찾아내줍니다. 위의 이미지들은 흑백이라 조금 보기 안 이쁘니 다른 예를 하나 더 살펴보겠습니다. 그림 2와 그림 3을 보시죠 (참고로 이 사진은 부산에 아는 형 결혼식 갔다가 찍은 사진입니다.) 그림 2에서는 왼쪽 아래에 있는 빨간 색 차를 매칭해봤습니다. 차 사진이 회전되었고, 좀 더 흐려졌음에도 잘 매칭되었음을 확인할 수 있습니다.  

 

그림 2. SIFT를 이용해서 두 이미지 안에 있는 빨간 색 차를 매칭

 

그림 3에서는 표지판을 매칭했는데, 두 포인트 정도 완전히 엇나간 것을 제외하고는 비교적 잘 매칭되었습니다.

 

그림 3. SIFT를 이용해서 두 이미지 안에 있는 표지판을 매칭

  

이런 식으로 작동하는 SIFT는 파노라마 영상을 만들 때도 사용됩니다. 파노라마 사진 만드는 알고리즘을 보진 않았지만 아마도 여러 장 조금 겹치게 찍은 사진에서 서로 겹치는 부분을 찾아서 옆에 붙이고 붙여주는 원리를 갖고 있지 않을까 싶네요. 그리고 SIFT는 두 장의 이미지가 좌우로 조금 다른 수평 디스패리티를 갖고 있는 스테레오 이미지를 매칭할 때도 사용될 수 있습니다. 

 

그렇다면, 도대체 SIFT 알고리즘은 어떤 순서를 갖고 있기에 회전과 크기 변화에도 robust하게 서로 매칭되는 부분을 잘 찾아낼 수 있는 것일까요? 절차를 하나씩 잘 살펴보겠습니다. 대략적으로 아래와 같은 순서로 진행됩니다. 

 

1. "Scale space" 만들기

2. Difference of Gaussian (DoG) 연산

3. keypoint들 찾기

4. 나쁜 Keypoint들 제거하기

5. keypoint들에 방향 할당해주기

6. 최종적으로 SIFT 특징들 산출하기

 

자, 그러면 하나씩 살펴보도록 하겠습니다. 

 

 

1. "Scale space" 만들기 

 

첫번째로 "Scale space" 만들기입니다. Scale space에 대한 개념은 아래 링크를 참고하세요.(http://bskyvision.com/144) 먼저 원본 이미지를 두 배로 크게 만든 다음에 점진적으로 블러되게 만듭니다. 그리고 원본 이미지를 점진적으로 블러되게 만듭니다. 그 다음에는 원본 이미지를 반으로 축소시킨 이미지에서 또한 점진적으로 블러된 이미지들을 만들어냅니다. 그리고 또 반으로 축소시킨 다음에 점진적으로 블러된 이미지들을 만들어냅니다. (원본 이미지를 일단 2배로 키우는 이유는 나중에 Difference of Gaussian (DoG) 이미지를 만들 때 같은 옥타브 내에서 인접한 2개의 블러 이미지를 활용해서 만들고, 또 그렇게 생성된 DoG 이미지들 중에서 인접한 세 개의 DoG 이미지를 활용해서 Keypoint들을 찾기 때문입니다.) 그림 4에 구현한 결과를 나타냈습니다. 

 

 

그림 4. Scale space 만든 결과

 

 

blurring, 즉 흐리게 만드는 것은 가우시안 연산자와 이미지를 컨볼루션 연산해줌으로 가능합니다. 공식으로 나타내면 아래와 같습니다. 

 

...(1)

 

여기서 L은 블러된 이미지이고, G는 가우시안 필터이고, I는 이미지입니다. x, y는 이미지 픽셀의 좌표값이고, 

는 "scale" 파라미터입니다. 이거 이름을 왜 scale 파라미터라고 했는지는 잘 모르겠지만, 아무튼 블러의 정도를 결정짓는 파라미터입니다. 값이 클수록 더 흐려집니다. 아래는 가우시안 필터의 공식입니다. 

 

...(2)

점차적으로 블러된 이미지를 얻기 위해서 

값을 k배씩 높여갔습니다. 처음 

값을 

으로, k 는 

로 설정했습니다. 2배로 키운 원본이미지가 처음에는 

의 값으로 블러되었다면, 그 다음에는 1이 되고, 그 다음에는 

, 또 그 다음에는 2의 값으로 블러가 되는 방식입니다. 점점 더 흐려진 결과를 볼 수 있습니다. 원본 이미지에서는 

에서 시작해서 k배씩 점차적으로 블러되게 해줍니다. 그리고 원본 이미지의 크기를 가로, 세로 각각 반씩 줄인 다음에는 

에서 시작해서 k배씩 점차적으로 블러되게 해줍니다. 같은 사이즈를 갖지만 다른 블러의 정도를 갖는 이미지가 각각 5장씩 존재하고, 총 4개의 그룹을 형성하게 됩니다. 여기서 같은 사이즈의 이미지들의 그룹을 octave라고 명칭합니다. 즉, 세로로 배열되어 있는 이미지들이 한 옥타브를 형성합니다. 아마도 우리가 어떤 물체를 볼 때 거리에 따라 명확하거나 흐리게 보이기도 하고, 크거나 작게 보이기도 하는 현상을 담으려고 이런 방법을 쓰는 것이 아닐까 싶습니다. 이 과정 덕분에 SIFT 특징은 이미지의 크기에 불변하는 특성을 갖게 됩니다. 

 

 

2. DoG 연산

 

전 단계에서 저희는 이미지의 scale space를 만들었습니다. 다양한 scale값으로 Gaussian 연산처리된 4 그룹의 옥타브, 총 20장의 이미지를 얻었습니다. 이제 Laplacian of Gaussian (LoG)를 사용하면 이미지 내에서 흥미로운 점들, 즉 keypoint들을 찾을 수 있습니다. LoG 연산자의 작동 원리를 간단히 말하자면, 먼저 이미지를 살짝 블러한 다음에 2차 미분을 계산합니다. 식으로 나타내면 다음과 같습니다.

 

...(3)

 

LoG 연산을 통해서 이미지 내에 있는 엣지들과 코너들이 두드러지게 됩니다. 이러한 엣지들과 코너들은 keypoint들을 찾는데 큰 도움을 줍니다. 하지만 LoG는 많은 연산을 요구하기 때문에, 비교적 간단하면서도 비슷한 성능을 낼 수 있는 Difference of Gaussian (DoG)로 대체합니다. LoG, DoG 모두 이미지에서 엣지 정보, 코너 정보를 도출할 때 널리 사용되는 방법들입니다. DoG는 매우 간단합니다. 전 단계에서 얻은 같은 옥타브 내에서 인접한 두 개의 블러 이미지들끼리 빼주면 됩니다. 그림 5는 그 과정을 설명해줍니다. 

그림 5. SIFT 내에서의 DoG 연산 [2]

 

이 과정을 실제 이미지로 구현한 결과는 그림 6에 있습니다. 

 

그림 6. DoG 연산을 실제로 구현한 결과

 

중간에 있는 DoG 이미지들 그냥 검은색 이미지 아닙니다. 컴퓨터 화면이 얼룩진건가 하시는 분들도 있을 텐데 자세히 봐보세요. 얼룩말의 형상이 보일 것입니다. 그래도 잘 안보이는 것 같아서, 정규화한 버전의 DoG 이미지들도 오른쪽에 추가했습니다. 이미지의 엣지 정보들이 잘 도출된 것을 확인할 수 있습니다. 이 과정이 모든 octave에서 동일하게 진행됩니다. 그러면 결과적으로 4개씩, 16장의 DoG이미지를 얻겠죠?? 

여기서 잠깐 LoG와 DoG에 대해 좀 더 깊이 살펴보고 지나가겠습니다. LoG를 DoG로 대체할 때 시간이 적게 걸린다는 것 외에도 장점이 더 있습니다. LoG는 scale 불변성을 위해 

로 라플라시안 연산자를 정규화(normalization)해줘야합니다. 즉, LoG연산자가 아래와 같이 scale-normalized LoG로 변합니다. 

 

...(4)

 

scale-normalized LoG의 극대값, 극소값은 매우 안정적으로 이미지 특징을 나타냅니다. 그래서 이 극대값, 극소값들은 keypoint의 후보가 되는 것입니다. 그러면 도대체 어떻게 LoG가 DoG로 대체될 수 있을까요? 이것을 증명하기 위해서 열 확산 방정식이 응용됩니다. 

 

...(5)

 

열 확산 방정식에 의해 이런 관계가 형성된다고 합니다. Gaussian을 

에 대해 미분한 것은 LoG에 

를 곱한 것과 같다는 의미죠. 이것은 미분함수의 성질을 이용하면 다음과 같이 전개될 수 있습니다.

 

...(6)

 

양변에 우변의 분모를 각각 곱해주면, 아래와 같은 식이 나오게 되죠.

 

...(7)

 

결국 다른 scale을 갖고 있는 Gaussian 이미지들끼리의 합, 즉 DoG는 scale-normalized LoG에 (k-1)을 곱한 것과 거의 같습니다. 따라서 DoG는 scale 불변성을 보장하는 

scale 정규화 과정을 자연스럽게 포함하게 됩니다. 그리고 곱해진 (k-1)은 극대값, 극소값을 찾는데는 아무런 영향을 주지 않기 때문에 무시해도 괜찮습니다. 암튼 이러한 방식으로 LoG는 DoG로 무리없이 잘 대체됩니다. (오히려 상당부분 이득을 보면서 대체됩니다.) 

이제 이 DoG 이미지들을 활용해서 흥미있는 keypoint들을 찾아낼 것입니다. 

 

 

3. keypoint들 찾기

 

이제 DoG 이미지들에서 keypoint들을 찾을 차례입니다. 먼저 DoG 이미지들 내에서 극대값, 극소값들의 대략적인 위치를 찾습니다. 그림 7에 극대값, 극소값의 위치를 찾는 방법이 설명되어 있습니다. 

 

그림 7. DoG이미지에서 극소값, 극대값 찾는 원리

 

한 픽셀에서의 극대값, 극소값을 결정할 때는 동일한 octave내의 세 장의 DoG 이미지가 필요합니다. 체크할 DoG이미지와 scale이 한 단계씩 크고 작은 DoG 이미지들이 필요합니다. 지금 체크할 픽셀 주변의 8개 픽셀과, scale이 한단계씩 다른 위 아래 두 DoG 이미지에서 체크하려고 하는 픽셀과 가까운 9개씩, 총 26개를 검사합니다. 만약 지금 체크하는 픽셀의 값이 26개의 이웃 픽셀값 중에 가장 작거나 가장 클 때 keypoint로 인정이 됩니다. 그러므로 4장의 DoG 이미지들 중에서 가장 위에 있고 가장 아래에 있는 DoG 이미지들에서는 극대값, 극소값을 찾을 수 없습니다. 이런 방식으로 DoG 이미지의 모든 픽셀에서 Keypoint들을 찾습니다. 좀 더 확실한 이해를 위해 이 과정을 실제 이미지로 구현해보겠습니다. 이번에도 octave1에서의 결과만 보이도록 하겠습니다. 그림 8을 확인하세요. 나머지 octave들에서도 동일한 연산이 이루어진다는 것을 잊지 마세요. 

 

그림 8. 극대값, 극소값 찾기

 

흰색 점들로 표현된 곳이 극대값, 극소값들입니다. 역시 잘 안보이지만 모니터의 각도를 좀 바꿔보면 잘 보이는 각도가 있을거에요. 암튼 이 점들이 일차적으로 keypoint들이 됩니다. 여기서 보면 두 장의 극값 이미지를 얻기 위해서는 4장의 DoG이미지가 필요하다는 것을 알 수 있습니다. 이를 위해서 scale space를 만들 때 5 단계의 가우시안 블러 이미지를 만든 것입니다. 5장의 블러 이미지에서 4장의 DoG 이미지가 나오고, 또 4장의 DoG 이미지에서 2장의 극값 이미지가 나오는 거죠. 네 그룹의 octave를 갖고 있으니, 8장의 극값 이미지를 얻게 됩니다. 그런데, 이렇게 얻은 극값들은 대략적인 것입니다. 왜냐하면, 그림 9에 설명한 것과 같이 진짜 극소값, 극대값들은 픽셀들 사이의 공간에 위치할 가능성이 많기 때문이죠. 

 

그림 9. 진짜 극값의 위치

 

그런데 우리는 이 진짜 극소값, 극대값들의 위치에 접근할 수 없습니다. 그래서 subpixel 위치를 수학적으로 찾아내야 합니다. subpixel은 이렇게 픽셀들 사이에 위치한 것을 의미하는 것 같습니다. 좀 더 정확한 극값들을 어떻게 찾아내는가 하면, 바로 테일러 2차 전개를 사용합니다. (여기 테일러 전개에 대한 이론적인 내용은 저도 잘 모르겠네요. 일단 대략적인 개념만 이해하고 넘어가셔도 좋습니다.) 

 

...(8)

 

여기서 D는 DoG이미지이고, x는

입니다. 이걸 x로 미분해서 0이 되게 하는 x의 값, 즉

이 극값의 위치가 됩니다. 계산하면 아래와 같이 구해진다고 합니다. 

 

...(9)

 

이 과정은 알고리즘이 좀 더 안정적인 성능을 낼 수 있게 도와줍니다. 

 

 

 

4. 나쁜 Keypoint들 제거하기

 

이제 전 단계에서 극값들로 찾은 keypoint들 중에서 활용가치가 떨어지는 것들은 제거해줘야 합니다. 두 가지 기준으로 제거해주는데요, 첫번째는 낮은 콘트라스트를 갖고 있는 것들을 제거해줍니다. 그 다음 엣지 위에 존재하는 것들을 제거해줍니다. 

먼저 낮은 콘트라스트의 keypoint들을 제거해주는 과정을 살펴보겠습니다. 간단히 DoG 이미지에서 keypoint들의 픽셀의 값이 특정 값(threshold)보다 작으면 제거해줍니다. 저희는 지금 subpixel 위치에서 keypoint들을 갖고 있기 때문에 이번에도 역시 테일러 전개를 활용해서 subpixel 위치에서의 픽셀값을 구합니다. 간단히 위에서 구한 

을 (8)에 대입해주면 됩니다. 그 결과는 (10)과 같습니다.

 

...(10)

 

이 값의 절대값이 특정 값보다 작으면 그 위치는 keypoint에서 제외됩니다. 

이제 두번째로 엣지 위에 존재하는 keypoint들을 제거해줍니다. 그 이유를 저는 다음과 같이 이해했습니다. DoG가 엣지를 찾아낼 때 매우 민감하게 찾아내기 때문에, 약간의 노이즈에도 반응할 수 있는 위험이 있습니다. 즉, 노이즈를 엣지로 찾아낼 수도 있다는 것입니다. 그래서 엣지 위에 있는 극값들을 keypoint로 사용하기에는 조금 위험성이 따릅니다. 따라서 좀 더 확실하고 안전한 코너점들만 keypoint로 남겨주는 것이죠. 이를 위해서 keypoint에서 수직, 수평 그레디언트를 계산해줍니다. 수평 방향으로는 픽셀 값이 어떻게 변화하는지, 수직 방향으로는 픽셀 값이 어떻게 변화하는지 변화량을 계산하는 것입니다. 만약 양 방향으로 변화가 거의 없다면 평탄한 지역(flat region)이라고 생각할 수 있습니다. 근데 수직 방향(or 수평 방향)으로는 변화가 큰데, 수평 방향(or 수직 방향)으로는 변화가 적으면 엣지(가장자리)라고 판단할 수 있습니다. 또 수직, 수평 방향 모두 변화가 크면 코너(모서리)라고 판단할 수 있구요. 설명이 부족하다면, 아래 그림10을 보세요.

 

그림 10. flat 지역, 엣지, 코너 설명

 

이 중에서 코너들이 좋은 keypoint들입니다. 따라서 우리의 목적은 코너에 위치한 keypoint들만 남기는 것입니다. Hessian Matrix를 활용하면 코너인지 아닌지 판별할 수 있다고 합니다. 이 두 제거 과정을 통해 그림 11에서 확인할 수 있듯이 keypoint의 갯수는 상당히 줄어듭니다. 흰 점들이 상당히 많이 줄어들었죠. 

 

그림 11. 나쁜 keypoint들 제거한 결과

 

이제는 이 keypoint들에게 방향을 할당해줄 차례입니다. 

 

 

5. keypoint들에 방향 할당해주기

 

전 단계에서 우리는 적당한 keypoint들을 찾았습니다. 이 keypoint들은 scale invariance(스케일 불변성)를 만족시킵니다. 이제는 keypoint들에 방향을 할당해줘서 rotation invariance(회전 불변성)를 갖게 하려고 합니다. 방법은 각 keypoint 주변의 그레디언트 방향과 크기를 모으는 것입니다. 그런 다음 가장 두드러지는 방향을 찾아내어 keypoint의 방향으로 할당해줍니다. 그림 12와 같이 하나의 keypoint 주변에 윈도우를 만들어준 다음 가우시안 블러링을 해줍니다. 이 keypoint의 scale값(블러의 정도를 결정해주는 파라미터)으로 가우시안 블러링을 해줍니다. 

 

그림 12. keypoint 주변에서 그레디언트 크기와 방향에 대한 값들 모으기위해 윈도우 설정 [1]

 

그 다음에 그 안에 있는 모든 픽셀들의 그레디언트 방향과 크기를 다음의 공식들을 이용해서 구합니다.

 

...(11)

 

그 결과는 그림 13과 같습니다. 

 

그림 13. 그레디언트 크기, 방향 계산 결과 [3]

 

여기서 그레디언트 크기에는 가우시안 가중함수를 이용해서 keypoint에 가까울수록 좀 더 큰 값을, 멀수록 좀 더 작은 값을 갖게 해줍니다. 이 과정은 그림 14에 있습니다. 

 

그림 14. 그레디언트 크기에 가우시안 가중함수를 곱해준 결과 [3]

 

이제 각 keypoint에 어떻게 방향을 할당해주는지 살펴보도록 하겠습니다. 먼저 36개의 bin을 가진 히스토그램을 만듭니다. 360도의 방향을 0~9도, 10~19도, ... , 350~359도로 10도씩 36개로 쪼갭니다. 그래서 만약 28도면 3번째 bin을 그레디언트 크기만큼 채우는 것입니다. 여기서 그레디언트 크기는 가중처리된 값입니다. 윈도우 내의 모든 픽셀에서 그레디언트 방향의 값을 이런 식으로 해당하는 bin에 채워줍니다. 그러면 그레디언트 방향에 대한 히스토그램이 완성됩니다. 그림 15를 보시죠. 

 

그림 15. keypoint 주변 그레디언트 방향 히스토그램 [1]

 

이런 히스토그램이 만들어집니다. 가장 높은 bin의 방향이 keypoint의 방향으로 할당됩니다. 또 여기서 만약 가장 높은 bin의 80% 이상의 높이를 갖는 bin이 있다면 그 방향도 keypoint의 방향으로 인정됩니다. 한 keypoint가 두 개의 keypoint로 분리되는 것입니다. 마찬가지로 만약 3개의 bin이 가장 높은 bin의 80% 이상의 높이를 갖는다면 총 4개의 keypoint로 분리됩니다. 이제 keypoint들에 방향을 할당해주는 작업이 완료되었습니다. 

 

 

6. 최종적으로 SIFT 특징들 산출하기

 

지금까지 keypoint들을 결정해왔습니다. 우리는 지금 keypoint들의 위치와 스케일과 방향을 알고 있습니다. 그리고 keypoint들은 스케일과 회전 불변성을 갖고 있습니다. 이제 이 keypoint들을 식별하기 위해 지문(fingerprint)과 같은 특별한 정보를 각각 부여해줘야 합니다. 각각의 keypoint의 특징을 128개의 숫자로 표현을 합니다. 이를 위해 keypoint 주변의 모양변화에 대한 경향을 파악합니다. keypoint 주변에 16x16 윈도우를 세팅하는데, 이 윈도우는 작은 16개의 4x4 윈도우로 구성됩니다. 그림 16을 확인하세요.

 

그림 16. 하나의 keypoint의 특징을 나타내는 128개의 숫자를 얻는 과정

 

16개의 작은 윈도우에 속한 픽셀들의 그레디언트 크기와 방향을 계산해줍니다. 전 단계에서 했던 것과 비슷하게 히스토그램을 만들어주는데, 이번에는 bin을 8개만 세팅합니다. 360도를 8개로 쪼갤 것입니다. 0~44, 45~89, 90~134, ... , 320~359로 나눠지겠죠? 역시 그레디언트 방향와 크기를 이용해서 bin들을 채웁니다. 결국 16개의 작은 윈도우들 모두 자신만의 히스토그램을 갖게 됩니다. 16개의 작은 윈도우마다 8개의 bin값들이 있으므로, 16 x 8을 해주면 128개의 숫자(feature vector)를 얻게 됩니다. 이것이 바로 keypoint의 지문이 되는 것입니다. 아직 회전 의존성과 밝기 의존성을 해결해야합니다. 128개의 숫자로 구성된 feature vector는 이미지가 회전하면 모든 그레디언트 방향은 변해버립니다. 회전 의존문제를 해결하기 위해 keypoint의 방향을 각각의 그레디언트 방향에서 빼줍니다. 그러면 각각의 그레디언트 방향은 keypoint의 방향에 상대적이게 됩니다.  (예를 들어 keypoint의 방향을 구한 것이 20-29도라면 24.5도를 keypoint 주변 16개의 4x4 윈도우의 방향에서 빼주면, 16개의 윈도우의 방향은 keypoint 방향에 상대적이게 됩니다.) 그리고 밝기 의존성을 해결해주기 위해서 정규화를 해줍니다. 이렇게 최종적으로 keypoint들에게 지문(fingerprint)을 할당해줬습니다. 이것이 바로 SIFT 특징입니다. 

 

그렇다면 SIFT로 이미지 매칭을 어떻게 하는 것일까요? 먼저 두 이미지에서 각각 keypoint들을 찾고 지문을 달아줬다면, 이 지문값들의 차이가 가장 작은 곳이 서로 매칭되는 위치인 것입니다. 

 

 

▶ 글을 마무리 하며..

 

이제 글을 마무리하려고 합니다. 여전히 부족함이 많다는 생각이 드네요. 이해가 안 되는 부분에 대해서는 질문을 남겨주시면, 좀 더 공부해서라도 답변해드리도록 노력하겠습니다. 또 잘 아시는 분은 지적해주시기 부탁드립니다. 긴 글을 보시느라 수고 많으셨습니다! 

 

 

이 글도 한번 읽어보세요 ☞

Harris 코너 검출기의 이해

서포트 벡터 머신(SVM)의 사용자로서 꼭 알아야할 것들-매개변수 C와 gamma

 

 

<참고 자료>

[1] http://aishack.in/tutorials/sift-scale-invariant-feature-transform-introduction/

[2] Lowe D G. Distinctive Image Features from Scale-Invariant Keypoints[M]. Kluwer Academic Publishers, 2004.

[3] https://salkuma.files.wordpress.com/2014/04/sifteca095eba6ac.pdf

 

 

  1. 이전 댓글 더보기
  2. 박영호이(가) 2020.03.21 05:10에 작성한 댓글:
    회전 불변성 쪽에서 이해가 잘 안되었는데 여러번 읽다보니 이해가 되네요 ㅎㅎ 잘읽고갑니다   주소   수정/삭제   답글
  3. Rotation이(가) 2020.04.09 20:20에 작성한 댓글:
    우선 좋은 설명 감사합니다. 정말 도움이 많이 되었고, 앞으로 계속 찾아오게 될 것 같습니다 ^^

    Rotation invariance와 관련된 질문이 있어서 댓글을 남깁니다.
    마지막 단계에서 4x4 window에서 각 window에 gradient들을 8개 bin으로 나눠 128 dim 벡터를 만들고, keypoint의 gradient 방향을 각 윈도우에 상쇄를 하면, keypoint와 window간의 상대적 회전 의존성은 없어지는 것은 알겠습니다.
    하지만, 만약 이미지를 시계방향 90도 돌리면, 그 4x4 window 자체가 모두 돌아가게 되는데요, 예를 들어
    1 2 3 4 ///////// 13 9 5 1
    5 6 7 8 ---------> 14 10 6 2
    9 10 11 12 ///// 15 11 7 3
    13 14 15 16 //// 16 12 8 4
    이런식으로 구성되면,
    1-2-3-4-5-6-7-8-...-16번 윈도우 순서대로 8개씩 빼와서 만든 벡터가
    13-9-5-1-14-10-6-2-...8-4번 순서대로 8개씩 빼오게 될텐데,
    그러면 128 dim vector가 바뀌게 되지 않나요?
    이러면 matching 과정에서 같은 keypoint라고 인식이 안될 것 같아 여쭤봅니다.
    감사합니다 ^^

      주소   수정/삭제   답글
    • 비스카이비전이(가) 2020.04.10 10:10 신고에 작성한 답글:
      질문감사합니다.^^
      시계방향으로 90도 돌리고 나면, 4 x 4 window들의 그레디언트 방향들도 모두 그에 따라 바뀌게 됩니다. 그래서 동일한 순서로 8개씩 빼와서 128 차원 벡터를 만들어도 전혀 문제 될 것이 없습니다. 이해가 안되시면 다시 질문 부탁드립니다. ㅎㅎ
        주소   수정/삭제
  4. 이재원이(가) 2020.04.16 23:51에 작성한 댓글:
    단계 5, 6에서 window 설정할때가 너무 어렵네요... key point가 이미지의 외곽쪽에 있다면 어떻게 처리하는게 좋을까요.. window를 최대한 작게 잡아도 3x3인데...   주소   수정/삭제   답글
  5. 이재원이(가) 2020.04.17 05:23에 작성한 댓글:
    답변 남겨주셔서 정말 감사드려요!

    혹시 나쁜 key point를 제거하는 단계에서 어떻게 하셨는지 좀 더 디테일하게 알려주실수 있나요?

    일단 낮은 contrast를 제거하는 부분에서 D(x헷) 식이나 테일러시리즈 식에 나와있는 x, x헷, D(x) xT이런것들이 무엇을 의미하는지와 코드상으로 미분구현은 어떻게 하셨는지 궁금해요! f(x+1,y)-f(x-1,y)로 하신건지...[-1,0,1]필터를 사용하신건지....

    이미지 자체를 x,y축으로 미분하는 방법 또한 가르쳐 주시면 감사하겠습니다. 저는 헤이시안 매트릭스에 들어가는 이미지 미분값을 소벨필터로 구한다음에 일정 threshold 넘어가면 key point로 짚었는데 점이 줄긴해도 뭔가...아닌 거 같은 느낌이 들어서요..

    알려주시면 감사하겠습니다!
      주소   수정/삭제   답글
  6. Lim이(가) 2020.05.06 01:19에 작성한 댓글:
    저희 학과 교수님보다 설명을 잘하시네요...ㅎ
    너무 이해가 잘됩니다!!감사합니다!!
      주소   수정/삭제   답글
  7. 컴공과이(가) 2020.06.07 05:22에 작성한 댓글:
    안녕하세요. dog를 2배,원본,1/2사이즈, 1/4 이렇게 여러 oct해서
    극값 이미지를 8장 얻었는데요 이걸 어떻게 쓰는건가요? 서로 무엇을 비교하거나 사용하는거죠?
      주소   수정/삭제   답글
  8. hyun이(가) 2020.07.06 19:13에 작성한 댓글:
    학부생이라 이해하는게 너무 어려웠는데 많은 도움 받고 갑니다! 감사해요   주소   수정/삭제   답글
  9. 총총이(가) 2020.08.12 13:57에 작성한 댓글:
    친절히 설명해주셔서 감사합니다! 정말 이해가 잘되었어요 감사합니다 ㅠㅠ!!   주소   수정/삭제   답글
  10. 익명이(가) 2020.09.17 11:21에 작성한 댓글:
  11. 전용차선이(가) 2021.01.12 10:27에 작성한 댓글:
    설명 감사합니다.
    비교하려는, 두 image가 size(scale)은 동일한 상태 입니다. 이 상황에서 두 image를 pixel 단위까지 정확히 matching 시킨 후, 차이점을 찾으려고 합니다. 이때
    1) Scale (size)은 동일하니, 속도 개선을 위해서 scale space 동작은 하지 않거나 가능한 scale변환이 적게 반영하면 될듯 합니다만.. 그렇다면.. SIFT 사용 시 어떤 parameter 를 조절 가능한가요?
    2) 가능한 많은 특징점을 검출하기 위해서는 조절 가능한 parameter가 있을까요?
      주소   수정/삭제   답글
  12. 전용차선이(가) 2021.01.12 10:27에 작성한 댓글:
    설명 감사합니다.
    비교하려는, 두 image가 size(scale)은 동일한 상태 입니다. 이 상황에서 두 image를 pixel 단위까지 정확히 matching 시킨 후, 차이점을 찾으려고 합니다. 이때
    1) Scale (size)은 동일하니, 속도 개선을 위해서 scale space 동작은 하지 않거나 가능한 scale변환이 적게 반영하면 될듯 합니다만.. 그렇다면.. SIFT 사용 시 어떤 parameter 를 조절 가능한가요?
    2) 가능한 많은 특징점을 검출하기 위해서는 조절 가능한 parameter가 있을까요?
      주소   수정/삭제   답글
    • 비스카이비전이(가) 2021.01.12 15:33 신고에 작성한 답글:
      코드를 살펴봐야할것같지만 이론상으로 보면 분명 조절가능합니다ㅎㅎ threshold 값들을 사용한 부분들도 있고 말씀하신대로 scale 을 다양하게 해준 부분도 있기 때문입니다   주소   수정/삭제
  13. hg이(가) 2021.02.09 15:58에 작성한 댓글:
    시그마 값을 '스케일' 인자로 해석하는 이유는 시그마 값을 달리 함에 따라 이미지의 특징점들의 크기에 대한 response가 다르기 떄문입니다. (ex. 큰 특징점들은 큰 시그마 값을 주었을 때 response가 크게 나타남). 따라서 다양한 시그마 인자를 줌에 따라 다양한 스케일의 특징점을 잡을 수 있게 됩니다.   주소   수정/삭제   답글
  14. 지나가던 복실이이(가) 2021.02.27 10:31 신고에 작성한 댓글:
    opencv 공부하는 중에 정말 도움이 되었습니다. 좋은 글 남겨주셔서 감사합니다!   주소   수정/삭제   답글
  15. 김김이(가) 2021.02.27 20:53에 작성한 댓글:
    게시글 정말 잘 봤습니다. 글을 보고 추가로 궁금한 점이 생겨 질문드립니다. 그림 9번에서 제가 정말 찾고 싶은(진짜 코너점으로 나와야 하는) 부분은 픽셀과 픽셀의 사이인 파란색 X위치인데 정수 화소로만 표현이 되니 빨간색의 부분으로 표현되기에 완벽하지 않아서 테일러근사를 이용해서 다시 표현해주는 것으로 이해하면 되나요? 그리고 한 픽셀에 밝기값 하나가 들어있고 스케일파라미터만 다르게 한 3가지 밝기값 중 가장 큰 것이 극대값이잖아요. 명백하게 한가지 값이 나올 텐데...방향 설정할때도 픽셀 사이가 특징점이라고 나오는데 픽셀과 픽셀 사이의 값을 다룬다는 것이 이해가 가지 않습니다 ㅠㅠ   주소   수정/삭제   답글
    • 비스카이비전이(가) 2021.03.01 13:14 신고에 작성한 답글:
      질문 감사합니다. 디지털 이미지는 이산적인 값들(1, 2, 3, 4, 5,...)을 다루다보니 진정한 극값의 위치는 이산적인 픽셀의 위치 사이일 가능성이 높습니다. 테일러 전개를 통해서 그 위치를 찾고자 하는 것입니다. 그러면 결과적으로 (15.324, 392.135)와 같이 keypoint의 좀 더 정확한 위치를 알 수 있겠죠. 그것들을 저장해놓은 후에 나쁜 keypoint들은 제거하고 남은 keypoint들에 방향을 할당해주는 방식으로 진행됩니다.   주소   수정/삭제
  16. wnajsldkf이(가) 2021.06.13 14:01에 작성한 댓글:
    교수님이 이 부분 강조하셨는데 머리속으로 정리가 안되어서 보러 왔습니다 정리해주셔서 감사합니다 :)   주소   수정/삭제   답글
  17. sol이(가) 2021.08.06 00:58에 작성한 댓글:
    안녕하세요 정말 잘 보고있습니다!! 혹시 코드로도 구현 하셨나요~~?   주소   수정/삭제   답글
  18. 딩딩기이(가) 2021.08.11 15:54에 작성한 댓글:
    어떻게하면 image scale를 image 절반으로 줄이거나 두배로 크게 할 수 있을까요?
    즉, 원본 image에 대해서 축소할려고 할때 어떤 kernel를 사용해야하구.. scale이 절반이 되게 줄인다고 하셨는데
    row*col=w 라고 할때 w에 대한 절반인지 아니면 row,col 각각에 대한 절반인지가 궁금합니다.
      주소   수정/삭제   답글
  19. Doro이(가) 2021.10.24 01:48에 작성한 댓글:
    학부과정에서 이해가 힘든 부분도 많았는데 이 글 보고 정말 도움 많이 받아갑니다 ㅠㅠ 앞으로 자주 방문할거같습니다. 정말정말 감사합니다!!   주소   수정/삭제   답글
  20. Petison이(가) 2022.01.23 11:11에 작성한 댓글:
    이렇게 깔끔하고 잘된 정리를 제공해주심으로써 저의 시간을 많이 아껴주시고 이해를 도와주셔서 진심으로 감사합니다!!   주소   수정/삭제   답글
  21. BaeZzang2이(가) 2022.05.18 10:32에 작성한 댓글:
    안녕하세요, 자세한 정리와 적절한 예시도 보여주시고 귀한 정보 공유 정말 감사드립니다. 덕분에 이해하는데 많은 도움이 되었습니다. 혹시 실례가 되지 않는다면 혼자 공부할 때 보려고, 출처를 남기고 퍼가도 괜찮을까요? (*외부나 블로그에 올리는 목적이 아니고 그냥 개인적으로 공부할 때 사용하려고 퍼가고 싶습니다.)   주소   수정/삭제   답글
이름
비밀번호
홈페이지
비밀여부
2017-05-22 12:14:22

▶호롭터와 파눔융합역이란?


호롭터와 파눔융합역은 모두 양안 비젼(두 눈을 사용해서 세상을 바라봄)에서 흔히 사용되는 용어들입니다. 우리의 두 눈은 수평으로 일정한 거리를 두고 분리되어 있습니다. 그래서 두 눈이 바라보는 장면은 거의 같지만 수평적으로 약간 다릅니다. 이 다름(시차, disparity)을 통해 뇌는 깊이에 대한 정보를 얻습니다. 


호롭터는 관찰자로부터 동일한 거리에 위치한 점들을 잇는 곡선을 의미합니다. 즉, 두 눈으로부터 같은 거리에 위치하여 디스패리티가 존재하지 않는 점들을 이은 곡선입니다. 따라서 호롭터 위에 있는 점들은 깊이감 없이 융합됩니다. 그리고 파눔융합역은 두 망막 이미지들이 하나로 융합될 수 있는 호롭터 주변의 구역을 말합니다. 호롭터 위를 제외한 나머지 파눔융합역에서는 디스패리티가 있기 때문에 깊이감을 가지면서 융합됩니다. 디스패리티가 너무 커서 파눔융합역을 벗어나면, 뇌는 융합에 실패하여 복시현상(diplopia)이 나타나고 눈은 상당한 피로를 느끼게 됩니다 [1][2][3]


그림1. 호롭터와 파눔융합역을 설명하는 그림 [1]

  


<참고 자료>

[1] http://www1.appstate.edu/~kms/classes/psy3203/Depth/horopter2.htm

[2] Lambooij M, Fortuin M, Heynderickx I, et al. Visual Discomfort and Visual Fatigue of Stereoscopic Displays: A Review[J]. Journal of Imaging Science & Technology, 2009, volume 53(3):30201-1-30201-14(14).

[3] Park J, Lee S, Bovik A C. 3D Visual Discomfort Prediction: Vergence, Foveation, and the Physiological Optics of Accommodation[J]. IEEE Journal of Selected Topics in Signal Processing, 2014, 8(3):415-427.


이름
비밀번호
홈페이지
비밀여부
2017-05-18 13:42:02

optic flow software 매트랩 소스코드는 [1]에서 다운로드 받으실 수 있습니다. 그리고 이 소프트웨어와 관련된 논문은 [2]입니다. [3] 논문에서 이 소프트웨어를 활용해서 디스패리티 맵을 산출한 것을 따라서 저도 디스패리티 맵을 산출해보려고 합니다. 소프트웨어를 통해 산출된 모션 벡터들에서 수평 성분을 수평 디스패리티로 사용했습니다.

 

▶ 소스코드 테스트

 

그림 1은 테스트에 사용된 이미지들[4]과 ground truth 디스패리티 맵과 산출된 디스패리티 맵을 보여줍니다. 밝을 수록 카메라로부터 가깝고, 어두울 수록 멀다는 것을 의미합니다. 이 소프트웨어를 활용해서 산출한 디스패리티 맵이 상당히 거리감을 잘 나타냄을 그림을 통해 알 수 있습니다.

 

그림1. 왼쪽 뷰, 오른쪽 뷰, ground truth disparity map, 산출된 disparity map

 

 

<참고 자료>

[1] http://ps.is.tue.mpg.de/person/black#tabs-code

[2] Secrets of optical flow estimation and their principles, Sun, D., Roth, S., and Black, M. J., IEEE Conf. on Computer Vision and Pattern Recog., CVPR, June 2010.

[3] J Park, S Lee, AC Bovik, 3d visual discomfort prediction: vergence, foveation, and the physiological optics of accommodation. IEEE J. Sel. Top. Sign. Process. 8(3), 415–427 (2014)

[4] http://vision.middlebury.edu/stereo/data/

이름
비밀번호
홈페이지
비밀여부
2017-05-16 19:56:59

GBVS는 visual saliency 모델 중 하나입니다. visual saliency 모델은 이미지 내 시각적으로 중요한 부분들이 어딘지를 나타내주는 알고리즘입니다. GBVS에 대한 이론적 설명은 링크를 참고해주세요. 그리고 GBVS의 매트랩 소스코드는 [1]에서 받을 수 있습니다. 또한 GBVS에 대한 original 논문은 [2]입니다.

 

 

소스코드 테스트

 

소스코드를 다운 받으시면 폴더 내에 readme.txt에 어떻게 사용하는지 구체적으로 자세히 나와있습니다. 혹시 잘 모르시겠다면 댓글 달아주세요. 네 장의 사진의 GBVS를 통해 얻은 saliency 맵은 그림1에 있습니다. saliency 맵에서 밝은 부분일 수록 시각적으로 중요하다고 판단한 부분입니다. 

 

그림1. gbvs로 얻은 saliency 맵들

 

첫번째 빨간 차가 있는 사진을 제외하고는 적절하게 시각적 중요도에 따라 saliency 맵들이 산출된 것 같습니다. GBVS로 얻은 saliency 맵만 봐서는 GBVS가 효과적인지 아닌지 판단하기 어려우니 다른 saliency 알고리즘도 사용해보겠습니다. 위에 소개드린 GBVS 매트랩 소스코드를 담은 폴더 내에 Itti가 제안한 알고리즘[3]의 소스코드도 포함되어 있습니다. Itti가 제안한 이 알고리즘은 최초의 visual saliency 계산 모델이라고 볼 수 있습니다. 그림2는 Itti 알고리즘을 사용해 얻은 saliency 맵들입니다. 

 

그림2. [3]이 제안하는 모델로 얻은 saliency 맵들

 

이 방법도 괜찮은 결과를 산출하는 것 같습니다. 개인적인 소견으로 사람이 있는 사진에서는 GBVS가 좀 더 나은 saliency 맵을 만들어내는 것 같고, 풍경 사진에서는 [3]이 좀 더 나은 결과를 내는 것 같습니다. 

 

 

<참고 자료>

[1] http://www.vision.caltech.edu/~harel/share/gbvs.php

[2] J. Harel, C. Koch, and P. Perona. "Graph-Based Visual Saliency", NIPS 2006

[3] L. Itti, C. Koch, & E. Niebur "A model of saliency based visual attention for rapid scene analysis", IEEE Transactions on Pattern Analysis and Machine 1998

 

  1. 익명이(가) 2019.05.08 19:56에 작성한 댓글:
  2. 익명이(가) 2019.05.29 10:16에 작성한 댓글:
  3. 익명이(가) 2019.10.08 14:42에 작성한 댓글:
이름
비밀번호
홈페이지
비밀여부
2017-05-16 00:51:07

오늘은 http://opencvexamples.blogspot.com/p/learning-opencv-functions-step-by-step.html에 있는 세번째 예제를 따라해보도록 하겠습니다. 

 

3. Basic drawing examples

1) 선 그리기

소스코드부터 보시죠. 

 

#include <opencv2/core/core.hpp>

#include <opencv2/highgui/highgui.hpp>

#include <opencv2/imgproc/imgproc.hpp>

 

using namespace cv;

 

int main()

{

// Create black empty images

Mat image = Mat::zeros(400, 400, CV_8UC3); // 세로, 가로 각각 400, 400 사이즈의 검정 이미지를 만든다. 

 

// Draw a line 

line(image, Point(15, 20), Point(300, 350), Scalar(0, 255, 0), 10, 8); 

imshow("Image", image);

imwrite("result.bmp", image);

 

waitKey(0);

return(0);

}

 

링크의 예제에서는 #include <opencv2/imgproc/imgproc.hpp>가 없는데, 이걸 포함해줘야 line 함수를 사용할 수 있습니다. line 함수에 대해서 아는 한 설명을 해보겠습니다. 

 

line(image, Point(15, 20), Point(300, 350), Scalar(0, 255, 0), 10, 8); 

  • 1번째 인수: 도화지가 되어줄 이미지를 넣어줍니다. 
  • 2번째, 3번째 인수: 어디서부터 어디까지 선을 그릴지 픽셀 위치들을 뜻합니다. 즉 (15, 20)부터 (300, 350)을 잇는 선을 그리겠다는 것입니다. 

  • 4번째 인수: 선의 색을 결정지어줍니다. Scalar(0, 255, 0)은 녹색 선을 그리겠다는 것입니다. BGR순서로 되어 있습니다. 만약 Scalar(0, 0, 255)라고 세팅-하면 빨간색 선이 될 것입니다. 
  • 5번째 인수: 선의 굵기를 선택합니다. 클수록 굵어집니다.
  • 6번째 인수: 라인의 타입을 결정해줍니다. (이것의 역할은 잘 몰라서, 일단 그냥 default값으로 놔두겠습니다.)

 

실행결과 아래와 같은 이미지가 전시되고, 저장됩니다.

 

 

2) 원 그리기

이번에는 원을 그려보도록 하겠습니다. 소스코드는 아래와 같습니다. 

 

#include <opencv2/core/core.hpp>

#include <opencv2/highgui/highgui.hpp>

#include <opencv2/imgproc/imgproc.hpp>

 

using namespace cv;

 

int main()

{

// Create black empty images

Mat image = Mat::zeros(400, 400, CV_8UC3); // 세로, 가로 각각 400, 400 사이즈의 검정 이미지를 만든다. 

 

// Draw a circle 

circle(image, Point(200, 200), 32.0, Scalar(0, 0, 255), 5, 8); 

  circle(image, Point(100, 300), 40.0, Scalar(255, 0, 255), 10, 8);

imshow("Image", image);

imwrite("result.bmp", image);

 

waitKey(0);

return(0);

}

 

circle 함수 

  • 1번째 인수: 역시 도화지가 되어줄 이미지입니다. 여기서는 400 x 400의 검정 이미지이겠지요? 
  • 2번째 인수: 원의 중심의 픽셀 위치입니다. 
  • 3번째 인수: 원의 반지름입니다. 
  • 4번째 인수: 원의 색깔로 역시 BGR순서로 되어 있습니다. 첫번째 원은 빨간 색이 되게 Scalar(0, 0, 255)로 설정했습니다. 
  • 5번째 인수: 원의 두께를 결정해주는 것입니다. 클수록 두꺼워집니다. 
  • 6번째 인수: 라인의 타입을 결정해주는 것입니다. 

 

line 함수의 인수들의 진행패턴과 크게 다르지 않습니다. 아래 그림은 결과로 그려진 두 개의 원을 보여줍니다. 

 

 

3) 타원 그리기

이번에는 타원을 그리겠습니다. 

 

#include <opencv2/core/core.hpp>

#include <opencv2/highgui/highgui.hpp>

#include <opencv2/imgproc/imgproc.hpp>

 

using namespace cv;

 

int main()

{

// Create black empty images

Mat image = Mat::zeros(400, 400, CV_8UC3); // 세로, 가로 각각 400, 400 사이즈의 검정 이미지를 만든다. 

 

// Draw a ellipse

        ellipse(image, Point(200, 200), Size(100.0, 160.0), 45, 0, 360, Scalar(255, 0, 0), 1, 8);

ellipse(image, Point(200, 200), Size(100.0, 160.0), 0, 0, 360, Scalar(255, 0, 0), 5, 8);

ellipse(image, Point(200, 200), Size(100.0, 160.0), 135, 0, 360, Scalar(255, 0, 0), 10, 8);

imshow("Image", image);

imwrite("result.bmp", image);

 

waitKey(0);

return(0);

}

 

ellipse 함수가 타원을 그릴 때 사용하는 함수입니다. 

  • 1번째 인수: 도화지가 될 이미지입니다. 
  • 2번째 인수: 타원의 중심위치입니다. 
  • 3번째 인수: 타원축들의 길이를 의미합니다. 즉, 세번째 인수가 Size(100,0, 160,0)라면 x축으로는 100만큼, y축으로는 160만큼 긴 모양의 타원을 만들겠다는 것입니다. 
  • 4번째 인수: 타원이 회전된 각도입니다. 첫번째 타원의 경우 45도만큼 오른쪽으로 회전시키겠다는 것입니다. 
  • 5번째, 6번째 인수: 타원이 시작하는 각도와 끝나는 각도를 결정지어주는 것인데 완전한 타원을 그리고 싶다면 0, 360으로 설정해주면 됩니다. 
  • 7번째 인수: 색을 결정해주는 것입니다. 세 타원 모두 파란색으로 설정해줬습니다. 
  • 8번째 인수: 타원의 굵기를 결정해주는 것입니다. 크면 클수록 굵어집니다. 
  • 9번째 인수: 역시 선의 타입을 결정해주는 것입니다. 

 

아래 그림은 결과적으로 그려진 3개의 타원을 보여줍니다. 뭔가 멋있네요. 

 

 

4) 직사각형 그리기

직사각형 그리는 코드입니다.

 

#include <opencv2/core/core.hpp>

#include <opencv2/highgui/highgui.hpp>

#include <opencv2/imgproc/imgproc.hpp>

 

using namespace cv;

 

int main()

{

// Create black empty images

Mat image = Mat::zeros(400, 400, CV_8UC3); // 세로, 가로 각각 400, 400 사이즈의 검정 이미지를 만든다. 

 

// Draw a rectangle

rectangle(image, Point(15, 20), Point(70, 50), Scalar(0, 55, 255), +1, 4); 

rectangle(image, Point(200, 300), Point(300, 350), Scalar(100, 100, 0), -1, 8); 

imshow("Image", image);

imwrite("result.bmp", image);

 

waitKey(0);

return(0);

}

 

직사각형을 그려주는 rectangle 함수를 살펴봅시다. 

  • 1번째 인수: 도화지가 되어줄 이미지입니다.
  • 2번째 인수: 직사각형의 왼쪽 상단 모서리 픽셀 위치입니다.
  • 3번째 인수: 직사각형의 오른쪽 하단 모서리 픽셀 위치입니다.
  • 4번째 인수: 색을 결정해줍니다.
  • 5번째 인수: 양수면 빈 직사각형, 음수면 채워진 직사각형을 만듭니다.
  • 6번째 인수: 라인의 타입을 결정해줍니다. 

 

구현된 결과이미지입니다. 하나의 빈 직사각형과 하나의 채워진 직사각형을 보실 수 있습니다.

 

 

 

5) 다각형 그리기

이번에는 채워져있는 다각형을 그려보겠습니다. 

 

#include <opencv2/core/core.hpp>

#include <opencv2/highgui/highgui.hpp>

#include <opencv2/imgproc/imgproc.hpp>

 

using namespace cv;

 

int main()

{

// Create black empty images

Mat image = Mat::zeros(400, 400, CV_8UC3); // 세로, 가로 각각 400, 400 사이즈의 검정 이미지를 만든다. 

 

int w = 400;

/** Create some points */

Point rook_points[1][20]; // 여기서 rook는 체스의 말의 이름입니다.

rook_points[0][0] = Point(w / 4.0, 7 * w / 8.0);

rook_points[0][1] = Point(3 * w / 4.0, 7 * w / 8.0);

rook_points[0][2] = Point(3 * w / 4.0, 13 * w / 16.0);

rook_points[0][3] = Point(11 * w / 16.0, 13 * w / 16.0);

rook_points[0][4] = Point(19 * w / 32.0, 3 * w / 8.0);

rook_points[0][5] = Point(3 * w / 4.0, 3 * w / 8.0);

rook_points[0][6] = Point(3 * w / 4.0, w / 8.0);

rook_points[0][7] = Point(26 * w / 40.0, w / 8.0);

rook_points[0][8] = Point(26 * w / 40.0, w / 4.0);

rook_points[0][9] = Point(22 * w / 40.0, w / 4.0);

rook_points[0][10] = Point(22 * w / 40.0, w / 8.0);

rook_points[0][11] = Point(18 * w / 40.0, w / 8.0);

rook_points[0][12] = Point(18 * w / 40.0, w / 4.0);

rook_points[0][13] = Point(14 * w / 40.0, w / 4.0);

rook_points[0][14] = Point(14 * w / 40.0, w / 8.0);

rook_points[0][15] = Point(w / 4.0, w / 8.0);

rook_points[0][16] = Point(w / 4.0, 3 * w / 8.0);

rook_points[0][17] = Point(13 * w / 32.0, 3 * w / 8.0);

rook_points[0][18] = Point(5 * w / 16.0, 13 * w / 16.0);

rook_points[0][19] = Point(w / 4.0, 13 * w / 16.0);

 

const Point* ppt[1] = { rook_points[0] };

int npt[] = { 20 };

 

fillPoly(image, ppt, npt, 1, Scalar(255, 255, 255), 8);

imshow("Image", image);

imwrite("result.bmp", image);

 

waitKey(0);

return(0);

}

 

fillPoly함수의 인수들을 살펴봅시다. 

  • 1번째 인수: 도화지가 될 이미지입니다.
  • 2번째 인수: 꼭지점들을 담고 있는 배열입니다. (Array of polygons where each polygon is represented as an array of points)
  • 3번째 인수: 꼭지점들의 갯수를 담고 있는 배열입니다. (Array of polygon vertex counters)
  • 4번째 인수: 색으로 채워진 지역을 감싸는 가장자리의 갯수를 설정해줍니다. (Number of contours that bind the filled region)
  • 5번째 인수: 다각형의 색을 설정합니다.
  • 6번째 인수: 라인의 타입을 결정해줍니다.

 

아래 그림과 같이 체스의 rook가 그려집니다.

 

 

이번에는 fillPoly 함수를 사용해서 다각형 중의 하나인 삼각형을 하나 그려보겠습니다. 코드는 아래와 같습니다.

 

#include <opencv2/core/core.hpp>

#include <opencv2/highgui/highgui.hpp>

#include <opencv2/imgproc/imgproc.hpp>

 

using namespace cv;

 

int main()

{

// Create black empty images

Mat image = Mat::zeros(400, 400, CV_8UC3); // 세로, 가로 각각 400, 400 사이즈의 검정 이미지를 만든다. 

 

int w = 400;

/** Create some points */

Point rook_points[1][3]; // 여기서 rook는 체스의 말의 이름입니다.

rook_points[0][0] = Point(w / 2.0, w / 4.0); // Point(가로 픽셀 위치, 세로 픽셀 위치)

rook_points[0][1] = Point(w / 4.0, 3 * w / 4.0);

rook_points[0][2] = Point(3 * w / 4.0, 3 * w / 4.0);

 

const Point* ppt[1] = { rook_points[0] };

int npt[] = { 3 };

 

fillPoly(image, ppt, npt, 1, Scalar(255, 255, 255), 8);

imshow("Image", image);

imwrite("result.bmp", image);

 

waitKey(0);

return(0);

}

 

삼각형을 그리므로 세 점만 있으면 됩니다. 그려진 그림은 아래와 같습니다.

 

 

 

6) 이미지에 텍스트 넣기

이제 마지막으로 이미지에 텍스트 넣는 것을 따라해보겠습니다. 

 

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
 
using namespace cv;
 
int main()
{
// Create black empty images
Mat image = Mat::zeros(400, 400, CV_8UC3); // 세로, 가로 각각 400, 400 사이즈의 검정 이미지를 만든다. 
 
putText(image, "God is good!", Point(50, 100), FONT_HERSHEY_PLAIN, 2, Scalar(0, 200, 200), 3); //이미지에 텍스트 넣기
imshow("Image", image);
imwrite("result.bmp", image);
 
waitKey(0);
return(0);
}
 
putText 함수를 살펴봅시다.
  • 1번째 인수: 도화지가 될 이미지입니다.
  • 2번째 인수: 텍스트 내용을 적어줍니다.
  • 3번째 인수: 이미지에서 텍스트가 배치될 위치를 결정해줍니다. 텍스트 스트링의 왼쪽 아래 위치가 됩니다. 
  • 4번째 인수: 폰트를 선택합니다. FONT_HERSHEY_SIMPLEX, FONT_HERSHEY_PLAIN,FONT_HERSHEY_DUPLEX, FONT_HERSHEY_COMPLEX, FONT_HERSHEY_TRIPLEX,FONT_HERSHEY_COMPLEX_SMALL, FONT_HERSHEY_SCRIPT_SIMPLEX, FONT_HERSHEY_SCRIPT_COMPLEX
  • 5번째 인수: 글자의 크기를 결정합니다. 
  • 6번째 인수: 글자의 색을 선택합니다.
  • 7번째 인수: 글자의 두께를 선택합니다. 
실행결과를 보시죠.

 

이번 예제는 조금 길었네요. 그리기 함수들을 이용해서 집을 하나 그려보겠습니다. 코드는 아래와 같습니다.
 

#include <opencv2/core/core.hpp>

#include <opencv2/highgui/highgui.hpp>

#include <opencv2/imgproc/imgproc.hpp>

 

using namespace cv;

 

int main()

{

// Create black empty images

Mat image = Mat::zeros(400, 400, CV_8UC3); // 세로, 가로 각각 400, 400 사이즈의 검은색 이미지를 만든다. 

 

rectangle(image, Point(0, 0), Point(399, 399), Scalar(255, 255, 255), -1, 8); // 흰색 도화지로 바꿔준다. 

 

// 큰 지붕, 다각형 함수 활용

int w = 400;

Point roof1_points[1][4];

roof1_points[0][0] = Point(140, 40);   

roof1_points[0][1] = Point(260, 40);

roof1_points[0][2] = Point(360, 160);

roof1_points[0][3] = Point(40, 160);

 

const Point* ppt1[1] = { roof1_points[0] };

int npt1[] = { 4 };

fillPoly(image, ppt1, npt1, 1, Scalar(30, 30, 30), 8);

 

// 건물의 몸통. 직사각형 함수 활용

rectangle(image, Point(70, 161), Point(330, 340), Scalar(150, 150, 150), -1, 8);

rectangle(image, Point(70, 341), Point(330, 360), Scalar(30, 30, 30), -1, 8);

 

// 작은 지붕

Point roof2_points[1][4];

roof2_points[0][0] = Point(160, 100);

roof2_points[0][1] = Point(240, 100);

roof2_points[0][2] = Point(300, 172);

roof2_points[0][3] = Point(100, 172);

 

const Point* ppt2[1] = { roof2_points[0] };

int npt2[] = { 4 };

fillPoly(image, ppt2, npt2, 1, Scalar(60, 60, 60), 8);

 

// 중간에 회색 두 줄

rectangle(image, Point(150, 173), Point(160, 340), Scalar(100, 100, 100), -1, 8);

rectangle(image, Point(240, 173), Point(250, 340), Scalar(100, 100, 100), -1, 8);

 

//굴뚝

Point chimney_points[1][4];

chimney_points[0][0] = Point(70, 60);

chimney_points[0][1] = Point(92, 60);

chimney_points[0][2] = Point(92, 97);

chimney_points[0][3] = Point(70, 124);

 

const Point* ppt3[1] = { chimney_points[0] };

int npt3[] = { 4 };

fillPoly(image, ppt3, npt3, 1, Scalar(60, 60, 60), 8);

 

//굴뚝 덮개 

rectangle(image, Point(67, 48), Point(95, 59), Scalar(30, 30, 30), -1, 8);

 

//문

rectangle(image, Point(172, 252), Point(228, 340), Scalar(70, 70, 90), -1, 8);

 

//문에 있는 창문 두 개

rectangle(image, Point(180, 264), Point(190, 288), Scalar(255, 200, 150), -1, 8);

rectangle(image, Point(210, 264), Point(220, 288), Scalar(255, 200, 150), -1, 8);

 

//문에 있는 점 두 개, 원 그리기 활용

circle(image, Point(190, 300), 2, Scalar(30, 30, 30), 2, 8);

circle(image, Point(210, 300), 2, Scalar(30, 30, 30), 2, 8);

 

//중간 원형 창문의 테두리

circle(image, Point(200, 210), 18, Scalar(55, 55, 55), 10, 8);

 

//중간 원형 창문

circle(image, Point(200, 210), 8, Scalar(255, 200, 150), 18, 8);

 

//중간 원형 창문 십자

rectangle(image, Point(199, 190), Point(201, 230), Scalar(55, 55, 55), -1, 8);

rectangle(image, Point(180, 209), Point(220, 211), Scalar(55, 55, 55), -1, 8);

 

//왼쪽 상단 창문 회색 틀

rectangle(image, Point(85, 240), Point(135, 247), Scalar(55, 55, 55), -1, 8);

 

//왼쪽 상단 창문 검은 틀

Point win1_points[1][4];

win1_points[0][0] = Point(88, 248);

win1_points[0][1] = Point(132, 248);

win1_points[0][2] = Point(127, 255);

win1_points[0][3] = Point(93, 255);

 

const Point* win1_ppt[1] = { win1_points[0] };

int win1_npt[] = { 4 };

fillPoly(image, win1_ppt, win1_npt, 1, Scalar(30, 30, 30), 8);

 

//왼쪽 상단 창문

rectangle(image, Point(88, 190), Point(132, 239), Scalar(255, 200, 150), -1, 8);

 

//왼쪽 상단 창문 십자

rectangle(image, Point(88, 215), Point(132, 217), Scalar(55, 55, 55), -1, 8);

rectangle(image, Point(109, 190), Point(111, 239), Scalar(55, 55, 55), -1, 8);

 

//오른쪽 상단 창문 회색 틀

rectangle(image, Point(265, 240), Point(315, 247), Scalar(55, 55, 55), -1, 8);

 

//오른쪽 상단 창문 검은 틀

Point win2_points[1][4];

win2_points[0][0] = Point(268, 248);

win2_points[0][1] = Point(312, 248);

win2_points[0][2] = Point(307, 255);

win2_points[0][3] = Point(273, 255);

 

const Point* win2_ppt[1] = { win2_points[0] };

int win2_npt[] = { 4 };

fillPoly(image, win2_ppt, win2_npt, 1, Scalar(30, 30, 30), 8);

 

//오른쪽 상단 창문

rectangle(image, Point(268, 190), Point(312, 239), Scalar(255, 200, 150), -1, 8);

 

//오른쪽 상단 창문 십자

rectangle(image, Point(268, 215), Point(312, 217), Scalar(55, 55, 55), -1, 8);

rectangle(image, Point(289, 190), Point(291, 239), Scalar(55, 55, 55), -1, 8);

 

//왼쪽 하단 창문 회색 틀

rectangle(image, Point(85, 315), Point(135, 322), Scalar(55, 55, 55), -1, 8);

 

//왼쪽 하단 창문 검은 틀

Point win3_points[1][4];

win3_points[0][0] = Point(88, 323);

win3_points[0][1] = Point(132, 323);

win3_points[0][2] = Point(127, 330);

win3_points[0][3] = Point(93, 330);

 

const Point* win3_ppt[1] = { win3_points[0] };

int win3_npt[] = { 4 };

fillPoly(image, win3_ppt, win3_npt, 1, Scalar(30, 30, 30), 8);

 

//왼쪽 하단 창문

rectangle(image, Point(88, 265), Point(132, 314), Scalar(255, 200, 150), -1, 8);

 

//왼쪽 하단 창문 십자

rectangle(image, Point(88, 290), Point(132, 292), Scalar(55, 55, 55), -1, 8);

rectangle(image, Point(109, 265), Point(111, 314), Scalar(55, 55, 55), -1, 8);

 

//오른쪽 하단 창문 회색 틀

rectangle(image, Point(265, 315), Point(315, 322), Scalar(55, 55, 55), -1, 8);

 

//오른쪽 하단 창문 검은 틀

Point win4_points[1][4];

win4_points[0][0] = Point(268, 323);

win4_points[0][1] = Point(312, 323);

win4_points[0][2] = Point(307, 330);

win4_points[0][3] = Point(273, 330);

 

const Point* win4_ppt[1] = { win4_points[0] };

int win4_npt[] = { 4 };

fillPoly(image, win4_ppt, win4_npt, 1, Scalar(30, 30, 30), 8);

 

//오른쪽 하단 창문

rectangle(image, Point(268, 265), Point(312, 314), Scalar(255, 200, 150), -1, 8);

 

//오른쪽 하단 창문 십자

rectangle(image, Point(268, 290), Point(312, 292), Scalar(55, 55, 55), -1, 8);

rectangle(image, Point(289, 265), Point(291, 314), Scalar(55, 55, 55), -1, 8);

 

 

imshow("Image", image);

imwrite("result.bmp", image);

 

 

waitKey(0);

return(0);

}

 

구현된 이미지는 밑에 있습니다. 노가다였지만 나름 성취감이 있네요. ㅋㅋ 

 

 

 

 

이름
비밀번호
홈페이지
비밀여부
2017-05-12 17:40:00

오늘은 http://opencvexamples.blogspot.com/p/learning-opencv-functions-step-by-step.html에 있는 두번째 예제를 공부해보겠습니다!

 

2. Capture video from camera

 

카메라를 작동시켜 촬영된 비디오를 캡쳐하는 방법입니다. 우선 코드부터 보시죠. 위 링크에서 소개하고 있는 예제에 좀 더 덧붙여 캡쳐한 비디오를 그레이영상으로 전환한 것도 함께 출력하도록 코드를 변경했습니다.

 

#include "opencv2/opencv.hpp"

using namespace cv;

 

int main(int, char**)

{

VideoCapture cap(0); // open the default camera

if (!cap.isOpened())  // check if we succeeded

return -1;

 

namedWindow("Video", 1);

        namedWindow("Video1", 1);

 

while (1) // 무한루프

{

Mat frame, gray_frame;

cap >> frame;         // 카메라로부터 새 프레임을 얻는다. 

cvtColor(frame, gray_frame, COLOR_BGR2GRAY);    // 얻은 프레임을 그레이이미지로 변환한다. 

imshow("Video1", frame);    // 컬러영상 전시

imshow("Video", gray_frame);    // 그레이 영상 전시

 

if (waitKey(30) == 'c') break;    // c를 타이핑해줘야 무한루프 탈출! 

}

 

return 0;

}

 

소스코드에서 VideoCapture cap(0);에 있는 VideoCapture는 동영상 캡쳐를 위한 클래스라고 하네요. 이 코드를 실행해보면, default camera로 설정되어 있는 웹캠에서 찍히고 있는 장면이 전시됩니다. 아래는 영상으로 출력되고 있는 장면을 스샷찍은 장면입니다. 

 

 

 

 

 

이름
비밀번호
홈페이지
비밀여부
2017-05-12 16:55:28

당분간 http://opencvexamples.blogspot.com/p/learning-opencv-functions-step-by-step.html에 있는 초보자들을 위한 예제들을 따라하면서 opencv를 공부하려고 합니다. 총 13개의 예제로 구성되어 있습니다. 참고로 영문사이트입니다. 오늘은 첫번째 예제를 공부해보겠습니다. :D

 

1. Load, Display and Save an image

 

첫번째 예제는 이미지를 불러와서 전시하고 저장하는 방법을 담고 있습니다. 우선 코드를 아래에 첨부하겠습니다. 저는 opencv 3.2.0 버전과 visual studio 2017을 사용했습니다.

 

main.cpp
다운로드

 

여기서 주요한 함수들을 살펴보겠습니다.

 

먼저 imread는 이미지를 불러오는 함수입니다. 저는 tsuL.png를 불러왔습니다. image = imread("tsuL.png", CV_LOAD_IMAGE_COLOR)에서 CV_LOAD_IMAGE_COLOR는 이미지를 컬러로 불러와서 image변수에 대입하겠다는 뜻입니다. 이 자리에 쓰일 수 있는 것은 4가지가 있습니다.

 

CV_LOAD_IMAGE_ANYDEPTH - return 16-bit/32-bit image when the input has the corresponding depth, otherwise convert it to 8-bit.

CV_LOAD_IMAGE_COLOR (>0) - If set, always convert image to the color one.

CV_LOAD_IMAGE_GRAYSCALE (0) - If set, always convert image to the grayscale one.

CV_LOAD_IMAGE_UNCHANGED (<0) - loads the image as is (including the alpha channel if present)

 

CV_LOAD_IMAGE_GRAYSCALE은 그레이영상으로 이미지를 변환해서 불러온다는 것이고, CV_LOAD_IMAGE_UNCHANGED는 있는 그대로 불러온다는 의미인 것 같습니다. CV_LOAD_IMAGE_ANYDEPTH는 잘 모르겠네요.. ㅎㅎ (아시는 분 계시면 댓글 부탁드립니다!)

 

imshow는 이미지를 전시해주는 함수입니다. imshow("window", image)에서 "window"라고 쓰여진 이 부분은 사진을 전시해주는 창의 이름을 의미합니다. 코드 실행결과 아래와 같은 이미지가 전시됩니다. 만약에 imshow("test_image", image)라고 코딩했다면 아래 출력된 이미지에서 창의 이름이 test_image가 되었을 것입니다.

 

imwrite는 이미지를 저장해주는 함수입니다. imwrite("result.jpg", image)는 이미지를 result.jpg로 저장해준다는 것입니다. 폴더를 보면 result.jpg가 생성되었음을 확인할 수 있습니다.

 

 

 

이름
비밀번호
홈페이지
비밀여부