반응형

CUDA 디바이스 정보를 확인해 보자.

 

helper_cuda.h
0.03MB
helper_string.h
0.01MB

 

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "helper_cuda.h"

#define _1MB (1024 * 1024)

int main()
{
	int ngpus;
	cudaGetDeviceCount(&ngpus);

	for (int i = 0; i < ngpus; i++) {
		cudaDeviceProp prop;
		cudaGetDeviceProperties(&prop, i);
		size_t a;
		printf("GPU %d: %s\n", i, prop.name);
		printf("\tCompute capability: %d.%d\n", prop.major, prop.minor);
		printf("\tNumber of multiprocessors: %d\n", prop.multiProcessorCount);
		printf("\tNumber of CUDA cores: %d\n", prop.multiProcessorCount * _ConvertSMVer2Cores(prop.major, prop.minor));
		printf("\tGlobal memory: %llu MB\n", prop.totalGlobalMem / _1MB);
		printf("\tShared memory per block: %llu KB\n", prop.sharedMemPerBlock / 1024);
		printf("\tRegisters per block: %d\n", prop.regsPerBlock);
		printf("\tWarp size: %d\n", prop.warpSize);
		printf("\tMax threads per block: %d\n", prop.maxThreadsPerBlock);
		printf("\tMax threads per multiprocessor: %d\n", prop.maxThreadsPerMultiProcessor);
	}

	return 0;
}

 

 

반응형
Posted by J-sean
:

[CUDA] C++ with CUDA

Computer Vision 2026. 4. 28. 17:35 |
반응형

C++ 프로젝트에 CUDA 파일을 추가해 병렬 프로그래밍 코드를 빌드해 보자.

 

우선 일반적인 C++ 프로젝트를 만들고 C++ 소스 파일(Source.cpp), 헤더 파일(Header.h), 그리고 CUDA 소스 파일(File.cu)을 생성한다.

 

Solution Explorer - 프로젝트에서 우클릭 - Build Dependencies - Build Customizations... 클릭

 

CUDA XX.X(.targets, .props) 선택

 

File.cu 우클릭 - Properties 클릭

 

Item Type은 이미 CUDA C/C++가 선택되어 있을 것이다.

 

 

File.cu 코드

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "Header.h"
#include <stdio.h>

__global__ void cudaFunc()
{
	printf("Hello World from GPU!\n");
}

// 외부(.cpp)에서 호출할 수 있는 래퍼 함수
extern "C" void callCudaFunc() {
	// 블록과 스레드 설정
	dim3 blocks(1);
	dim3 threads(5);

	// 내부에서 CUDA 커널 호출
	cudaFunc <<<blocks, threads>>> ();

	// 디바이스 동기화 (필요시)
	cudaDeviceSynchronize();
}

 

Header.h 코드 (.cpp 파일과 .cu 파일 양쪽에서 include 된다)

#pragma once

// C++ 네임 맹글링(Name Mangling) 문제를 방지하려면 extern "C"를 사용할 수 있다.
#ifdef __cplusplus
extern "C" {
#endif

	// cpp 파일에서 호출할 래퍼 함수의 선언
	void callCudaFunc();

#ifdef __cplusplus
}
#endif

 

Source.cpp 코드

#include <iostream>
#include "Header.h"

int main() {
	std::cout << "Hello World from CPU!" << std::endl;

	callCudaFunc();

	return 0;
}

 

다른 파일(예: .cpp 파일)에서 .cu 파일에 정의된 CUDA 함수(cudaFunc())를 호출하려면, C++ 컴파일러가 CUDA 특화 문법(<<<...>>> 등)을 이해하지 못하기 때문에 래퍼(Wrapper) 함수 패턴을 사용해야 한다.

.cpp에서는 커널(__global__)을 직접 호출할 수 없기 때문에 .cu 파일에 일반 C/C++ 래퍼 함수를 만드는 것이다.

 

 

반응형
Posted by J-sean
:
반응형

memcpy()를 사용해 cv::Mat 이미지를 복사해 보자.

 

#include <iostream>
#include <opencv2/opencv.hpp>

int main()
{
	cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_ERROR);

	cv::Mat image = cv::imread("palvin1.png");
	if (image.empty()) {
		std::cerr << "Error: Could not open image file" << std::endl;

		return -1;
	}

	cv::Mat copyImage(image.size(), image.type());
	//cv::Mat copyImage; // 이 경우 메모리 공간이 전혀 할당되어 있지 않다.
	//copyImage.create(image.size(), image.type()); // 필요한 메모리 공간 할당.

	// image에서 copyImage로 memcpy()를 사용하여 복사
	if (image.isContinuous() && copyImage.isContinuous()) {
		std::cout << "Using memcpy for continuous memory" << std::endl;
		std::memcpy(copyImage.data, image.data, image.total() * image.elemSize());
		// 메모리가 연속적으로 할당되어 있는 경우, 전체 데이터를 한 번에 복사할 수 있다.
	}
	else {
		for (int i = 0; i < image.rows; ++i) {
			std::cout << "Using memcpy for uncontinuous memory, row " << i << std::endl;
			std::memcpy(copyImage.ptr(i), image.ptr(i), image.cols * image.elemSize());
			// 메모리가 연속적으로 할당되어 있지 않은 경우, 각 행을 개별적으로 복사해야 한다.
		}
	}

	cv::imshow("Original Image", image);
	cv::imshow("Copied Image", copyImage);

	cv::waitKey(0);

	cv::destroyAllWindows();

	return 0;
}

 

원본과 동일한 이미지가 생성된다.

 

 

CUDA를 지원하도록 컴파일된 OpenCV가 아닌 경우, 아래와 같이 GPU 메모리에서 모든 연산을 직접 지시해야 한다.

이미지의 밝기를 어둡게 하는 예를 살펴보자.

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <opencv2/opencv.hpp>
#include <stdio.h>

// 간단한 CUDA 커널 예시: 이미지를 어둡게 만들기 (밝기 반감)
__global__ void darkenImageKernel(uchar3* d_img, int width, int height)
{
	int x = blockIdx.x * blockDim.x + threadIdx.x;
	// blockIdx.x: 현재 블록의 x 인덱스, blockDim.x: 블록당 스레드 수, threadIdx.x: 현재 스레드의 x 인덱스
	int y = blockIdx.y * blockDim.y + threadIdx.y;
	// blockIdx.y: 현재 블록의 y 인덱스, blockDim.y: 블록당 스레드 수, threadIdx.y: 현재 스레드의 y 인덱스

	if (x < width && y < height)
	{
		int idx = y * width + x;
		// BGR 각 채널의 값을 절반으로 줄임
		d_img[idx].x = d_img[idx].x / 2; // B
		d_img[idx].y = d_img[idx].y / 2; // G
		d_img[idx].z = d_img[idx].z / 2; // R
	}
}

int main()
{
	// CPU에서 이미지 읽기
	cv::Mat cpuImg = cv::imread("palvin1.png", cv::IMREAD_COLOR);
	if (cpuImg.empty())
	{
		printf("Could not read the image\n");
		return 1;
	}

	int width = cpuImg.cols;
	int height = cpuImg.rows;
	size_t imgSize = width * height * sizeof(uchar3); // 3채널(BGR) 8비트 이미지 크기

	// GPU 메모리 포인터 선언
	uchar3* d_imgData = nullptr;

	// GPU 공간 할당 (cudaMalloc)
	cudaMalloc((void**)&d_imgData, imgSize);

	// CPU 데이터(cv::Mat 내부 배열)를 GPU 메모리로 복사 (Host -> Device)
	if (!cpuImg.isContinuous())
	{
		std::cout << "Image data is continuous. Copying all at once." << std::endl;
		// cv::Mat이 연속적인 메모리 레이아웃을 가지고 있는지 확인
		// isContinuous()가 true인 경우, 데이터는 한 블록으로 연속적으로 저장되어 있다. 이는 cudaMemcpy로 복사할 때 효율적이다.
		cudaMemcpy(d_imgData, cpuImg.data, imgSize, cudaMemcpyHostToDevice);
	}
	else
	{
		std::cout << "Image data is not continuous. Copying row by row." << std::endl;
		// cv::Mat이 연속적이지 않은 경우, 각 행을 개별적으로 복사해야 한다. 이는 비효율적이지만, 모든 경우에 안전하다.
		for (int i = 0; i < height; ++i)
		{
			cudaMemcpy(d_imgData + i * width, cpuImg.ptr<uchar3>(i), width * sizeof(uchar3), cudaMemcpyHostToDevice);
		}
	}

	// 직접 작성한 CUDA 커널 실행
	dim3 threadsPerBlock(16, 16);
	// CUDA에서 dim2를 사용하는것은 표준이 아니며, 일반적으로 dim3를 사용한다. dim3는 3차원 블록과 그리드 구성을 지원하지만,
	// 2D 이미지 처리에서는 threadsPerBlock를 (16, 16)으로 설정하는 것이 일반적이다. 사용하지 않는 z 차원은 기본값인 1로 유지된다.
	// 각 블록에 16x16 스레드 (총 256 스레드, 32의 배수로 맞추는것이 효율적)
	// threadsPerBlock.x = 16, threadsPerBlock.y = 16
	// 16x16 스레드 블록은 일반적으로 이미지 처리에 적합한 크기다. 이는 GPU의 워프(warp) 크기와도 잘 맞아떨어지며, 많은 GPU에서 효율적으로 실행된다.
	// 워프(warp): CUDA에서 스레드 그룹의 기본 단위로, 일반적으로 32개의 스레드로 구성된다. 16x16 블록은 256개의 스레드를 포함하므로,
	// 이는 8개의 워프에 해당한다.
	dim3 blocksPerGrid((width + threadsPerBlock.x - 1) / threadsPerBlock.x, (height + threadsPerBlock.y - 1) / threadsPerBlock.y);
	// 필요한 블록 수 계산 (이미지 크기에 맞게)
	// blocksPerGrid.x = (width + 15) / 16, blocksPerGrid.y = (height + 15) / 16
	// 15를 더하는 이유는 올림을 하기 위해서다. 예를 들어, width가 30이라면 (30 + 15) / 16 = 45 / 16 = 2 블록이 필요하다.

	darkenImageKernel <<<blocksPerGrid, threadsPerBlock >>> (d_imgData, width, height);
	// <<<필요한 블록 수, 각 블록당 스레드 수>>>: CUDA 커널 launch 구문
	// 블록(block): CUDA에서 스레드 그룹을 나타내며, 각 블록은 여러 스레드로 구성된다. 블록은 1D, 2D 또는 3D로 구성될 수 있다.
	// 그리드(grid): CUDA에서 블록의 집합을 나타내며, 전체 작업을 처리하기 위해 여러 블록이 필요할 수 있다. 그리드도 1D, 2D 또는 3D로 구성될 수 있다.	
	// 그리드 수는 이미지 크기에 따라 자동으로 계산되며, 각 블록은 16x16 스레드를 포함한다. 예를 들어, 512x512 이미지의 경우, (512 + 15) / 16 = 32 블록이 필요하므로,
	// blocksPerGrid는 (32, 32)가 된다. 이는 총 1024 블록이 실행되며, 각 블록은 256 스레드를 포함하므로, 총 262144 스레드가 실행된다.
	// 스레드(thread): CUDA에서 실제로 작업을 수행하는 단위이다. 각 스레드는 고유한 인덱스를 가지며, 이를 통해 데이터에 접근한다.

	// GPU 연산이 완료될 때까지 대기
	cudaDeviceSynchronize();

	// GPU에서 연산이 완료된 데이터를 다시 CPU(cv::Mat)로 복사 (Device -> Host)
	cudaMemcpy(cpuImg.data, d_imgData, imgSize, cudaMemcpyDeviceToHost);

	// 다 쓴 GPU 메모리 해제
	cudaFree(d_imgData);

	// 결과 확인
	cv::imshow("Darkened Image", cpuImg);
	cv::waitKey(0);
	cv::destroyAllWindows();

	return 0;
}

 

 

반응형
Posted by J-sean
:
반응형

CPU와 GPU의 이미지 처리 속도를 비교해 보자.

 

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/cudafilters.hpp>
#include <opencv2/cudaimgproc.hpp>

#pragma comment(lib, "opencv_core4d.lib")
#pragma comment(lib, "opencv_highgui4d.lib")
#pragma comment(lib, "opencv_imgcodecs4d.lib")
#pragma comment(lib, "opencv_imgproc4d.lib")
#pragma comment(lib, "opencv_cudaimgproc4d.lib")
#pragma comment(lib, "opencv_cudafilters4d.lib")

int main() {
	cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_ERROR);

	cv::Mat image = cv::imread("palvin1.png");
	if (image.empty()) {
		std::cerr << "Error: Could not open image file" << std::endl;

		return -1;
	}

	int64 start;
	double timeSec;

	// For CPU processing
	std::vector<cv::Vec4i> lines;
	cv::Mat grayImage;
	cv::Mat resultImage;

	// For GPU processing
	cv::cuda::GpuMat gpuImage;
	cv::cuda::GpuMat gpuResultImage;
	cv::cuda::GpuMat gpuLines;
	cv::cuda::GpuMat gpuGrayImage;

	/////////////// CPU Processing ///////////////
	start = cv::getTickCount();

	cv::cvtColor(image, grayImage, cv::COLOR_BGR2GRAY);
	cv::HoughLinesP(grayImage, lines, 1, CV_PI / 180, 50, 50, 10); // 이 함수의 연산량이 굉장히 크다
	cv::Sobel(image, resultImage, CV_8U, 1, 0, 3);
	cv::Canny(grayImage, resultImage, 50, 150, 3);

	timeSec = (cv::getTickCount() - start) / cv::getTickFrequency();
	std::cout << "CPU Processing Time : " << timeSec << " sec" << std::endl;
	///////////////////////////////////////////////

	//////////////// GPU Processing ///////////////
	start = cv::getTickCount();

	gpuImage.upload(image);
    
	cv::cuda::cvtColor(gpuImage, gpuGrayImage, cv::COLOR_BGR2GRAY);

	cv::Ptr<cv::cuda::HoughSegmentDetector> houghDetector = cv::cuda::createHoughSegmentDetector(1, CV_PI / 180, 50, 50, 10);
	houghDetector->detect(gpuGrayImage, gpuLines);
	gpuLines.download(lines);

	cv::Ptr<cv::cuda::Filter> sobelFilter = cv::cuda::createSobelFilter(gpuImage.type(), CV_8UC3, 1, 0, 3);
	sobelFilter->apply(gpuImage, gpuResultImage);

	cv::Ptr<cv::cuda::CannyEdgeDetector> cannyDetector = cv::cuda::createCannyEdgeDetector(50, 150, 3);
	cannyDetector->detect(gpuGrayImage, gpuResultImage);

	gpuImage.download(image);

	timeSec = (cv::getTickCount() - start) / cv::getTickFrequency();
	std::cout << "GPU Processing Time : " << timeSec << " sec" << std::endl;
	////////////////////////////////////////////////

	cv::imshow("Original Image", image);

	cv::waitKey(0);

	cv::destroyAllWindows();

	return 0;
}

 

이미지 사이즈: 840 X 1260

 

 

나름 공정하게 비교했는데 정확한지는 모르겠다.

어쨌든 10배 이상의 속도 차이가 난다.

 

 

HoughLinesP()의 연산량이 커서 속도에 큰 차이가 벌어지는데, HoughLinesP()를 몇 번 더 호출하면 더 큰 차이를 보이게 된다.

 

	/////////////// CPU Processing ///////////////
	start = cv::getTickCount();

	cv::cvtColor(image, grayImage, cv::COLOR_BGR2GRAY);
	cv::HoughLinesP(grayImage, lines, 1, CV_PI / 180, 50, 50, 10);
	cv::HoughLinesP(grayImage, lines, 1, CV_PI / 180, 50, 50, 10);
	cv::HoughLinesP(grayImage, lines, 1, CV_PI / 180, 50, 50, 10);
	cv::Sobel(image, resultImage, CV_8U, 1, 0, 3);
	cv::Canny(grayImage, resultImage, 50, 150, 3);

	timeSec = (cv::getTickCount() - start) / cv::getTickFrequency();
	std::cout << "CPU Processing Time : " << timeSec << " sec" << std::endl;
	///////////////////////////////////////////////

	//////////////// GPU Processing ///////////////
	start = cv::getTickCount();

	gpuImage.upload(image);
    
	cv::cuda::cvtColor(gpuImage, gpuGrayImage, cv::COLOR_BGR2GRAY);

	cv::Ptr<cv::cuda::HoughSegmentDetector> houghDetector = cv::cuda::createHoughSegmentDetector(1, CV_PI / 180, 50, 50, 10);
	houghDetector->detect(gpuGrayImage, gpuLines);
	gpuLines.download(lines);

	houghDetector->detect(gpuGrayImage, gpuLines);
	gpuLines.download(lines);

	houghDetector->detect(gpuGrayImage, gpuLines);
	gpuLines.download(lines);

	cv::Ptr<cv::cuda::Filter> sobelFilter = cv::cuda::createSobelFilter(gpuImage.type(), CV_8UC3, 1, 0, 3);
	sobelFilter->apply(gpuImage, gpuResultImage);

	cv::Ptr<cv::cuda::CannyEdgeDetector> cannyDetector = cv::cuda::createCannyEdgeDetector(50, 150, 3);
	cannyDetector->detect(gpuGrayImage, gpuResultImage);

	gpuImage.download(image);

	timeSec = (cv::getTickCount() - start) / cv::getTickFrequency();
	std::cout << "GPU Processing Time : " << timeSec << " sec" << std::endl;
	////////////////////////////////////////////////

 

GPU가 약 37배 더 빠르다

 

반대로 HoughLinesP() 호출을 삭제하면 오히려 GPU보다 CPU가 더 빠른 결과를 보인다.

GPU 연산을 위한 메모리 복사 등의 오버헤드가 크기 때문이다.

	/////////////// CPU Processing ///////////////
	start = cv::getTickCount();

	cv::cvtColor(image, grayImage, cv::COLOR_BGR2GRAY);
	//cv::HoughLinesP(grayImage, lines, 1, CV_PI / 180, 50, 50, 10);
	cv::Sobel(image, resultImage, CV_8U, 1, 0, 3);
	cv::Canny(grayImage, resultImage, 50, 150, 3);

	timeSec = (cv::getTickCount() - start) / cv::getTickFrequency();
	std::cout << "CPU Processing Time : " << timeSec << " sec" << std::endl;
	///////////////////////////////////////////////

	//////////////// GPU Processing ///////////////
	start = cv::getTickCount();

	gpuImage.upload(image);
    
	cv::cuda::cvtColor(gpuImage, gpuGrayImage, cv::COLOR_BGR2GRAY);

	//cv::Ptr<cv::cuda::HoughSegmentDetector> houghDetector = cv::cuda::createHoughSegmentDetector(1, CV_PI / 180, 50, 50, 10);
	//houghDetector->detect(gpuGrayImage, gpuLines);
	//gpuLines.download(lines);

	cv::Ptr<cv::cuda::Filter> sobelFilter = cv::cuda::createSobelFilter(gpuImage.type(), CV_8UC3, 1, 0, 3);
	sobelFilter->apply(gpuImage, gpuResultImage);

	cv::Ptr<cv::cuda::CannyEdgeDetector> cannyDetector = cv::cuda::createCannyEdgeDetector(50, 150, 3);
	cannyDetector->detect(gpuGrayImage, gpuResultImage);

	gpuImage.download(image);

	timeSec = (cv::getTickCount() - start) / cv::getTickFrequency();
	std::cout << "GPU Processing Time : " << timeSec << " sec" << std::endl;
	////////////////////////////////////////////////

 

CPU가 약 두 배 더 빠르다

 

반응형
Posted by J-sean
:

[OpenCV] OpenCV with CUDA Build

2026. 4. 26. 00:16

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

반응형

영상을 리사이즈하면 어떻게 변화하는지 살펴보자. (불필요한 로그 메세지도 없애보자)

 

2X2 데이터

#include <iostream>
#include <opencv2/opencv.hpp>

int main()
{
	// OpenCV 로그 레벨을 WARNING 또는 ERROR로 설정하여 불필요한 INFO 메시지를 숨김
	cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_WARNING);
	//LOG_LEVEL_VERBOSE: 모든 로그 출력(매우 상세)
	//LOG_LEVEL_INFO : 일반적인 정보 메시지만 출력
	//LOG_LEVEL_WARNING : 경고 메시지만 출력
	//LOG_LEVEL_ERROR : 실제 에러 발생 시에만 출력
	//LOG_LEVEL_FATAL : 프로그램이 중단될 정도의 치명적 에러만 출력
	//LOG_LEVEL_SILENT : 모든 로그 끄기

	char data[4] = {
		30, 60,
		90, 120
	};
	cv::Mat mat(2, 2, CV_8UC1, data);

	std::cout << "Original Mat: " << std::endl << mat << std::endl;

	cv::Mat resizedMat;

	cv::resize(mat, resizedMat, cv::Size(), 2, 2, cv::INTER_NEAREST);
	// INTER_NEAREST는 가장 가까운 픽셀의 값을 사용하여 이미지를 확대하는 방법이다.
	// 이 방법은 빠르지만, 확대된 이미지가 계단 현상(픽셀화)으로 보일 수 있다.
	std::cout << "Resized Mat (INTER_NEAREST): " << std::endl << resizedMat << std::endl;

	cv::resize(mat, resizedMat, cv::Size(), 2, 2, cv::INTER_LINEAR);
	// INTER_LINEAR는 주변 픽셀의 값을 선형 보간하여 이미지를 확대하는 방법이다.
	// 이 방법은 INTER_NEAREST보다 부드러운 결과를 제공하지만, 계산 비용이 더 높다.
	// 가장 일반적으로 사용되는 보간 방법으로, 대부분의 경우에 적절한 결과를 제공한다.
	std::cout << "Resized Mat (INTER_LINEAR): " << std::endl << resizedMat << std::endl;

	cv::resize(mat, resizedMat, cv::Size(), 2, 2, cv::INTER_CUBIC);
	// INTER_CUBIC는 주변 픽셀의 값을 3차 보간하여 이미지를 확대하는 방법이다.
	// 이 방법은 INTER_LINEAR보다 더 부드러운 결과를 제공하지만, 계산 비용이 더 높다.
	// 특히, 이미지가 크게 확대될 때 더 좋은 결과를 제공할 수 있다.
	std::cout << "Resized Mat (INTER_CUBIC): " << std::endl << resizedMat << std::endl;

	cv::resize(mat, resizedMat, cv::Size(), 2, 2, cv::INTER_LANCZOS4);
	// INTER_LANCZOS4는 주변 픽셀의 값을 Lanczos 보간을 사용하여 이미지를 확대하는 방법이다.
	// 이 방법은 가장 부드러운 결과를 제공하지만, 계산 비용이 가장 높다.
	// 특히, 이미지가 크게 확대될 때 가장 좋은 결과를 제공할 수 있다.
	std::cout << "Resized Mat (INTER_LANCZOS4): " << std::endl << resizedMat << std::endl;

	return 0;
}

 

4X4 크기의 이미지를 네 가지 방법으로 리사이즈한 결과

 

4X4 데이터

...
	char data[16] = {
		30, 30, 60, 60,
		30, 30, 60, 60,
		90, 90, 120, 120,
		90, 90, 120, 120
	};
	cv::Mat mat(4, 4, CV_8UC1, data);
...

 

 

8X8 데이터

...
	char data[64] = {
		30, 30, 30, 30, 60, 60, 60, 60,
		30, 30, 30, 30, 60, 60, 60, 60,
		30, 30, 30, 30, 60, 60, 60, 60,
		30, 30, 30, 30, 60, 60, 60, 60,
		90, 90, 90, 90, 120, 120, 120, 120,
		90, 90, 90, 90, 120, 120, 120, 120,
		90, 90, 90, 90, 120, 120, 120, 120,
		90, 90, 90, 90, 120, 120, 120, 120
	};
	cv::Mat mat(8, 8, CV_8UC1, data);
...

 

 

 

반응형
Posted by J-sean
:

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

반응형

히스토그램을 그리고 중간 빈도수(중앙값)의 위치를 찾아보자.

 

#include <iostream>
#include <opencv2/opencv.hpp>

// src의 히스토그램을 계산하고 중간 빈도수의 값(위치)을 반환하는 함수
int Median(const cv::Mat& src)
{
	cv::Mat hist;
	int nImages = 1;
	int channels[] = { 0 };
	int dims = 1;
	int histSize[] = { 256 };
	float graylevel[] = { 0, 256 };
	const float* ranges[] = { graylevel };

	cv::calcHist(&src, nImages, channels, cv::noArray(), hist, dims, histSize, ranges);

	// 히스토그램에서 각 밝기 값의 빈도수를 모두 더해서 총 빈도수를 계산한다
	//int total = 0;
	//for (int i = 0; i < hist.rows; ++i)
	//	total += hist.at<float>(i, 0);
	// 그러나 위 코드는 비효율적이다. src 이미지의 총 픽셀 수를 직접 계산하여 총 빈도수로 사용할 수 있다.
	int total = src.total(); // src.total()는 src 이미지의 총 픽셀 수를 반환한다.

	int median = 0;
	int cumulative = 0;
	// 누적 빈도수가 총 빈도수의 절반에 도달하는 위치값을 찾는다
	for (int i = 0; i < hist.rows; ++i) {
		cumulative += hist.at<float>(i, 0);
		if (cumulative >= total / 2) {
			median = i;
			break;
		}
	}

	std::cout << "Total: " << total << std::endl <<
		"Cumulative: " << cumulative << std::endl <<
		"Median Index: " << median << std::endl;

	return median;
}

cv::Mat getHistImage(const cv::Mat& src)
{
	int nImages = 1;
	int channels[] = { 0 };
	cv::Mat hist; // calcHist()에서 반환되는 hist는 256X1 크기의 행렬로, 각 행은 해당 밝기 값의 빈도수를 나타낸다.
	int dims = 1;
	const int histSize[] = { 256 };
	float graylevel[] = { 0, 256 };
	const float* ranges[] = { graylevel };

	cv::calcHist(&src, nImages, channels, cv::noArray(), hist, dims, histSize, ranges);

	double maxVal;
	// 히스토그램에서 최대 빈도수 값을 찾는다
	cv::minMaxLoc(hist, nullptr, &maxVal);

	cv::Mat histImage(100, 256, CV_8UC3, cv::Scalar(255, 255, 255));
	for (int i = 0; i < 256; ++i)
		// 각 빈도수를 최대 빈도수로 정규화하여 100픽셀 높이로 표현한다
		cv::line(histImage, cv::Point(i, 100), cv::Point(i, 100 - cvRound(hist.at<float>(i, 0) / maxVal * 100)), cv::Scalar(0));

	// 옵션: 히스토그램에서 중간빈도수 위치에 빨간색 선을 그려서 시각적으로 표시한다
	int midFreq = Median(src);
	cv::line(histImage, cv::Point(midFreq, 100), cv::Point(midFreq, 0), cv::Scalar(0, 0, 255));

	return histImage;
}

int main() {
	cv::Mat image = cv::imread("palvin1.png");
	if (image.empty()) {
		std::cerr << "Could not read the image" << std::endl;
		return 1;
	}

	cv::cvtColor(image, image, cv::COLOR_BGR2GRAY);
	cv::Mat histImage = getHistImage(image);

	cv::imshow("Image", image);
	cv::imshow("Histogram", histImage);

	cv::waitKey(0);

	cv::destroyAllWindows();

	return 0;
}

 

 

 

빈도수의 중앙값 위치를 빨간색 선으로 표시한다.

 

반응형
Posted by J-sean
: