5. 행렬 이론 및 실습
5.1. 기본 배열 처리 함수
컬러 영상
- 파란색(B), 녹색(G), 빨간색(R)의 각기 독립적인 2차원 정보
- 2차원 정보 3개를 갖는 컬러 영상을 표현 -> 3차원 배열 사용
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image = imread("../image/flip_test.jpg", IMREAD_COLOR); // 컬러 읽기 모드
CV_Assert(image.data);
Mat x_axis, y_axis, xy_axis, rep_img, trans_img;
flip(image, x_axis, 0); // 상하 반전
flip(image, y_axis, 1); // 좌우 반전
flip(image, xy_axis, -1); // 상하좌우 반전
// Mat repeat(InputArray src, int ny, int nx);
// src: 반복할 행렬
// ny: y방향 반복 횟수
// nx: x방향 반복 횟수
// return: 반복 복사된 행렬
repeat(image, 1, 2, rep_img); // 가로 방향 2번 반복 복사
transpose(image, trans_img); // 행렬 전치
imshow("image", image);
imshow("x_axis", x_axis);
imshow("y_axis", y_axis);
imshow("xy_axis", xy_axis);
imshow("rep_img", rep_img);
imshow("trans_img", trans_img);
// 응용 실습
repeat(image, 2, 2, rep_img);
int i = 0, j = 0;
// 하나의 행에 대해
for (j = 0; j < image.rows; j++)
// 컬러는 열로만 이루어짐
// 컬러는 3개의 채널(B, G, R)이 있기 때문에 3칸씩 움직임
for (i = 0; i < image.cols * 3; i+=3) {
// image.cols * 3: 원래 영상
// 우측 상단은 좌측 기준부터 image.cols * 3만큼 떨어짐
rep_img.at<uchar>(j, i + image.cols * 3) = x_axis.at<uchar>(j, i);
rep_img.at<uchar>(j, i + image.cols * 3 + 1) = x_axis.at<uchar>(j, i + 1);
rep_img.at<uchar>(j, i + image.cols * 3 + 2) = x_axis.at<uchar>(j, i + 2);
}
for (j = 0; j < image.rows; j++)
for (i = 0; i < image.cols * 3; i += 3) {
// 좌측 하단은 좌측 기준부터 image.rows만큼 떨어짐
rep_img.at<uchar>(j + image.rows, i) = y_axis.at<uchar>(j, i);
rep_img.at<uchar>(j + image.rows, i + 1) = y_axis.at<uchar>(j, i + 1);
rep_img.at<uchar>(j + image.rows, i + 2) = y_axis.at<uchar>(j, i + 2);
}
for (j = 0; j < image.rows; j++)
for (i = 0; i < image.cols * 3; i += 3) {
// 우측 하단은 좌측 기준부터 image.rows, image.cols * 3만큼 떨어짐
rep_img.at<uchar>(j + image.rows, i + image.cols * 3) = xy_axis.at<uchar>(j, i);
rep_img.at<uchar>(j + image.rows, i + image.cols * 3 + 1) = xy_axis.at<uchar>(j, i + 1);
rep_img.at<uchar>(j + image.rows, i + image.cols * 3 + 2) = xy_axis.at<uchar>(j, i + 2);
}
// imshow("image", image);
imshow("rep_img", rep_img);
waitKey();
return 0;
}
5.2. 채널 처리 함수
void merge(): 여러 개의 단일채널 배열로 다중 채널의 배열을 합성한다.
- Mat* mv: 합성될 입력 배열 혹은 벡터, 합성될 단일채널 배열들의 크기와 깊이가 동일해야 함
- size_t count: 합성될 배열의 개수, 0보다 커야 함
- OutputArray dst: 입력 배열과 같은 크기와 같은 깊이의 출력 배열
void split(): 다중 채널 배열을 여러 개의 단일채널 배열로 분리한다.
- Mat& src: 입력되는 다중 채널 행렬
- Mat* mvbegin: 분리되어 반환되는 단일채널 행렬을 원소로 하는 배열
- InputArray m: 입력되는 다중 채널 배열
- InputArrayOfArrays mv: 분리되어 반환되는 단일채널 배열들의 벡터 혹은 베열
void mixChannels(): 명시된 채널의 순서쌍에 의해 입력 배열들(src)로부터 출력 배열들(dst)의 복사한다.
- Mat* src: 입력 배열 혹은 행렬 벡터
- size_t nsrcs: 입력 배열(src)의 행렬 개수
- Mat* dst: 입력 배열 혹은 행렬 벡터
- size_t ndsts: 출력 배열(dst)의 행렬 개수
- int* fromTo: 입력과 출력의 순서쌍 배열
- size_t npairs: 순서쌍의 개수
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat ch0(3, 4, CV_8U, Scalar(10));
Mat ch1(3, 4, CV_8U, Scalar(20));
Mat ch2(3, 4, CV_8U, Scalar(30));
Mat bgr_arr[] = { ch0, ch1, ch2 };
Mat bgr;
merge(bgr_arr, 3, bgr);
vector<Mat> bgr_vec;
split(bgr, bgr_vec);
cout << "[ch0] = " << endl << ch0 << endl;
cout << "[ch1] = " << endl << ch1 << endl;
cout << "[ch2] = " << endl << ch2 << endl << endl;
cout << "[bgr] = " << endl << bgr << endl << endl;
cout << "[bgr_vec[0]] = " << endl << bgr_vec[0] << endl;
cout << "[bgr_vec[1]] = " << endl << bgr_vec[1] << endl;
cout << "[bgr_vec[2]] = " << endl << bgr_vec[2] << endl;
return 0;
}
5.3. 실습
컬러 영상 파일에서 채널 분리하기
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image = imread("../image/color.jpg", IMREAD_COLOR);
CV_Assert(image.data);
Mat bgr[3];
split(image, bgr);
imshow("image", image);
imshow("blue 채널", bgr[0]);
imshow("green 채널", bgr[1]);
imshow("red 채널", bgr[2]);
waitKey(0);
return 0;
}
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat ch0(3, 4, CV_8U, Scalar(10));
Mat ch1(3, 4, CV_8U, Scalar(20));
Mat ch2(3, 4, CV_8U, Scalar(30));
Mat ch_012;
vector<Mat> vec_012;
vec_012.push_back(ch0);
vec_012.push_back(ch1);
vec_012.push_back(ch2);
merge(vec_012, ch_012); // ch_012: CV_8UC3
Mat ch_13(ch_012.size(), CV_8UC2);
Mat ch_2(ch_012.size(), CV_8UC1);
Mat out[] = { ch_13, ch_2 };
int from_to[] = { 0,0, 2,1, 1,2 };
Mat ch_13(ch_012.size(), CV_8UC3);
Mat ch_2(ch_012.size(), CV_8UC1);
Mat out[] = { ch_13, ch_2 };
int from_to[] = { 0,0, 2,1, 1,2, 0,1 };
// ch_012를 ch_13과 ch_2로 나누기
mixChannels(&ch_012, 1, out, 2, from_to, 3); // ch_012 행렬은 1개, out의 행렬의 개수는 2개, 0번->0번/2번->1번/1번->2번의 3가지
cout << "[ch_123] = " << endl << ch_012 << endl << endl;
cout << "[ch_13] = " << endl << ch_13 << endl;
cout << "[ch_2] = " << endl << ch_2 << endl;
return 0;
}
5.4. 사칙연산
void add(): 두 개의 배열이나 배열과 스칼라의 각 원소 간 합을 계산한다.
입력 인수 src1, src2 중 하나는 스칼라값일 수 있다.
- InputArray src1: 첫 번째 입력 배열 혹은 스칼라
- InputArray src2: 두 번째 입력 배열 혹은 스칼라
- OutputArray dst: 계산된 결과의 출력 배열
- InputArray mask: 연산 마스크 - 마스크가 0이 아닌 좌표만 연산 수행(8비트 단일채널)
- int dtype: 출력 배열의 깊이
void subtract(): 두 개의 배열이나 배열과 스칼라의 각 원소 간 차분을 계산한다.
add() 함수와 인수 동일
void multiply(): 두 배열의 각 원소 간 곱을 계산한다.
- double scale: 원소 간의 곱할 때 추가로 곱해지는 배율
void divide(): 두 배열의 각 원소 간 나눗셈을 수행한다.
void scaleAdd(): 스케일된 배열과 다른 배열의 합을 계산한다.
- double alpha: 첫 번째 배열의 모든 원소들에 대한 배율
void addweighted(): 두 배열의 가중된 합을 계산한다.
- double alpha: 첫 번째 배열의 원소들에 대한 가중치
- double beta: 두 번째 배열의 원소들에 대한 가중치
- double gamma: 두 배열의 합에 추가로 더해지는 스칼라
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat m1(3, 6, CV_8UC1, Scalar(10));
Mat m2(3, 6, CV_8UC1, Scalar(50));
Mat m_add1, m_add2, m_sub, m_div1, m_div2;
Mat mask(m1.size(), CV_8UC1); // 마스크 행렬 - 8비트 단일채널
mask.setTo(0);
// Point(3, 0)은 보통 행렬과 다르게
// (3, 0) 값은 좌측 기준에서 오른쪽으로 3만큼 떨어짐
// Size(3, 3)은 3 x 3만큼
Rect rect(Point(3, 0), Size(3, 3)); // 관심영역 지정
// rect 사각형 영역만큼 mask 행렬에서 참조하여 원소값을 1로 지정
mask(rect).setTo(1);
add(m1, m2, m_add1);
// mask 1인 영역만 덧셈 수행
add(m1, m2, m_add2, mask);
divide(m1, m2, m_div1);
// 형변환 - 소수부분 보존
m1.convertTo(m1, CV_32F);
m2.convertTo(m2, CV_32F);
divide(m1, m2, m_div2);
cout << "[m1] = " << endl << m1 << endl;
cout << "[m2] = " << endl << m2 << endl;
cout << "[mask] = " << endl << mask << endl << endl;
cout << "[m_add1] = " << endl << m_add1 << endl;
cout << "[m_add2] = " << endl << m_add2 << endl;
cout << "[m_div1] = " << endl << m_div1 << endl;
cout << "[m_div2] = " << endl << m_div2 << endl;
return 0;
}
5.5. 지수 로그 루트 관련 함수
void exp(): 모든 배열 원소의 지수를 계산한다.
- InputArray src: 입력 배열
- OutputArray dst: 입력 배열과 같은 크기의 타입의 출력 배열
void log(): 모든 배열 원소의 절댓값에 대한 자연 로그를 계산한다.
void sqrt(): 모든 배열 원소에 대해 제곱근을 계산한다.
void pow(): 모든 배열 원소에 대해서 power 승수를 계산한다.
- double power: 제곱 승수
void magnitude(): 2차원 벡터들의 크기를 계산한다.
- InputArray x: 벡터의 x좌표들의 배열
- InputArray y: 벡터의 y좌표들의 배열
- OutputArray magnitude: 입력 배열과 같은 크기의 출력 배열
void phase(): 2차원 벡터의 회전 각도를 계산한다.
- InputArray x: 벡터의 x좌표들의 배열
- InputArray y: 벡터의 y좌표들의 배열
- OutputArray angle: 벡터 각도들의 출력 배열
- bool angleInDegrees: true - 각을 도(degree)로 측정, false - 각을 라디안(radian)으로 측정
void cartToPolar(): 2차원 벡터들의 크기와 각도를 계산한다.
void polarToCart(): 각도와 크기로부터 2차원 벡터들의 좌표를 계산한다.
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
vector<float> v1, v_exp, v_log;
Matx <float, 1, 5> m1(1, 2, 3, 5, 10);
Mat m_exp, m_sqrt, m_pow;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
exp(v1, v_exp); // 벡터에 대한 지수 계산
exp(m1, m_exp); // 행렬에 대한 지수 계산
log(m1, v_log); // 입력은 행렬, 출력은 벡터
sqrt(m1, m_sqrt);
pow(m1, 3, m_pow);
cout << "[m1] = " << m1 << endl << endl;
cout << "[v_exp] = " << ((Mat)v_exp).reshape(1, 1) << endl << endl; // 벡터를 행렬로 변환 후, 1채널 1행으로 변환
cout << "[m_exp] = " << m_exp << endl << endl;
cout << "[v_log] = " << ((Mat)v_log).reshape(1, 1) << endl << endl;
cout << "[m_sqrt] = " << m_sqrt << endl << endl;
cout << "[m_pow] = " << m_pow << endl << endl;
return 0;
}
5.6. 논리(비트) 연산
NOT: 각 자릿수의 값을 반대로 바꾸는 연산
OR: 두 값의 각 자릿수를 비교해, 둘 중 하나라도 1이 있다면 1, 아니면 0
XOR: 두 값의 각 자릿수를 비교해, 값이 0으로 같으면 0, 값이 1로 같으면 0, 다르면 1
AND: 두 값의 각 자릿수를 비교해, 두 값 모두에 1이 있을 때만 1, 나머지는 0
void bitwise_and(): 두 배열의 원소 간 혹은 배열 원소와 스칼라 간에 비트 간 논리곱(AND) 연산을 수행한다.
- InputArray src1: 첫 번째 입력 배열 혹은 스칼라값
- InputArray src2: 두 번째 입력 배열 혹은 스칼라값
- OutputArray dst: 입력 배열과 같은 크기의 출력 배열
- InputArray mask: 마스크 연산 - 마스크의 원소가 0이 아닌 좌표만 계산 수행
void bitwise_or(): 두 배열의 원소 간 혹은 배열 원소와 스칼라 간에 비트 간 논리합(OR) 연산을 수행한다.
void bitwise_xor(): 두 개의 배열 원소 간 혹은 배열 원소와 스칼라 간에 비트 간 배타적 논리합(OR) 연산을 수행한다.
void bitwise_not(): 입력 배열의 모든 원소에 대해서 각 비트의 역을 계산한다.
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image1(250, 250, CV_8U, Scalar(0));
Mat image2(250, 250, CV_8U, Scalar(0));
Mat image3, image4, image5, image6;
Point center = image1.size() / 2;
circle(image1, center, 80, Scalar(255), -1); // 중심에 원 그리기(마지막 인자를 -1로 주면, 내부가 채워짐)
rectangle(image2, Point(0, 0), Point(125, 250), Scalar(255), -1);
// 비트 연산
bitwise_or(image1, image2, image3);
bitwise_xor(image1, image2, image4);
bitwise_and(image1, image2, image5);
bitwise_not(image1, image6);
imshow("image1", image1);
imshow("image2", image2);
imshow("bitwise_or", image3);
imshow("bitwise_xor", image4);
imshow("bitwise_and", image5);
imshow("bitwise_not", image6);
waitKey();
return 0;
}
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image = imread("../image/bit_test.jpg", IMREAD_COLOR);
Mat logo = imread("../image/logo.jpg", IMREAD_COLOR);
CV_Assert(image.data && logo.data); // 예외처리
Mat logo_th, masks[5], background, foreground, dst; // 결과 행렬
threshold(logo, logo_th, 70, 255, THRESH_BINARY); // 로고 영상 이진화
split(logo_th, masks); // 로고영상 채널 분리
// imshow("image", image);
// 로고를 3개의 컬러로 분리
// imshow("masks[0]", masks[0]); // blue
// imshow("masks[1]", masks[1]); // green
// imshow("masks[2]", masks[2]); // red
// imshow("logo", logo);
// 흰색 글자는 RGB가 모두 가지고 있기 때문에 어느 영역에서든 포함
bitwise_or(masks[0], masks[1], masks[3]); // 전경통과 마스크(blue 컬러와 green 컬러의 합)
// imshow("masks[3] - 1", masks[3]);
bitwise_or(masks[2], masks[3], masks[3]); // 마스크(blue 컬러와 green 컬러의 합에서 다시 한 번 더 red 컬러의 합)
imshow("masks[3] - 2", masks[3]);
bitwise_not(masks[3], masks[4]); // 배경통과 마스크(모든 컬러의 반전)
imshow("masks[4]", masks[4]);
Point center1 = image.size() / 2; // 영상 중심좌표
Point center2 = logo.size() / 2;; // 로고 중심좌표
Point start = center1 - center2;
Rect roi(start.x, start.y, logo.cols, logo.rows);
// Rect roi(start, logo.size());
//비트곱과 마스킹을 이용한 관심 영역의 복사
bitwise_and(logo, logo, foreground, masks[3]); // 컬러가 True인 경우만 출력
bitwise_and(image(roi), image(roi), background, masks[4]);
imshow("foreground", foreground);
imshow("background", background);
// 로고 전경과 원본 배경의 합성
bitwise_or(foreground, background, dst);
imshow("dst", dst);
// 합성 영상을 image 관심영역에 복사
add(background, foreground, dst);
dst.copyTo(image(roi));
imshow("image", image);
waitKey();
return 0;
}
5.7. 심화 실습 1
컬러 영상 파일(logo.jpg)을 입력 받아서 RGB의 3개 채널을 분리하고, 각 채널의 컬러영상을 윈도우에 표기
즉, Red 채널은 빨간색으로, Green 채널은 초록색으로, Blue 채널은 파란색으로 표현
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image = imread("../image/logo.jpg", 1);
// zero는 아무것도 없는 빈 채널
Mat bgr[3], blue_img, red_img, green_img, zero(image.size(), CV_8U, Scalar(0));
split(image, bgr);
imshow("bgr[0]", bgr[0]);
imshow("bgr[1]", bgr[1]);
imshow("bgr[2]", bgr[2]);
Mat b_ch[] = { bgr[0], zero, zero }; // blue 컬러만 1, 나머지는 0
Mat g_ch[] = { zero, bgr[1], zero }; // green 컬러만 1, 나머지는 0
Mat r_ch[] = { zero, zero, bgr[2] }; // red 컬러만 1, 나머지는 0
merge(b_ch, 3,blue_img);
merge(g_ch, 3, green_img);
merge(r_ch, 3, red_img);
imshow("image", image), imshow("blue_img", blue_img);
imshow("green_img", green_img), imshow("red_img", red_img);
waitKey();
}
5.8. 원소의 절댓값
MatExpr abs(): 행렬의 각 원소에 대한 절댓값을 계산하여 수식을 위한 행렬인 MatExprr 객체로 반환한다.
void absdiff(): 두 배열간 각 원소 간(per-element) 차분의 절댓값을 계산한다. src1, src2 중 하나는 스칼라값이 될 수 있다.
- InputArray src1: 두 번째 입력 배열
- InputArray src2: 첫 번째 입력 배열
- OutputArray dst: 계산된 결과 출력 배열
void convertScaleAbs(): 입력 배열의 각 원소에 alpha만큼 배열을 곱하고 beta만큼 더한 후에 절댓값을 계산한 결과를 8비트 자료형으로 변환한다.
- double alpha: 입력 배열의 각 원소에 곱해지는 스케일 벡터
- double beta: 스케일된 값에 더해지는 델타 옵션
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image1 = imread("../image/abs_test1.jpg", 0);
Mat image2 = imread("../image/abs_test2.jpg", 0);
CV_Assert(image1.data && image2.data);
Mat dif_img, abs_dif1, abs_dif2;
image1.convertTo(image1, CV_16S);
image2.convertTo(image2, CV_16S);
subtract(image1, image2, dif_img); // 감산
Rect roi(10, 10, 7, 3);
cout << "[dif_img] = " << endl << dif_img(roi) << endl;
abs_dif1 = abs(dif_img);
image1.convertTo(image1, CV_8U);
image2.convertTo(image2, CV_8U);
dif_img.convertTo(dif_img, CV_8U);
abs_dif1.convertTo(abs_dif1, CV_8U);
absdiff(image1, image2, abs_dif2);
cout << "[dif_img] = " << endl << dif_img(roi) << endl << endl;
cout << "[abs_dif1] = " << endl << abs_dif1(roi) << endl;
cout << "[abs_dif2] = " << endl << abs_dif2(roi) << endl;
imshow("image1", image1);
imshow("image2", image2);
imshow("dif_img", dif_img);
imshow("abs_dif1", abs_dif1);
imshow("abs_dif2", abs_dif2);
waitKey();
return 0;
}
5.9. 원소의 최소값과 최대값
void min() / max(): 두 입력 행렬을 원소 간 비교하여 작은값을 출력 행렬로 반환한다.
- InputArray src1, InputArray src2: 두 개의 입력 배열
- OutputArray dst: 계산 결과 출력 배열(행렬 및 벡터)
- Mat& dst: 계산 결과 출력 행렬
MatExpr min() / max(): 행렬의 원소와 스칼라를 비교하여 작은값을 출력 행렬로 반환한다.
- Mat& a: 첫 번째 행렬
- double s: 스칼라값
void manMaxIdx(): 전체 배열에서 최솟값과 최댓값인 원소의 위치와 그 값을 반환한다.
- InputArray src: 단일채널 입력 배열
- double* minVal, double* maxVal: 최솟값과 최대값 원소의 값 반환
- int* minIdx, int* maxIdx: 최솟값과 최댓값 원소의 위치를 배열로 반환
- InputArray mask: 연산 마스크
void minMaxLoc(): 전체 배열에서 최댓값과 최솟값을 갖는 원소의 위치와 그 값을 반환한다. 위치를 Point형으로 반환한다.
- Point* minLoc: 최솟값인 원소의 위치를 Point 객체로 반환
- Point* maxLoc: 최댓값인 원소의 위치를 Point 객체로 반환
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
uchar data[] = {
10, 200, 5, 7, 9,
15, 35, 60, 80, 170,
100, 2, 55, 37, 70
};
Mat m1(3, 5, CV_8U, data);
Mat m2(3, 5, CV_8U, Scalar(50));
Mat m_min, m_max; // 최소값 행렬, 최대값 행렬
double minVal, maxVal;
int minIdx[2] = {}, maxIdx[2] = {}; // 최소값 좌표, 최대값 좌표
Point minLoc, maxLoc;
min(m1, 30, m_min); // 두 행렬 원소간 최소값 저장
max(m1, m2, m_max); // 두 행렬 최대값 계산
minMaxIdx(m1, &minVal, &maxVal, minIdx, maxIdx);
minMaxLoc(m1, 0, 0, &minLoc, &maxLoc);
cout << "[m1] = " << endl << m1 << endl << endl;
cout << "[m_min] = " << endl << m_min << endl;
cout << "[m_max] = " << endl << m_max << endl << endl;
cout << "m1 행렬 원소 최소값 : " << minVal << endl;
cout << " 최소값 좌표 : " << minIdx[1] << ", " << minIdx[0] << endl;
cout << "m1 행렬 원소 최대값 : " << maxVal << endl;
cout << " 최대값 좌표 : " << maxIdx[1] << ", " << maxIdx[0] << endl << endl;
cout << "m1 행렬 최소값 좌표: " << minLoc << endl;
cout << "m1 행렬 최대값 좌표 " << maxLoc << endl;
return 0;
}
5.10. 심화 실습 2
영상 파일을 읽어서 최소값/최대값 계산하기 -> 영상 개선
최소값은 0으로 변환, 최대값은 255로 변환
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image = imread("../image/minMax.jpg", IMREAD_GRAYSCALE);
double minVal, maxVal;
minMaxIdx(image, &minVal, &maxVal);
double ratio = (maxVal - minVal) / 255.0;
Mat dst = (image - minVal) / ratio; // 영상에서 숫자를 적용해줌으로써 밝기값이 변해짐
// (x - min) * 255 / (max - min)
// x = min이면, 0 x 255 = 0
// x = max이면, 1 x 255 = 255로 늘어남
cout << "최소값 = " << minVal << endl;
cout << "최대값 = " << maxVal << endl;
imshow("image", image);
imshow("dst", dst);
waitKey();
return 0;
}
5.11. 통계 관련 함수
Scalar sum(): 배열의 각 채널 별로 원소들의 합 N을 계산하여 스칼라값으로 반환한다.
- InputArray src: 1개에서 4개 채널을 갖는 입력 배열(행렬 or 벡터)
Scalar mean(): 배열의 각 채널별로 원소들의 평균을 계산하여 스칼라값으로 반환한다.
- InputArray src: 1개에서 4개 채널을 갖는 입력 배열
- InputArray mask: 연산 마스크 - 마스크가 0이 아닌 좌표만 연산 수행
void meanStdDev(): 배열 원소들의 평균과 표준편차를 계산한다.
- InputArray src: 1개에서 4개 채널을 갖는 입력 배열
- OutputArray mean: 계산된 평균이 반환되는 출력 인수, CV_64F형으로 반환
- OutputArray stddev: 계산된 표준편차가 반환되는 출력 인수, CV_64F형으로 반환
- InputArray mask: 연산 마스크 - 마스크가 0이 아닌 좌표만 연산 수행
int countNonZero(): 0이 아닌 배열 원소를 개수 N을 반환한다.
void reduce(): 행렬을 열방향 혹은 행방향으로 옵션상수의 연산을 수행하여 벡터로 감축한다.
- InputArray src: 2차원 입력 배열(CV_32F, CV_64F 타입만 수행 가능)
- OutputArray dst: 출력 벡터, 감소방향과 타입은 dim, dtype 인수에 따라 정해짐
- int dim: 행렬이 감축될 때 차원 감소 인덱스
1) 0: 1행으로 감축
2) 1: 1열로 감축
- int rtype: 감축 연산 종류
1) REDUCE_SUM(0): 행렬의 모든 행(열)들을 합한다.
2) REDUCE_AVG(1): 행렬의 모든 행(열)들을 평균한다.
3) REDUCE_MAX(3): 행렬의 모든 행(열)들의 최댓값을 구한다.
4) REDUCE_MIN(4): 행렬의 모든 행(열)들의 최솟값을 구한다.
- int dtype: 감소된 벡터의 자료형
void sort(): 행렬의 각 행 혹은 각 열의 방향으로 정렬한다.
- InputArray src: 단일채널 입력 배열
- OutputArray dst: 정렬된 출력 배열
- int flags: 연산 플래그 - 다음의 상수를 조합해서 정렬 방식 구성
1) SORT_EVERY_ROW(0): 각 행을 독립적으로 정렬
2) SORT_EVERY_COLUMN(1): 각 열을 독립적으로 정렬
3) SORT_ASCENDING(0): 오츰차순으로 정렬
4) SORT_DESCENDING(16): 내림차순으로 정렬
void sortIdx(): 행렬의 각 행 혹은 각 열로 정렬한다. 출력 배열(dst)에 정렬된 원소의 인덱스들을 저장한다.
인수는 sort()와 동일하다.
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image = imread("../image/sum_test.jpg", 1);
CV_Assert(image.data);
Mat mask(image.size(), CV_8U, Scalar(0));
mask(Rect(20, 40, 70, 70)).setTo(255);
Scalar sum_value = sum(image); // BGR 채널별로 합
Scalar mean_value1 = mean(image); // BGR 채널별로 평균
Scalar mean_value2 = mean(image, mask);// 마스크 1 영역만 평균 계산
cout << "[sum_value] = " << sum_value << endl;
cout << "[mean_value1] = " << mean_value1 << endl;
cout << "[mean_value2] = " << mean_value2 << endl << endl;
Mat mean, stddev;
meanStdDev(image, mean, stddev);
cout << "[mean] = " << mean << endl;
cout << "[stddev] = " << stddev << endl << endl;
meanStdDev(image, mean, stddev, mask);
cout << "[mean] = " << mean << endl;
cout << "[stddev] = " << stddev << endl;
imshow("image", image), imshow("mask", mask);
waitKey();
return 0;
}
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat_<uchar> m1(3, 5);
m1 << 21, 15, 10, 9, 14,
6, 10, 15, 9, 7,
7, 12, 8, 14, 1;
Mat m_sort1, m_sort2, m_sort3;
cv::sort(m1, m_sort1, SORT_EVERY_ROW); // 행으로 오름차순
cv::sort(m1, m_sort2, SORT_EVERY_ROW + SORT_DESCENDING); // 행으로 내림차순
cv::sort(m1, m_sort3, SORT_EVERY_COLUMN); // 열로 오름차순
cout << "[m1] = " << endl << m1 << endl << endl;
cout << "[m_sort1] = " << endl << m_sort1 << endl << endl;
cout << "[m_sort2] = " << endl << m_sort2 << endl << endl;
cout << "[m_sort3] = " << endl << m_sort3 << endl;
return 0;
}
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat_<uchar> m1(3, 5);
m1 << 21, 15, 10, 9, 14,
6, 10, 15, 9, 7,
7, 12, 8, 14, 1;
Mat m_sort_idx1, m_sort_idx2, m_sort_idx3;
sortIdx(m1, m_sort_idx1, SORT_EVERY_ROW);
sortIdx(m1, m_sort_idx2, SORT_EVERY_COLUMN);
cout << "[m1] = " << endl << m1 << endl << endl;
cout << "[m_sort_idx1] = " << endl << m_sort_idx1 << endl << endl;
cout << "[m_sort_idx2] = " << endl << m_sort_idx2 << endl << endl;
return 0;
}
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Matx<ushort, 5, 4> pts; // 1, 2열은 앞점, 3, 4열은 뒷점
Mat_<int> sizes, sort_idx;
vector<Rect> rects;
randn(pts, Scalar(200), Scalar(100)); // 평균 200, 표준편차 100인 랜덤값
cout << "----------------------------------------" << endl;
cout << " 랜덤 사각형 정보 " << endl;
cout << "----------------------------------------" << endl;
for (int i = 0; i < pts.rows; i++)
{
Point pt1(pts(i, 0), pts(i, 1)); // 사각형 시작좌표
Point pt2(pts(i, 2), pts(i, 3)); // 사각형 종료좌표
rects.push_back(Rect(pt1, pt2)); // 벡터 저장
sizes.push_back(rects[i].area()); // 사각형 크기 저장
cout << format("rects[%d] = ", i) << rects[i] << endl;
}
// 정렬 후, 정렬 원소의 원본 좌표 반환(열 단위의 오름차순 정렬 후 원본 좌표 반환)
// sizes의 값을 열 방향으로 오름차순 정렬 후, sort_idx에 저장
sortIdx(sizes, sort_idx, SORT_EVERY_COLUMN);
cout << sort_idx << endl;
cout << endl << " 크기순 정렬 사각형 정보 \t크기" << endl;
cout << "----------------------------------------" << endl;
for (int i = 0; i < rects.size(); i++) {
int idx = sort_idx(i);
cout << rects[idx] << "\t" << sizes(idx) << endl;
}
cout << "----------------------------------------" << endl;
return 0;
}
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat_<float> m1(3, 5);
m1 << 11, 2, 3, 4, 10,
6, 10, 15, 9, 7,
7, 12, 8, 14, 1;
Mat m_reduce1, m_reduce2, m_reduce3, m_reduce4;
reduce(m1, m_reduce1, 0, REDUCE_SUM); // 열방향(0) 합
reduce(m1, m_reduce2, 1, REDUCE_AVG); // 행방향(1) 평균
reduce(m1, m_reduce3, 0, REDUCE_MAX); // 열방향(0) 최대값
reduce(m1, m_reduce4, 1, REDUCE_MIN); // 행방향(1) 최소값
cout << "[m1] = " << endl << m1 << endl << endl;
cout << "[m_reduce_sum] = " << m_reduce1 << endl;
cout << "[m_reduce_avg] = " << m_reduce2.t() << endl << endl;
cout << "[m_reduce_max] = " << m_reduce3 << endl;
cout << "[m_reduce_min] = " << m_reduce4.t() << endl;
return 0;
}
5.12. 행렬 연산 함수
void gemm(): 일반화된 행렬 곱셈을 수행한다.
- InputArray src1, src2: 행렬 곱을 위한 두 입력 행렬(CV_32/CV_64F 타입 2채널까지 가능)
- double alpha: 행렬 곱에 대한 가중치
- InputArray src3: 행렬 곱에 더해지는 델타 행렬
- double beta: src3 행렬에 곱해지는 가중치
- OutputArray dst: 출력 행렬
- int flags: 연산 플래그, 옵션을 조합하여 입력 행렬들을 전치
1) GEMM_1_T(1): src1을 전치
2) GEMM_2_T(2): src2을 전치
3) GEMM_3_T(4): src3을 전치
- void transform(): 입력 배열의 모든 원소에 행렬 변환을 수행한다.
- InputArray src: 변환 행렬 m의 열수만큼 채널을 갖는 입력 배열
- OutputArray dst: src와 같은 크기와 깊이의 출력 배열, 채널 수는 m.rows개
- InputArray m: 2x2 혹은 3x3 부동소수점 변환 행렬
- void perspectiveTransform(): 입력 벡터들에 대해서 투명(perspective) 변환 m을 수행한다.
- InputArray src: 좌표로 변환될 2채널 혹은 3채널의 부동소수점 배열
- OutputArray dst: src와 같은 크기와 타입의 출력 배열
- InputArray m: 3x3 혹은 4x4 부동소수점 투영 변환 행렬
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Matx23f src1(1, 2, 3, 4, 5, 1);
Matx23f src2(5, 4, 2, 3, 2, 1);
Matx32f src3(5, 4, 2, 3, 2, 1);
Mat dst1, dst2, dst3;
double alpha = 1.0, beta = 1.0;
// 행렬 곱 수행
gemm(src1, src2, alpha, Mat(), beta, dst1);
gemm(src1, src2, alpha, noArray(), beta, dst2);
gemm(src1, src3, alpha, noArray(), beta, dst3);
// 콘솔창에 출력
cout << "[src1] = " << endl << src1 << endl;
cout << "[src2] = " << endl << src2 << endl;
cout << "[src3] = " << endl << src3 << endl << endl;
cout << "[dst1] = " << endl << dst1 << endl;
cout << "[dst2] = " << endl << dst2 << endl;
cout << "[dst3] = " << endl << dst3 << endl;
return 0;
}
4개의 좌표로 사각형을 그리고, 사각형 회전하기
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
vector<Point> rect_pt1, rect_pt2;
rect_pt1.push_back(Point(200, 50));
rect_pt1.push_back(Point(400, 50));
rect_pt1.push_back(Point(400, 250));
rect_pt1.push_back(Point(200, 250));
float theta = 20 * CV_PI / 180;
Matx22f m(cos(theta), -sin(theta), sin(theta), cos(theta));
transform(rect_pt1, rect_pt2, m);
Mat image(400, 500, CV_8UC3, Scalar(255, 255, 255));
for (int i = 0; i < 4; i++)
{
line(image, rect_pt1[i], rect_pt1[(i + 1) % 4], Scalar(0, 0, 0), 1);
line(image, rect_pt2[i], rect_pt2[(i + 1) % 4], Scalar(255, 0, 0), 2);
cout << "rect_pt1[" + to_string(i) + "]=" << rect_pt1[i] << "\t";
cout << "rect_pt2[" + to_string(i) + "]=" << rect_pt2[i] << "\t";
}
imshow("image", image);
waitKey();
return 0;
}
5.13. 심화 실습 3
사각형 회전하기 예제를 확장하여 그 사각형의 중심점을 기준으로 45도 회전
1) 원점으로 평행이동
2) 회전변환
3) 역 평행이동
-> 세 번의 변환은 각 변환 행렬의 곱으로 나타낼 수 있다.
-> 전체 변환 행렬 = 이동변환행렬(3) x 회전변환행렬(2) x 이동변환행렬(1)
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
vector<Point3f> rect_pt1, rect_pt2;
rect_pt1.push_back(Point3f(200, 50, 1)), rect_pt1.push_back(Point3f(400, 50, 1));
rect_pt1.push_back(Point3f(400, 250, 1)), rect_pt1.push_back(Point3f(200, 250, 1));
// 회전 변환 행렬 : 3x3 행렬(호모지어스 코디네이트)
float theta = 45 * CV_PI / 180;
Matx33f m;
m << cos(theta), -sin(theta), 0,
sin(theta), cos(theta), 0,
0, 0, 1;
// 평행이동 행렬
Mat t1 = Mat::eye(3, 3, CV_32F); // 평행이동
Mat t2 = Mat::eye(3, 3, CV_32F); // 역평행이동
Point3f delta = (rect_pt1[2] - rect_pt1[0]) / 2.0f;
Point3f center = rect_pt1[0] + delta;
// 원점으로 기준을 보내기 위해서
t1.at<float>(0, 2) = center.x;
t1.at<float>(1, 2) = center.y;
t2.at<float>(0, 2) = -center.x; // 원점으로
t2.at<float>(1, 2) = -center.y; // 원점으로
Mat m2 = t1 * (Mat)m * t2; // 중심점 좌표 계산
transform(rect_pt1, rect_pt2, m2);
Mat image(400, 500, CV_8UC3, Scalar(255, 255, 255));
for (int i = 0; i < 4; i++)
{
Point pt1(rect_pt1[i].x, rect_pt1[i].y);
Point pt2(rect_pt1[(i + 1) % 4].x, rect_pt1[(i + 1) % 4].y);
Point pt3(rect_pt2[i].x, rect_pt2[i].y);
Point pt4(rect_pt2[(i + 1) % 4].x, rect_pt2[(i + 1) % 4].y);
line(image, pt1, pt2, Scalar(0, 0, 0), 2);
line(image, pt3, pt4, Scalar(255, 0, 0), 2);
cout << "rect_pt1[" + to_string(i) + "]=" << rect_pt1[i] << "\t";
cout << "rect_pt2[" + to_string(i) + "]=" << rect_pt2[i] << endl;
}
imshow("image", image);
waitKey();
return 0;
}