- 다른 컬러 감축 공식
- 다른 컬러 감축 공식은 정수로 나눈다는 장점이 있으며, 나눈 결과인 부동 소수점이 가장 가까운 정수가 된다.- 나머지 연산자를 사용해 컬러 감축을 계산할 수 있으며, 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