[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개의 댓글