[OpenCV] OpenCV and memcpy (feat.CUDA)
Computer Vision 2026. 4. 27. 15:48 |반응형
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;
}

반응형
'Computer Vision' 카테고리의 다른 글
| [CUDA] Device Properties 디바이스 정보 확인 (0) | 2026.04.29 |
|---|---|
| [CUDA] C++ with CUDA (0) | 2026.04.28 |
| [OpenCV] CPU, GPU Image Processing Comparison 이미지 처리 속도 비교 (0) | 2026.04.26 |
| [OpenCV] OpenCV with CUDA Build (0) | 2026.04.26 |
| [OpenCV] Image Resize 영상 리사이즈 (feat.LogLevel) (0) | 2026.04.23 |
