[OpenCV] 2. OpenCV 기초

OpenCV 기초

1.MAT

예전 책들 뒤져보면 전부 lpllImage등의 구조체를 이용하고 있다.
3.0이상부터 이 구조체를 이용하는 C언어 인터페이스가 아닌
MAT클래스를 이용하는 c++인터페이스로 발전하였고, 내부적으로 많은 변화가 있다.

  • 매트릭스 클래스
  • 이미지, value등 모든 데이타가 매트릭스에 존재.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main()
{
//행, 열, 데이타 타입 순이다.
Mat mtx(3, 3, CV_32F); //3x3 32비트 float 타입의 매트릭스
Mat cmtx(10, 1, CV_64FC2); //10x1 2ch floating
Mat img(Size(5, 3), CV_8UC3); //3ch image

//생성후 선언
Mat mtx2;
mtx2 = Mat(3, 3, CV_32F);
Mat cmtx2;
cmtx2 = Mat(10, 1, CV_64FC1);

//포인터 이용
Mat* mtx3 = new Mat(3, 3, CV_32F);
delete mtx3;

//값 설정후 출력
mtx.setTo(10);
cout << mtx << endl;

cmtx.setTo(11);
cout << cmtx << endl;

return 0;
}
//콘솔 출력값
/*
[10, 10, 10;
10, 10, 10;
10, 10, 10]
[11, 11;
11, 11;
11, 11;
11, 11;
11, 11;
11, 11;
11, 11;
11, 11;
11, 11;
11, 11]

*/

2. MAT 연산

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;


int main()
{
Mat m = Mat::ones(3, 3, CV_64F);
m = m * 3;
cout << m << endl;

double dm[3][3] = {
{1,2,1},
{0,1,1},
{1,0,0}
};
Mat m2 = Mat(3, 3, CV_64F, dm); //4번째 인자에 배열을 넣어서 똑같은 형태의 매트릭스 클래스 생성
cout << m2 << endl;

return 0;
}
//콘솔출력
/*
[3, 3, 3;
3, 3, 3;
3, 3, 3]
[1, 2, 1;
0, 1, 1;
1, 0, 0]
*/

사칙연산 테스트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;


int main()
{
Mat m = Mat::ones(3, 3, CV_64F);
m = m * 3;
//cout << m << endl;

double dm[3][3] = {
{1,2,1},
{0,1,1},
{1,0,1}
};
Mat m2 = Mat(3, 3, CV_64F, dm);
//cout << m2 << endl;

cout << m + m2 << endl << endl; //덧셈 - 각 요소끼리 덧셈
cout << m - m2 << endl << endl; //뺼셈 - 각 요소끼리 뺄셈
cout << m * m2 << endl << endl; //곱셈 - 행렬곱! 각 요소끼리 곱셈을 하기위해선 밑처럼 mul()이용.
cout << m.mul(m2) << endl << endl; //곱셈 - 각 요소끼리 곱셈! 을 위해서 mul()사용
cout << m / m2 << endl << endl; //나눗셈 - 각 요소끼리 나눗셈
return 0;
}
//콘솔출력
/*
[4, 5, 4;
3, 4, 4;
4, 3, 4]

[2, 1, 2;
3, 2, 2;
2, 3, 2]

[6, 9, 9;
6, 9, 9;
6, 9, 9]

[3, 6, 3;
0, 3, 3;
3, 0, 3]

[3, 1.5, 3;
0, 3, 3;
3, 0, 3]

*/

위의 코드를 보았듯이 행렬의 곱은 각 요소의 곱이 아닌 행렬곱으로 정의되어있으며 각 요소끼리 곱셈이 필요한 경우 mul()을 이용한다. 위에서 주의할 점은 m2처럼 배열을 이용해서 Mat을 만든경우 Mat의 값을 변경시 배열의 값이 변하며, 반대로 배열의 값을 변경시 Mat의 값도 변한다는 점이다. m2가 배열 dm을 참조하고 있기 때문이다.

역행렬

숫자에 있어서 곱셈 항등원인 1을 만드는 역수가 존재하듯이, 행렬에도 단위행렬을 만드는 역행렬이 존재한다. A행렬의 역행렬을 B라고 하였을때 A와 B를 행렬곱하면 단위행렬인 E가 나온다. 숫자 a의 역수를$$a^{-1}$$ 로 나타내듯이 행렬 A에 대한 역행렬은 $$A^{-1}$$ 로 나타낸다. 이 역행렬을 구하는 방법은?

1
2
Mat m;
m.inv(); //행과 열이 같지 않은 경우 에러 발생

전치행렬

전치 행렬(transposed matrix)은 행과 열을 교환하여 얻는 행렬로 대각선을 주축으로 하는 반사 대칭을 가하여 얻는 행렬로 $$A^T$$ 로 표현한다.

1
2
Mat m;
m.t();

2.Mat을 이용한 이미지 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main()
{
Mat img = imread("anr.png"); //img라는 클래스 생성
namedWindow("wimg", 0); //wimg 창이름 가진 창 생성

//그냥 출력하면 심심하니 sobel edge 필터 적용
Sobel(img, img, img.depth(), 1, 0);

imshow("wimg", img); //show

//창이 열렸다 바로 닫아지면 안되니깐
waitKey(0); //ms숫자만큼 기다림. 1000이면 1초, 0이면 무한대로 wait

destroyAllWindows(); //모든창 closed
return 0;
}

실행하면 윈도우 창에 해당 이미지가 보여지며 키를 누르면 창이 닫힌다. 이게 가장 기본적인 활용 방법이다.

3.Mat 접근방법

  • At 접근
    • 장점: 가장 안정적
    • 단점: 4가지 방법중 가장 속도가 느림
  • ptr 접근
    • 장점: 위의 At 방법보다 빠름
    • 단점: 밑의 data방법보다 느리고, 활용하기가 까다롭다.
  • data 접근
    • 장점: 가장 빠름
    • 단점: 오류검증 안됨. 주소오류등의 에러시 굉장히 치명적.
  • iterator 접근

소스로 보는 예

At접근

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
Mat image (ROW, COL, CV_TYPE);
image.at (WANT_ROW, WANT_COL);

- ROW: Row
- COL: Column
- CV_TYPE: data type ( for example : CV_8UC3 = 8 bit 3 channels)
- DATA_TYPE: Mat creation data type ( for example : float, usigned char)
- WANT_ROW: access to the desired row
- WANT_COL: access to the desired column
*/


//at접근 방법 예
//img.at< 원하는 자료형>(row좌표,col좌표)[채널(여기선 rgb 3ch라 0~2)]
//Vec3b means 'uchar 3ch'
unsigned char b = img.at< cv::Vec3b>(i, j)[0];
unsigned char g = img.at< cv::Vec3b>(i, j)[1];
unsigned char r = img.at< cv::Vec3b>(i, j)[2];

//printf("%d %d %d\n", b, g, r);
//반전부분
img.at< cv::Vec3b>(i, j)[0] = unsigned char(255 - b); //b
img.at< cv::Vec3b>(i, j)[1] = unsigned char(255 - g); //g
img.at< cv::Vec3b>(i, j)[2] = unsigned char(255 - r); //r

ptr접근

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*
Mat image (ROW, COL, CV_TYPE);
image.ptr (WANT_ROW, WANT_COL); (This access is changed to the Point)

- ROW: Row
- COL: Column- CV_TYPE: data type ( for example : CV_8UC3 = 8 bit 3 channels)
- DATA_TYPE: Mat creation data type ( for example : float, usigned char)
- WANT_ROW: access to the desired row
- WANT_COL: access to the desired column
*/
//using ptr
for (int i = img.rows / 10 * 5; i < img.rows / 10 * 6; i++) {

//포인터 받아오는 부분 유심히.
cv::Vec3b* ptr = img.ptr< cv::Vec3b >(i);

for (int j = 0; j < img.cols; j++) {

unsigned char b1 = (ptr[j][0]);
unsigned char g1 = (ptr[j][1]); //받아온 ptr에서 바로 컬럼과 채널로 접근.
unsigned char r1 = (ptr[j][2]);


cv::Vec3b bgr = ptr[j]; //ptr에서 컬럼에 해당하는 rgb를 받아와서
unsigned char b2 = (bgr[0]); //채널로만 접근
unsigned char g2 = (bgr[1]); //일반 배열에서 2차원 배열 접근방식 생각하면서 소스 보면 금방 감옴
unsigned char r2 = (bgr[2]);

//반전부분
ptr[j] = cv::Vec3b(255 - b1, 255 - g1, 255 - r1);

}
}

data접근 : 1차원 배열 접근 방법과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
Mat image (ROW, COL, CV_TYPE);
DATA_TYPE * data = (DATA_TYPE *) image.data;data [WANT_ROW * image.cols + WANT_COL]

- ROW: Row
- COL: Column
- CV_TYPE: data type ( for example : CV_8UC3 = 8 bit 3 channels)
- DATA_TYPE: Mat creation data type ( for example : float, usigned char)
- WANT_ROW: access to the desired row
- WANT_COL: access to the desired column
*/
//using data
for (int i = img.rows / 10 * 7; i < img.rows / 10 * 8; i++) {
for (int j = 0; j < img.cols; j++) {

unsigned char r, g, b;

b = img.data[i * img.step + j * img.elemSize() + 0];
g = img.data[i * img.step + j * img.elemSize() + 1];
r = img.data[i * img.step + j * img.elemSize() + 2];

//반전부분
img.data[i * img.step + j * img.elemSize() + 0] = unsigned char(255 - b);
img.data[i * img.step + j * img.elemSize() + 1] = unsigned char(255 - g);
img.data[i * img.step + j * img.elemSize() + 2] = unsigned char(255 - r);

}
}

iterator 접근: cv안의 MatIterator를 사용.

1
2
3
4
5
6
7
8
9
10
11

cv::MatIterator_< cv::Vec3b> itd = img.begin< cv::Vec3b>(), itd_end = img.end< cv::Vec3b>();

for (int i = 0; itd != itd_end; ++itd, ++i) {

cv::Vec3b bgr = (*itd);

(*itd)[0] = 255 - bgr[0];
(*itd)[1] = 255 - bgr[1];
(*itd)[2] = 255 - bgr[2];
}

4. Vector 2 Mat, Mat 2 Vector

c++에서 많이쓰게 되는 STL의 벡터로의 전환 그리고 그 반대에 대해 알아보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

printf("/////////////////////////////////////////////////////////////\n");
printf("//vector to Mat\n");
int r=3;
int c=4;

vector< float> Vf;

//insert value 초기화
int cnt=0;
for(int i=0; i< c; ++i)
for(int j=0; j< r; ++j)
Vf.push_back(cnt++);

//Mat 생성
Mat M=Mat(r,c,CV_32FC1); //vector 크기와 동일한 mat생성

//vector to mat 부분.
//M.data : MAt의 값을 나타내는 배열의 첫번째 포인터
memcpy(M.data,Vf.data(),Vf.size()*sizeof(float));

//print Mat
cout < < M < < endl;


printf("/////////////////////////////////////////////////////////////\n");
printf("//Mat to vector\n");
vector< float> Vf2;
//copy mat to vector부분
Vf2.assign((float*)M.datastart, (float*)M.dataend);

//confirm
cnt=0;
for(int i=0; i< c; ++i)
{
for(int j=0; j< r; ++j)
printf("%lf ", Vf2[cnt++]);
printf("\n");
}

Related POST

공유하기