video object의 특정 region을 탐지하는 방법
물론 히스토그램을 완벽히 평평하게 만들 수 없는데 그 이유는 룩업 테이블은 전역 다대일 변환이기 때문이다. 아무튼 히스토그램의 일반적인 분포는 원히스토그램보다 지금 더 균등하다.
video object의 특정 region을 탐지하는 알고리즘
완벽한 균등 히스토그램의 모든 빈도는 화소 개수와 모두 같다. 화소의 50%는 128보다 작은 명암도를 갖고, 25%는 64보다 작은 명암도를 갖는다는 의미다. 이런 관찰은 다음과 같은 규칙을 사용해 표현할 수 있다. 균등 히스토그램에서 화소의 p%에 속한 명암도 값을 갖되 255*p%보다 적거나 같아야 한다는 게 히스토그램을 평활화하는 데 사용하는 규칙이다. 명암도 i는 i보다 적은 명암도 값을 갖는 화소의 %에 대응하는 명암도로 매핑한다. 따라서 다음 공식을 적용해 구축된 룩업 테이블이 필요하다.
lookup.at<uchar>(i) = static_cast<uchar>(255.0*p[i]);
여기서 p[i] 는 i보다 적거나 같은 명암도를 갖는 화소 개수다. 이 기능인 p[i]는 종종 누적 히스토그램으로 불린다. 즉, 지정된 명암도와 적거나 같은 화소 개수를 포함하는 히스토그램으로, 해당 화소값을 갖는 화소 개수를 대신한다.
일반적으로 히스토그램 평활화는 영상을 많이 향상시킨다. 그러나 시각적인 내용에 의존하므로, 결과의 품질은 영상마다 다를 수 있다.
* 영상 내 특정 내용을 감지하기 위한 히스토그램 역투영
히스토그램은 영상 내용의 중요한 특징이다. 특정 질감이나 특정 객체가 있는 영상 영역을 살펴보면 이 영역의 히스토그램은 특정 질감이나 특정 객체에 있는 화소가 속하는 분포를 제공함을 알 수 있다. 이번 예제에서는 영상 내의 특정 내용을 감지하는 데 유리하게 사용할 수 있는 방법을 알아본다.
4 히스토그램을 이용한 화소 개수 세기
histogram pixel counting method 화소수 세는 방법
있으면 255(흰색)를 결과 영상에 할당하고, 아니라면 (검은색)을 할당한다. 두 컬러값 간의 거리를 계산하려면 getDistance 메소드를 사용한다. 하나만 예를든다면 RGB 컬러값을 갖는 3벡터 사이의 유클리디안 거리 ulideul distance를 계산한다. 이번 경우에는 간단한 계산이지만 효율적으로 만들기 위해 RGB 값의절대 차이를 단순히 합친다(또한 도시 블록 거리'로 알려짐).
// 대상 컬러와 거리 계산
int getDistance (const cv:: Vec3b& color) const { return abs (color [0]-target [0]) +
abs (color[1]-target [1] ) +
abs (color [2]-target [2]); }
컬러인 RGB 값을 대표하는 세 개의 unsigned char를 갖는 cv:Vec3d를사용하는 방법을 참고하라. target 변수는 지정한 대상 컬러를 분명히 가리키며, 정의할 클래스 알고리즘에서 클래스 변수로 정의함을 볼 수 있다. 이제 처리하기 위한 메소드를 정의해보자. 사용자는 입력 영상을 제공하고 영상을 조회하는 과정이 완료되면 결과를 반환한다.
cv:: Mat ColorDetector: : process (const cv:: Mat &image) {{
// 필요하면 이진 맵으로 재할당한다.// 입력 영상과 같은 크기지만, 1채널이다.
result.create (image.rows, image.cols, CV_8U);
처리할 반복문을 여기에 놓고
return result;
1. 맨해튼 거리(Manhattan distance)의 다른 이름이며, 또한 LI 거리(Li distance)로도 부른다. 반면 유클리디안 거리의 다른 이름은 L2 거리(L2 distance)이다. - 옮긴이
}
함수를 사용하면 선명화 함수를 다음과 같이 쉽게 재정의한다.
void sharpen2D (const cv:: Mat &image, cv::Mat &result) { // 커널 생성 (모든 값을 0으로 초기화) CV:: Mat kernel (3,3, CV_32F, CV:: Scalar (0)); // 커널 값에 할당 kernel.at<float> (1,1) = 5.0; kernel.at<float> (0,1)= -1.0; kernel.at<float> (2,1) = -1.0; kernel.at<float>(1,0) = -1.0; kernel.at<float> (1,2) = -1.0; // 영상 필터링 cv::filter2D (image, result, image. depth(), kernel); 1 }
이번 구현은 정확히 이전에서 했던 것과 동일한 결과(그리고 같은 효율)를 제공한다. 그러나 더 큰 커널로 한다면 filter2D 메소드를 사용하는 게 유리하며, 이번 경우에도 좀 더 효율적인 알고리즘을 사용한다.
video filtering conept과 응용방법의 효과
6장에서 영상 필터링의 개념을 자세히 설명한다.
간단한 영상 산술 연산 실행
영상을 여러 가지 방법으로 조합할 수 있다. 영상은 일반 행렬이므로 더하고, 빼고, 곱하거나 나눌 수 있다. OpenCV는 다양한 영상 산술 연산을 제공하고, 이번 예제에서는 산술 연산의 사용 방법을 다룬다.
화소 다루기
다음과 같이 영교차인 이진 영상을 생성하는 메소드로 구현했다.
// 영교차인 이진 영상 가져오기
// 두 인접한 화소의 곱이 경계값 미만이라면 영교차를 무시cv:: Mat getzeroCrossings(float threshold=1.0) {
// 반복자 생성
cv::Mat float>:: const iterator it=
laplace.begin<float> () + laplace.step1();
CV:: Mat <float:: const iterator itend=
laplace.end<float>();
cv:: Mat <float>::const iterator itup=
laplace.begin<float>();
// 이진 영상을 흰색으로 초기화
cv::Mat binary(laplace.size(), CV_80, cv::Scalar (255)); CV::Mat <uchar> ::iterator itout=
binary.begin<uchar> () +binary.step1(); // 입력 경계값을 반전시킴
threshold *= -1.0;
for ( ; it!= itend; ++it, ++itup, ++itout) {
// 인접한 화소 간의 곱이 음수라면 부호 변경 if (*it * *(it-1) < threshold)
*itout= 0; // 수평 영교차 else if (*it * *itup < threshold)
*itout= 0; // 수직 영교차
return binary;
또한 현재 라플라시안 값이 에지를 고려할 만큼 중요한지 확인하기 위한 경계값을 추가했다. 결과는 이진 맵으로, 다음과 같다.
간단한 video compress operation execution 영상 산술 연산 실행
영상을 여러 가지 방법으로 조합할 수 있다. 경강은 김 로빼고, 곱하거나 나눌 수 있다. OpenCV는 다양한 경사 기술 을 저이번 예제에서는 산술 연산의 사용 방법을 다룬다.
코너 감지는 네 가지 구조 요소를 사용하기 때문에 약간 더 복잡하다. 코너 연산자는 OpenCV에서 구현되지 않지만, 여기서 여러 모양의 구조 요소를 정의하고 조합하는 방법으로 증명했음을 보여줬다. 이 아이디어는 두 개의 서로 다른 구조 요소와, 팽창과 침식에 의한 영상 닫힘에 있다. 이 요소는 에지가 바로 변하는 부분을 선택하지만, 해당 효과로 인해 코너 점의 에지에 영향을 끼친다. 비대칭 닫힘 연산에 대한 영향을 더욱 이해하려면 단일 흰색 사각형으로 만든 다음 영상을 사용해보자.
첫 번째 사각형은 원 영상이다. 십자가 모양의 구조 요소로 팽창했을 때 코너점을 제외하며 사각형 에지는 확장되는데, 십자가 모양이 정사각형을 건드리지 않는다. 이에 대한 결과는 중간에 있는 사각형이다. 팽창 이미지는 구조 요소로 침식, 즉 다이아몬드 모양을 갖는다. 침식은 원 위치에 대부분 에지가 있지만, 코너에 가해도 더 이상 팽창되지 않았다. 마지막 사각형을 다음과 같이 얻는데, 보다시피 모양 코너를 잃었다. 동일한 절차로 X 모양과 사각형 모양 구조 요소를 갖고 반복한다. 이전 버전을 회전한 두 요소는 결과로 45도 방향에서 코너를 잡는다. 끝으로 두 결과의 차이로 인해 코너 특징을 추출한다.
2. 2007년에 공개했던 Opency 1.0에서는 코너를 감지하기 위한 cvGoodFeaturesToTrack 함수와 CvFindConerSubPix 함수를 제공한다. - 옮긴이
을 반환한다. 함수를 사용하면 선명화 함수를 다음과 같이 쉽게 재정의한다.
void sharpen2D (const cv::Mat &image, cv:: Mat &result) { 77 커널 생성 (모든 값을 0으로 초기화) cv:: Mat kernel (3,3, CV_32F, CV :: Scalar (0)); | 커널 값에 할당 kernel.at<float>(1,1) = 5.0; kernel.at<float> (0,1)= -1.0; kernel.at<float>(2,1)= -1.0; kernel.at<float> (1,0) = -1.0; kernel.at<float> (1,2) = -1.0; // 영상 필터링 cv::filter2D(image, result, image.depth(), kernel); }
이번 구현은 정확히 이전에서 했던 것과 동일한 결과(그리고 같은 효율)를 제공한다. 그러나 더 큰 커널로 한다면 filter2D 메소드를 사용하는 게 유리하며, 이번 경우에도 좀 더 효율적인 알고리즘을 사용한다.
영상 필터링의 개념을 자세히 설명한다.
간단한 영상 산술 연산 operation execution
영상을 여러 가지 방법으로 조합할 수 있다. 영상은 일반 행렬이므로 더하고, 빼고, 곱하거나 나눌 수 있다. OpenCV는 다양한 영상 산술 연산을 제공하고, 이번 예제에서는 산술 연산의 사용 방법을 다룬다.
대부분 C++ 연산자는 오버로드됐다. 비트 연산자 중에는 &, , , ~, min, max, abs 함수, 비교 연산자 중에는 〈, <=, ==, ! =, >= 가 있다. 나중에는 8비트 이진 영상을 반환한다. 또한 행렬 곱인 m1*m2(ml과 m2는 모두 cv:: Mat 인스턴스)를 찾을 수 있으며, 역행렬인 ml.inv() 와 전치행렬 m1.t(), 행렬식m1.determinant(), 벡터 놈 v1.norm(), 외적 v1.cross(v2), 내적 v1.dot (v2) 등이 있다. 더 말이 되게 한다면 op= 연산자(예를 들어 +=)도 물론 정의할 수 있다.
영상을 조회하기 위한 효율적인 반복문을 만드는 예제에서 일부 산술 연산을 수행하기 위한 영상 화소를 조회하는 반복문을 사용한 컬러 감축 함수를 보여줬다. 여기서 배운 대로 다음과 같이 입력 영상에 대한 산술 연산을 사용해 함수를 간단히 다시 쓸 수 있다.
image= (image&cv::Scalar (mask, mask, mask)) +cv:: Scalar (div/2, div/2, div/2);
cv:: Scalar를 사용하는 이유는 사실 컬러 영상을 다루기 때문이다. 영상을 조회하기 위한 효율적인 반복문을 만드는 예제에서 했던 동일한 테스트를 수행하면 실행 시간인 89ms를 얻는다. 큰 이유라면 쓰여진 구문대로 두 가지 비트AND와 스칼라 합(영상 반복문 내부 전체 작업을 수행하는 대신)을 호출했기 때문이다. 결과 코드가 항상 최적화되지 않더라도 영상 연산자를 사용하면 코드가 매우 단순해진다. 그리고 프로그래머도 대부분 상황을 고려할 수 있어 역시 능률적이다.
Video channel decoupling method and effect 영상 채널 분리
때로는 영상의 각 채널을 독립적으로 처리하길 원할 수 있다. 예를 들어 영상의 한 개 채널에만 작업을 수행하고자 할 수 있다. 이런 작업은 물론 가능하다. 영상 조회 반복문에서 얻으면 된다. 컬러 영상의 세 가지 채널을 세 가지의 서로 다른 cv::Mat 인스턴스에 복사하는 cv::split 함수를 사용할 수 있다. 비 내리는 영상의 파랑 채널을 합친다고 가정하자. 다음 코드는 어떻게 처리하는지보여준다.
Original Image with window
이제 다음과 같이 창 내부의 라플라시안 값(7x7 커널)을 살펴보자.
1 0 2 2 -7 25 -78 -140 -115 -59-23 -5 -3 -6 46 -127 -186 -148 -73 -21 -11 -12 -40 16"-46..-164 -213 -165 -84 -33 -10 -6 84 105 •-121 -185 -158 -88 -29 135 202 284 296 199* *.20. -109 -106 -47 124 193 316 438 442 251 **16. -69 -41 39111 216 287 254 123 ...-19 -69 -42 38 110 180 85:-131 -217 -161 -90 -37 36 121 31: -299 -355 -183 -61 -12 1 95 210 43: -271 -293 -111 -17 0 -8 102 208 48:-227-228 -62 4 3 19 167 247 51: -225 -220 -57 6 3 ដី ដីន 0 -10 -9 -12 -7 5 6 2 0 CF . 217808876730 -3 8 6 0 0 1 2 0 210 -4
그림과 같이 신중하게 라플라시안의 영교차를 따라가면(다른 부호를 갖는 화소간에 놓인 위치) 영상 창의 안에 보이는 에지에 대응하는 곡선을 얻는다. 위에서 선택한 영상 창에서 보이는 타워의 에지에 대응하는 영교차 사이에 점선을 그렸다. 원칙적으로 부화소 정밀도에서 영상 에지를 감지할 수 있음을 의미한다.
라플라시안 영상 내의 영교차 곡선에 따라가는 것이 섬세한 작업이다. 그러나 대략적으로 영교차 위치를 감지하는 데 사용할 수 있는 간단한 알고리즘이 있다. 다음과 같이 진행하자. 라플라시안 영상을 조회해 지나간 화소와 현재 화소를 비교한다. 두 화소가 서로 다른 부호라면 현재 화소에 대해 영교차를 선언한다. 그렇지 않다면 즉시 화소 간에 같은 테스트를 되풀이한다. 이 알고리
영역은 낮은 값을 만드는 반면 높은 값은 필터 방향으로 명암도가 크게 변화하는 영역을 얻는다. 영상 미분을 계산하는 필터가 고주파 필터인 이유가 있다.
개니 연산자를 이용한 에지 감지 예제에서 두 가지 다른 경계값을 이용해 이진 맵을 얻을 수 있다.
영상에 대한 라플라시안 계산
라플라시안은 다른 고주파 선형 필터로, 영상 미분 계산에 기반을 둔다. 설명하겠지만 2차 미분 영상 기능의 곡률을 측정하기 위한 2차 미분을 계산한다.
예제 구현
OpenCV 함수인 cv:: Laplacian은 영상에 대한 라플라시안을 계산한다. cv::Sobel 함수와 매우 유사하다. 사실 동일한 기본 함수이자 커널 행렬을 얻기 위한 cv:: getDriveKernel을 사용한다. 단지 차이점이라면 2차 미분 정의 과정에서 미분 파라미터가 없다는 점이다.
라플라시안 Laplacian 연산자에 대해 라플라시안과 관련 있는 몇 가지 유용한 연산자를 캡슐화하는 간단한 클래스를 만든다. 기본 메소드는 다음과 같다.
class Laplacianzc {
private:// 원 영상 CV::Mat img;// 라플라시안을 포함한 32비트 부동소수점 영상 CV::Mat laplace;// 라플라시안 커널의 틈새 크기 int aperture;public:
영상 필터링 Video filtering method 필터링 효능
class WatershedSegmenter i
private: CV:: Mat markers; public: void setMarkers (const cv:: Mat & markerImage) // 정수형 영상 변환 markerImage.convertTo(markers, CV 32S); ) cv:: Mat process (const cv:: Mat &image) { // 워터쉐드 적용 CV::watershed (image, markers); return markers; }
{
마커를 얻는 방법은 애플리케이션에 따른다. 예를 들어 몇 가지 전처리 단계는 관심 객체에 속하는 일부 화소에 대한 식별로 이어질 수 있다. 워터쉐드는초기 감지에서 전체 객체를 결정하기 위해 사용한다. 이번 예제에서 5장에 걸쳐원 영상의 동물을 식별하기 위해(4장의 초반에 보여줬던 영상) 사용하는 이진 영상을간단히 이용한다.
따라서 이진 영상에서 확실히 전경(동물)에 속하는 화소와 배경(주로 잔디)에 속하는 화소를 분명하게 식별해야 한다. 여기서 255 레이블인 전경 화소와 128레이블(이 선택은 전적으로 임의이며, 255 이외의 레이블 번호로도 정할 수 있다)인 배경 화소를 마크한다. 다른 화소, 즉 레이블의 하나가 알려지지 않았을 때는 0인 값을할당한다. 지금으로선 이진 영상은 영상의 여러 부분에 속하는 흰색 화소를 너무 많이 포함하고 있다. 중요한 객체에 속하는 화소만 남기기 위해 이 영상을여러 번 침식한다.
// 잡음과 유사한 객체를 제거
cv:: Mat fg; cv:: erode (binary, fg, CV:: Mat(), CV:: Point (-1,-1), 6);
영상은 여러 값을 갖는 화소로 구성돼 있다. 영상 전체에 걸친 화소값 분포는 해당 영상의 중요한 특징이다. 4장은 영상 히스토그램의 개념을 소개한다. 히스토그램을 계산하는 방법과 영상의 내용을 수정할 때 히스토그램을 사용하는 방법을 알아본다. 또한 히스토그램은 영상 내용의 특성화와 특정 객체 감지, 또는 영상의 질감을 구할 때 사용할 수 있다. 그 중 일부 기술을 4장에서 보여준다.
히스토그램 계산 histogram operation
영상은 여러 값을 갖는 화소로 구성돼 있다. 예를 들어 1채널 그레이레벨 영상에서 각 화소는 0(검은색)과 255(흰색) 사이의 값을 갖는다. 사진의 내용을 보면 영상 내부에 분포된 각 회색 그림자의 진함이 다름을 알 수 있다.
히스토그램은 영상(또는 영상 집합) 내 해당 값을 갖는 화소 개수를 담은 간단한 테이블이다. 그레이레벨 영상의 히스토그램은 당연히 256개의 항목(또는 빈도)이다.
빈도가 0이면 0인 값을 갖는 화소 개수를 의미하며, 빈도가 1이면 1인 값을 갖는 화소 개수를 의미한다. 물론 히스토그램의 모든 항목을 합하면 화소의 총개수를 얻을 수 있다. 또한 히스토그램을 빈도의 합인 1로 정규화할 수 있다. 이런 경우 각 빈도는 영상 내 특정 값을 갖는 화소의 비율이다.
준비
간단한 콘솔 프로젝트를 정의하고 다음과 같은 영상을 사용할 수 있게 미리 준비한다.
예제 구현
첫 번째 단계에서 ROI를 정의한다. 일단 정의하면 일반적인 cv::Mat 인스턴스로 ROI를 다룰 수 있다. ROI는 부모 영상과 동일한 데이터 버퍼를 갖는다는 점이 핵심이다. 다음과 같이 수행하면 로고가 삽입된다.
// 영상 ROI 정의 CV:: Mat imageROI; imageROI= image(cv:: Rect (385,270, logo.cols, logo.rows)); // 영상에 로고 붙이기 cv:: addWeighted (imageROI, 1.0, logo, 0.3, 0., imageROI);
다음과 같은 영상을 얻는다.
with logo
로고의 컬러는 영상의 컬러에 더해지기 때문에(물론 새추레이션' 적용), 시각적인 결과는 항상 맘에 들지 않는다. 이런 이유로 로고 영역이 있는 영상의 화소값을 로고 값으로 간단히 설정하는 것이 더 나을 수 있다. 마스크를 사용해 로고를 ROI에 복사하자.
7. 새추레이션(saturation), 즉 예제에서 이미 소개했던 cv::saturate_cast 함수의 알고리즘을 가리키며, 0 미만을 0으로, 255 이상을 255로 강제 변환하는 기법이다. 옮긴이는 클램핑(clamping) 기법의 한 가지로 정의하고 있으며, 새추레이션 외에 랩(wrap)이 있다. 랩은 0 미만, 즉 음수를 0으로, 1부터 255까지는 그대로 하되 256으로, 257을 1로 강제 변환하는 방법으로 톱니바퀴를 연상하면 된다. 옮긴이