[opencv] Mat클래스의 기본연산
앞으로 나올 수학적 개념과 연산… 들이 많이 걱정되긴 하지만
결국은 배워야 늘고 해야 느는것이기 때문에 마인드컨트롤하며 무너지지않고 잘 진행할 수 있도록 노력해야겠다.
이번에는 수학적 개념에 앞서 Mat클래스내에서 연산(API의 상위 레벨)하는것을 목표로 한다.
- flip()
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
#define WIN_NAME "winSrcImage"
#define FILE_LENA_JPG "lena.jpg"
int main(void) {
IplImage* image = cvLoadImage("lena.jpg", CV_LOAD_IMAGE_GRAYSCALE); // Intel Processing Library
Mat matrixA1 = cvarrToMat(image); // 데이터 공유
Mat matrixA2 = cvarrToMat(image).clone(); // 데이터까지 복사
Mat matrixB, matrixC;
flip(matrixA1, matrixB, 0); // 상하
imshow("matB", matrixB);
imshow("origin1", matrixA1);
flip(matrixA1, matrixC, 1); // 좌우
imshow("matC", matrixC);
imshow("origin2", matrixA1);
// flip을 해도 원본데이터에는 당연히 영향이 가지않음 -> clone:데이터복사
waitKey(0);
destroyAllWindows();
return 0;
}
flip함수는 이름과 같이 직관적이다.
기억해야할 것은 마지막 flipCode인자에 0을 넣으면 상하(x축을 기준으로), 1을 넣으면 좌우(y축을 기준으로)에 대하여 대칭이 된다.
위에 주석에도 적었듯이 flip을 적용한다고 원본 매트릭스에 영향이 가지는 않는다.

flip을 한 후에 다시 원본데이터를 imshow했을 때 기존 원본가 같음을 확인할 수 있다.
- repeat
- roi
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
#define WIN_NAME "winSrcImage"
#define FILE_LENA_JPG "lena.jpg"
int main(void) {
Mat srcImage = imread("lena.jpg", IMREAD_GRAYSCALE);
if (srcImage.empty()) {
return -1;
}
Rect rectROI(0, 0, 256, 256);
Mat extractROI = srcImage(rectROI);
for (int i = 0; i < 64; i++) {
for (int j = 0; j < 64; j++) {
extractROI.at<uchar>(i, j) = 0;
}
}
imshow("ROI", extractROI);
imshow("src", srcImage);
waitKey(0);
Mat repeatedMat = repeat(extractROI, 2, 2);
imshow("repeatedMat", repeatedMat);
waitKey(0);
return 0;
}
ROI는 관심영역일 뿐 데이터를 클로닝하여 저장하는 것이 아니다.
ROI를 지정한 Mat객체의 값을 인위적으로 손실시킨 다음 원본 Mat객체를 확인해보자.

위처럼 원본 srcImage 객체의 데이터에 반영된 것을 볼 수 있다.

repeat함수는 주어진 Mat객체의 데이터를 바탕으로 n * m형태의 바둑판식 패턴처럼 이어붙여준다.
물론 주어진 Mat객체의 데이터는 우리가 64*64만큼을 손실시켜놨으므로 위와같은 결과가 나오게 된다.
- roi
- 모자이크화
모자이크 함수를 쓰는것은 아니고, ROI를 이용해 모자이크 효과를 내는것이 목표이다.
TYPE값이 왜 0인가… 설마 CV_8UC1의 상수값이 0이던가~~했는데
역시나. 이왕이면 좀 알아두는게 좋을 것 같아 아래 링크를 참고하였다.
| C1 | C2 | C3 | C4 | |
| CV_8U | 0 | 8 | 16 | 24 |
| CV_8S | 1 | 9 | 17 | 25 |
| CV_16U | 2 | 10 | 18 | 26 |
| CV_16S | 3 | 11 | 19 | 27 |
| CV_32S | 4 | 12 | 20 | 28 |
| CV_32F | 5 | 13 | 21 | 29 |
| CV_64F | 6 | 14 | 22 | 30 |
Unsigned 8bits uchar 0~255
IplImage: IPL_DEPTH_8U
Mat: CV_8UC1, CV_8UC2, CV_8UC3, CV_8UC4
Signed 8bits char -128~127
IplImage: IPL_DEPTH_8S
Mat: CV_8SC1,CV_8SC2,CV_8SC3,CV_8SC4
Unsigned 16bits ushort 0~65535
IplImage: IPL_DEPTH_16U
Mat: CV_16UC1,CV_16UC2,CV_16UC3,CV_16UC4
Signed 16bits short -32768~32767
IplImage: IPL_DEPTH_16S
Mat: CV_16SC1,CV_16SC2,CV_16SC3,CV_16SC4
Signed 32bits int -2147483648~2147483647
IplImage: IPL_DEPTH_32S
Mat: CV_32SC1,CV_32SC2,CV_32SC3,CV_32SC4
Float 32bits float -1.18*10-38~3.40*10-38
IplImage: IPL_DEPTH_32F
Mat: CV_32FC1,CV_32FC2,CV_32FC3,CV_32FC4
Mat: CV_64FC1,CV_64FC2,CV_64FC3,CV_64FC4Unsigned 1bit bool
IplImage: IPL_DEPTH_1U

이렇게 될 것!
왜 이렇게 될까는 단순하다. float * int = int에서 소실되는 floating value들 때문에 ROI를 지정할 때 취약구간이 발생.
ex) Rect(0,0, 150.5, 150.5) / Rect(151, 151, 150.5, 150.5)
이 두개만봐도 결과적으로는

이렇게 될 것이다. 반복된다면 위의 사진처럼 바둑판식 무늬가 나타날테다.
따라서 나는 이 문제를 해결하기위해 width,height에 대해 roundup, 즉 ceiling하도록 했다.
Mat destImage = Mat::zeros(srcImage.rows, srcImage.cols, srcImage.type());
int mosaicConst = 64 ;
float fWidth = float(srcImage.cols) / mosaicConst;
float fHeight = float(srcImage.rows) / mosaicConst;
int x, y;
Rect rectROI;
Mat extractROI;
for (int i = 0; i < mosaicConst; i++) {
for (int j = 0; j < mosaicConst; j++) {
y = int(i * fHeight);
x = int(j * fWidth);
//rectROI = Rect(x, y, fWidth, fHeight);
rectROI = Rect(x, y, ceil(fWidth), ceil(fHeight)) ;
extractROI = srcImage(rectROI);
destImage(rectROI) = mean(extractROI); // mean은 평균
}
}
imshow("result", destImage);
waitKey(0);
int nWidth -> float fWidth
rectrectROI = Rect(x, y, ceil(fWidth), ceil(fHeight)) ;

128의 모자이크 상수를 주더라도 위처럼 깔끔하게 처리가 된다.
흐뭇..
- merge
- split
#pragma warning(disable: 4819)
#include "opencv.hpp"
#include <iomanip>
using namespace cv;
using namespace std;
int main()
{
Mat srcImage = imread("lena.jpg");
if (srcImage.empty()) {
return -1;
}
vector<Mat> planes;
split(srcImage, planes);
// BGR 순서
imshow("plane 0", planes[0]);
imshow("plane 1", planes[1]);
imshow("plane 2", planes[2]);
waitKey(0);
Mat destImage;
merge(planes, destImage);
imshow("destImage", destImage);
waitKey(0);
return 0;
}
먼저 lena.jpg를 BGR로 읽어들인다.
혹여나 다른 용도로 RGB로 사용하기 원한다면 아래 코드를 이용하면 된다…
cvtColor(srcImage, srcImage, CV_BGR2RGB);
만 뒤에 사용할 merge에서도 BGR컬러셋을 사용하기 때문에 예제를 따라하고 싶다면 변환하지 않는것이 좋다.
위에서 불러들였던 lena.jpg를 split으로 채널별로 쪼개고, imread로 확인하면

각각 Blue, Green, Red채널의 단편적 화상을 나타냄을 알 수 있다.
merge는 InputArrayOfArrays를 인자로 받기때문에 Matrix Array의 vector Array를 전달. 즉, 채널별 mat data를 보관하고있는 planes을 넘겨주면 된다.

결과적으로는 각 채널이 합쳐서 BGR컬러의 이미지가 만들어지게 된다.
- channel
channel에 관련된 함수는 3개가 소개되어있다.
mixChannels, insertImageCOI, extractImageCOI
- mixChannels : 채널을 분리 및 합성할 수 있게 해준다.
#pragma warning(disable: 4819)
#include "opencv.hpp"
#include <iomanip>
using namespace cv;
using namespace std;
int main()
{
Mat srcImage = imread("lena.jpg");
if (srcImage.empty()) {
return -1;
}
Mat imB(srcImage.rows, srcImage.cols, CV_8UC1);
Mat imG(srcImage.rows, srcImage.cols, CV_8UC1);
Mat imR(srcImage.rows, srcImage.cols, CV_8UC1);
Mat outImage[] = { imB, imG, imR };
int fromTo[] = { 0,0,1,1,2,2 }; // src의 0번채널은 out의 0번채널에 ...
// src, src내 갯수, out, out의 갯수, fromTo는 복사할 채널 대응 배열, 대응 채널이 몇 쌍인지
mixChannels(&srcImage, 1, outImage, 3, fromTo, 3); // 1개의 이미지를 3개의 이미지(채널)로 쪼갬
Mat destImage(srcImage.rows, srcImage.cols, srcImage.type());
mixChannels(outImage, 3, &destImage, 1, fromTo, 3);
// 3개의 이미지를 1개의 이미지로 합침
// 대응되는 채널 순서쌍은 3개.
imshow("destImage", destImage);
waitKey(0);
return 0;
}
2. insertImageCOI :
3. extractImageCOI :
5개의 댓글