2012년 12월 28일 금요일

OpenCV #2-2 Example (포인터로 영상 조회 - 부연 설명)

  • 다른 컬러 감축 공식

 - 다른 컬러 감축 공식은 정수로 나눈다는 장점이 있으며, 나눈 결과인 부동 소수점이 가장 가까운 정수가 된다.
 - 나머지 연산자를 사용해 컬러 감축을 계산할 수 있으며, div의 곱에 가까워 진다.
 - data[i] = data[i] - data[i]%div + div/2;

 - 소스
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

void colorReduce(cv::Mat &image, int div=64){
int nl = image.rows;
int nc = image.cols*image.channels();

for(int j=0 ; j<nl ; j++){
uchar* data = image.ptr<uchar>(j);

for(int i=0 ; i<nc ; i++){
// 각 화소 처리
int v= *data;
*data++= v - v%div + div/2;
// 각 화소 처리 끝
} // 행 끝
}
}

int main()
{
cv::Mat image = cv::imread("boldt.jpg");

colorReduce(image);

cv::namedWindow("Image");
cv::imshow("Image", image);
cv::waitKey(5000);

return 0;
}


 - 또 다른 컬러 감축 공식의 옵션 : 비트 연산자
 - 감축 인자를 2의 지수승, 즉 div=pow(2,n)으로 제한하면 화소값의 첫 번째 n 비트를 마스킹 할 때 가장 낮은 div의 곱에 가깝다.
 - 마스크는 간단한 비트 이동 연산으로 얻는다.

 - 소스
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

void colorReduce(cv::Mat &image, int div=64){
int nl = image.rows;
int nc = image.cols*image.channels();
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
 // 화소값을 반올림하기 위해 사용하는 마스크
uchar mask= 0xFF<<n; // 예, div=16이면 mask= 0xF0
              

for(int j=0 ; j<nl ; j++){
uchar* data = image.ptr<uchar>(j);

for(int i=0 ; i<nc ; i++){
// 각 화소 처리
*data++= *data&mask + div/2;
// 각 화소 처리 끝
} // 행 끝
}
}

int main()
{
cv::Mat image = cv::imread("boldt.jpg");

colorReduce(image);

cv::namedWindow("Image");
cv::imshow("Image", image);
cv::waitKey(5000);

return 0;
}

  • 입출력 인자 사용

 - 입력 영상에 직접 적용, 내부 공간에서 변환한다. (다른 메모리에 저장)
 - 영상의 복사본을 강제로 만듬으로 써 원 영상을 지킬 수 있다.
 - clone 메소드를 사용해서 영상과 동일한 깊은 복사본을 만든다.

 - 소스
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

void colorReduce(cv::Mat &image, int div=64){
int nl = image.rows;
int nc = image.cols*image.channels();

for(int j=0 ; j<nl ; j++){
uchar* data = image.ptr<uchar>(j);

for(int i=0 ; i<nc ; i++){
// 각 화소 처리
data[i] = data[i]/div*div + div/2;
// 각 화소 처리 끝
} // 행 끝
}
}

int main()
{
cv::Mat image;
image = cv::imread("boldt.jpg"); // 영상 읽기
cv::Mat imageClone = image.clone(); // 영상 복제
colorReduce(imageClone); // 복제본 처리, 원래 영상을 건드리지 않음

cv::namedWindow("Image Result");
cv::imshow("Image Result", imageClone);
cv::waitKey(5000);

return 0;
}


 - 사용자에게 재사용 옵션을 제공하거나 내부 공간 처리를 사용하지 않는 함수를 정의하는 방법은 따로 오버로딩을 하는 방법이 있음.

 - 소스
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

void colorReduce(cv::Mat &image, cv::Mat &result, int div=64){
// &image는 입력 영상, &result는 결과 영상
int nl = image.rows;
int nc = image.cols*image.channels();
result.create(image.rows, image.cols, image.type());
// 결과 영상이 입력 영상과 일치하는 크기와 화소 타입을 할당한 데이터 버퍼를 갖는지 확인
// create는 행렬에 새로운 크기와 타입을 갖고 다시 할당할 때 사용

for(int j=0 ; j<nl ; j++){
const uchar* data_in = image.ptr<uchar>(j);
uchar* data_out = result.ptr<uchar>(j);

for(int i=0 ; i<nc ; i++){
// 각 화소 처리
data_out[i] = data_in[i]/div*div + div/2;
// 각 화소 처리 끝
} // 행 끝
}
}

int main()
{
cv::Mat image;
image = cv::imread("boldt.jpg"); // 영상 읽기

colorReduce(image, image); 
// 입력 영상을 const 참조자로 넘김
// 함수에서 입력 영상을 수정하지 않음을 의미
// 내부 공간 처리를 선호한다면 동일한 영상을 입력과 결과로 지정

cv::Mat result;
colorReduce(image, result);
// 입력 영상과 결과 영상이 동일하면 수행

cv::namedWindow("Image Result");
cv::imshow("Image Result", result);
cv::waitKey(5000);

return 0;
}

  • 연속된 영상을 효율적으로 조회

 - 효율상의 이유로 영상 내 각 행의 마지막에 추가 화소로 채울 수 있다.
 - 하지만 연속된 영상은 채우지 않아도 된다.
 - 영상을 WxH 화소의 긴 1차원 배열로 볼 수 있다.
 - isContinues : 영상이 채워진 화소를 포함하지 않았을 땐 true 반환.

 -  소스
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

void colorReduce(cv::Mat &image, int div=64){
int nl = image.rows;
int nc = image.cols*image.channels();

if(image.isContinuous()){ // 채운 화소가 없을 경우
nc = nc*nl;
nl = 1; // 지금은 1차원 배열
} // 이 반복문은 단 한번 실행
// 연속된 영상의 경우이다.

for(int j=0 ; j<nl ; j++){
uchar* data = image.ptr<uchar>(j);

for(int i=0 ; i<nc ; i++){
// 각 화소 처리
data[i] = data[i]/div*div + div/2;
// 각 화소 처리 끝
} // 행 끝
}
}

int main()
{
cv::Mat image = cv::imread("boldt.jpg");

colorReduce(image);

cv::namedWindow("Image");
cv::imshow("Image", image);
cv::waitKey(5000);

return 0;
}


 - 위 코드는 연속성 테스트에서 채워진 화소를 포함하지 않는 영상임을 보임.
 - 너비를 1로 하고 높이를 WxH로 설정해 바깥쪽 반복문 제거.
 - reshape : 임의의 메모리 복사나 재할당 없이 행렬의 차원 수를 변경.
 - 내부 반복문은 여러개의 작은 영상을 같은 반복문에서 동시에 조회할 때 유리.

 - 소스
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

void colorReduce(cv::Mat &image, int div=64){
if(image.isContinuous()){ // 채운 화소가 없을 경우
image.reshape(1, image.cols*image.rows);
// 1은 채널의 새로운 개수, 다음 파라미터는 행의 새로운 값
// 행의 값에 맞춰 열 개수 조절
}
int nl = image.rows; // 행 개수
int nc = image.cols*image.channels();

for(int j=0 ; j<nl ; j++){
uchar* data = image.ptr<uchar>(j);

for(int i=0 ; i<nc ; i++){
// 각 화소 처리
data[i] = data[i]/div*div + div/2;
// 각 화소 처리 끝
} // 행 끝
}
}

int main()
{
cv::Mat image = cv::imread("boldt.jpg");

colorReduce(image);

cv::namedWindow("Image");
cv::imshow("Image", image);
cv::waitKey(5000);

return 0;
}

  • 저수준 포인터 산술 연산

 - unsigned char 포인터를 반환하는 데이터 속성에는 메모리 블록의 첫 번째 요소인 주소를 갖는다.
 - step : 해당 행의 바이트 총 개수(채운 화소를 포함)를 제공. 일반적으로 행 j와 열 i에 놓인 화소의 주소를 얻을 수 있다.
 - data=image.data + j*image.step + i*image.elemSize(); // (j, i)에 있는 화소의 주소를 &image.at(j, i)로 얻을 수 있다.

 - 소스
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

void colorReduce(cv::Mat &image, int div=64){
int nl = image.rows; // 행 개수
int nc = image.cols*image.channels();
int step = image.step; // 유효 너비
uchar *data= image.data; // 영상 버퍼에 대한 포인터 가져오기

for(int j=0 ; j<nl ; j++){
uchar* data = image.ptr<uchar>(j);

for(int i=0 ; i<nc ; i++){
// 각 화소 처리
data[i] = data[i]/div*div + div/2;
// 각 화소 처리 끝
} // 행 끝
data+= image.step; 
// 다음 줄(유효 너비를 사용해 행 포인터를 이동하면 하나씩 다음 행으로 이동)
}
}

int main()
{
cv::Mat image = cv::imread("boldt.jpg");

colorReduce(image);

cv::namedWindow("Image");
cv::imshow("Image", image);
cv::waitKey(5000);

return 0;
}


  • 참고문헌 : OpenCV 2 Computer Vision Application Programming Cookbook