반응형

배경을 제거하고 전경(마스크)만 남겨 보자.

 

#include <opencv2/opencv.hpp>

int main()
{
	cv::VideoCapture cap("Cars_On_Highway.mp4");
	if (!cap.isOpened())
	{
		std::cerr << "Error opening video stream or file" << std::endl;
		return -1;
	}

	int fps = static_cast<int>(cap.get(cv::CAP_PROP_FPS));

	cv::Ptr<cv::BackgroundSubtractor> pBgSub = cv::createBackgroundSubtractorMOG2(500, 16.0, false);
	// history: 모델이 배경으로 간주하기 위해 고려하는 프레임 수. 기본값은 500.
	// 최초 500 프레임이 배경 모델을 구축하는 데 사용되고, 이후 프레임은 모델 업데이트에 영향을 미친다.
	// 그러므로 최초에는 배경 모델이 구축될 수 있도록 충분한 프레임이 필요하다. history가 너무 작으면 모델이
	// 빠르게 업데이트되어 일시적인 변화에 민감해질 수 있고, 너무 크면 모델이 느리게 업데이트되어 변화에
	// 적응하는 데 시간이 걸릴 수 있다.
	// varThreshold: 픽셀이 배경 모델과 일치하는지 여부를 결정하는 분산 임계값. 기본값은 16.0.
	// 분산 임계값이 낮을수록 모델과 일치하는 픽셀이 더 많아지고, 높을수록 모델과 일치하는 픽셀이 더 적어진다.
	// detectShadows: 그림자 감지를 활성화할지 여부를 결정하는 부울 값. 기본값은 true.
	// 이 예에서는 detectShadows만 false로 설정하여 그림자 감지를 비활성화했다.
	// 배경은 0으로 표시되고 전경은 255, 그림자는 128로 표시된다.
	// 그림자는 배경 모델이 그림자를 배경으로 간주하지 않도록 도와주지만, 그림자 감지를 활성화하면 속도가 느려질 수 있다.

	cv::Mat frame;
	cv::Mat fgMask;

	while (true)
	{
		cap >> frame;
		if (frame.empty())
			break;

		cv::resize(frame, frame, cv::Size(), 0.5, 0.5);
		pBgSub->apply(frame, fgMask);
		// apply 메서드는 프레임을 입력으로 받아 fgMask에 전경 마스크를 출력한다.
		// fgMask는 8비트 단일 채널 이미지로, 배경 픽셀은 0, 전경 픽셀은 255로 표시된다.
		// apply 메서드는 또한 배경 모델을 업데이트한다. learningRate 매개변수를 사용하여 업데이트 속도를 제어할 수 있다.
		// learningRate가 음수이면 알고리즘이 자동으로 학습 속도를 선택한다. 0이면 모델이 업데이트되지 않고, 1이면 모델이
		// 마지막 프레임에서 재초기화된다.

		// 노이즈가 많을 경우, 노이즈 제거를 위해 모폴로지 연산을 적용한다. (열림 연산)
		//cv::Mat mask = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
		//cv::morphologyEx(fgMask, fgMask, cv::MORPH_OPEN, mask);

		cv::imshow("Frame", frame);
		cv::imshow("FG Mask", fgMask);

		if (cv::waitKey(1000 / fps) >= 0)
			break;
	}

	cv::Mat bgImage;
	pBgSub->getBackgroundImage(bgImage);
	// getBackgroundImage 메서드는 현재 배경 모델을 기반으로 배경 이미지를 반환한다.
	// 이 이미지는 배경 모델이 구축된 후에 호출해야 한다. 배경 제거 알고리즘이 배경으로 간주하는 픽셀의 평균값을 포함하는
	// 이미지를 확인할 수 있다. 이 이미지는 때때로 매우 흐릿할 수 있으며, 배경 통계가 포함되어 있기 때문이다.
	cv::imshow("BG Image", bgImage);
	cv::waitKey(0);

	cap.release();
	cv::destroyAllWindows();

	return 0;
}

 

분석된 전경(마스크) 영상

 

프로그램을 실행한 뒤 2~3초 후 생성된 배경 모델의 배경 이미지. 학습 시간이 너무 짧아 배경이 제대로 구축되지 않았다.

영상의 초반(이 예의 경우 500프레임)에 배경만 나오는 영상을 사용한다면 훨씬 나은 결과를 보일 것으로 생각된다.

 

충분한 시간이 흐른 후 생성된 배경 모델의 배경 이미지. 학습이 충분히 되어 적당한 배경이 구축되었다.

 

 

분석된 전경(마스크)을 이용해 움직이는 전경에 바운딩 박스를 표시해 보자.

#include <opencv2/opencv.hpp>

int main()
{
	cv::VideoCapture cap("Cars_On_Highway.mp4");

	if (!cap.isOpened())
	{
		std::cerr << "Error opening video stream or file" << std::endl;
		return -1;
	}

	int fps = static_cast<int>(cap.get(cv::CAP_PROP_FPS));

	cv::Ptr<cv::BackgroundSubtractor> pBgSub = cv::createBackgroundSubtractorMOG2(500, 16.0, false);
	// history: 모델이 배경으로 간주하기 위해 고려하는 프레임 수. 기본값은 500.
	// 최초 500 프레임이 배경 모델을 구축하는 데 사용되고, 이후 프레임은 모델 업데이트에 영향을 미친다.
	// 그러므로 최초에는 배경 모델이 구축될 수 있도록 충분한 프레임이 필요하다. history가 너무 작으면 모델이
	// 빠르게 업데이트되어 일시적인 변화에 민감해질 수 있고, 너무 크면 모델이 느리게 업데이트되어 변화에
	// 적응하는 데 시간이 걸릴 수 있다.
	// varThreshold: 픽셀이 배경 모델과 일치하는지 여부를 결정하는 분산 임계값. 기본값은 16.0.
	// 분산 임계값이 낮을수록 모델과 일치하는 픽셀이 더 많아지고, 높을수록 모델과 일치하는 픽셀이 더 적어진다.
	// detectShadows: 그림자 감지를 활성화할지 여부를 결정하는 부울 값. 기본값은 true.
	// 이 예에서는 detectShadows만 false로 설정하여 그림자 감지를 비활성화했다.
	// 배경은 0으로 표시되고 전경은 255, 그림자는 128로 표시된다.
	// 그림자는 배경 모델이 그림자를 배경으로 간주하지 않도록 도와주지만, 그림자 감지를 활성화하면 속도가 느려질 수 있다.

	cv::Mat frame;
	cv::Mat fgMask;

	std::vector<std::vector<cv::Point>> contours;

	while (true)
	{
		cap >> frame;
		if (frame.empty())
			break;

		cv::resize(frame, frame, cv::Size(), 0.3, 0.3);
		pBgSub->apply(frame, fgMask);
		// apply 메서드는 프레임을 입력으로 받아 fgMask에 전경 마스크를 출력한다.
		// fgMask는 8비트 단일 채널 이미지로, 배경 픽셀은 0, 전경 픽셀은 255로 표시된다.
		// apply 메서드는 또한 배경 모델을 업데이트한다. learningRate 매개변수를 사용하여 업데이트 속도를 제어할 수 있다.
		// learningRate가 음수이면 알고리즘이 자동으로 학습 속도를 선택한다. 0이면 모델이 업데이트되지 않고, 1이면 모델이
		// 마지막 프레임에서 재초기화된다.

		// 점 노이즈 제거 (열림 연산)
		cv::Mat maskOpen = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
		cv::morphologyEx(fgMask, fgMask, cv::MORPH_OPEN, maskOpen);

		// 사물이 여러 조각으로 나뉘는 것을 방지하기 위해 닫힘(Closing) 연산 적용
		// 분리된 작은 조각들이 하나로 뭉쳐진다. 필요하면 Size(15, 15)를 증감.
		cv::Mat maskClose = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(15, 15));
		cv::morphologyEx(fgMask, fgMask, cv::MORPH_CLOSE, maskClose);

		cv::findContours(fgMask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
		//std::cout << "Contours found: " << contours.size() << std::endl;

		double minArea = 300.0; // 합쳐지면서 전체 면적이 커지므로 최소 면적 임계값도 그에 맞게 조정
		for (size_t i = 0; i < contours.size(); ++i)
		{
			if (cv::contourArea(contours[i]) < minArea)
			{
				contours.erase(contours.begin() + i);
				// contours.erase의 매개변수는 삭제할 요소의 위치를 나타내는 반복자이므로, contours.begin() + i로 표현한다.
				// contours.erase(contours[i]); 이렇게 표현할 수 없다.
				--i; // 인덱스 조정				
			}
			else
			{
				// 윤곽선 대신 직사각형 바운딩 박스로 감싸서 묶어준다.
				cv::Rect rect = cv::boundingRect(contours[i]);
				cv::rectangle(frame, rect, cv::Scalar(0, 255, 0), 2);
			}
		}
		//std::cout << "Contours after filtering: " << contours.size() << std::endl;

		cv::imshow("Frame", frame);
		cv::imshow("FG Mask", fgMask);

		if (cv::waitKey(1000 / fps) >= 0)
			break;
	}

	cv::Mat bgImage;
	pBgSub->getBackgroundImage(bgImage);
	// getBackgroundImage 메서드는 현재 배경 모델을 기반으로 배경 이미지를 반환한다.
	// 이 이미지는 배경 모델이 구축된 후에 호출해야 한다. 배경 제거 알고리즘이 배경으로 간주하는 픽셀의 평균값을 포함하는
	// 이미지를 확인할 수 있다. 이 이미지는 때때로 매우 흐릿할 수 있으며, 배경 통계가 포함되어 있기 때문이다.
	cv::imshow("BG Image", bgImage);
	cv::waitKey(0);

	cap.release();
	cv::destroyAllWindows();

	return 0;
}

 

딥러닝을 이용한 객체 추척은 아니기 때문에 여러 객체가 뭉쳐서 표현되기도 한다.

 

 

전체 비디오에서 초반의 영상은 주요 학습 데이터로 빠르게 학습하고, 후반의 영상은 천천히 업데이트하고 싶다면 아래와 같이 코딩한다.

.
.
.
// 필요하다면 varThreshold를 높여서(16 -> 100) 조명 변화나 미세 노이즈에 덜 민감하게 만든다.
// 이 값이 커지면 웬만한 픽셀 값의 미세한 변화(빛 변화, 노이즈)는 전경으로 취급하지 않고 무시하게 된다.
cv::Ptr<cv::BackgroundSubtractor> pBgSub = cv::createBackgroundSubtractorMOG2(500, 100.0, false);

cv::Mat frame;
cv::Mat fgMask;

std::vector<std::vector<cv::Point>> contours;

while (true)
{
	cap >> frame;
	if (frame.empty())
		break;

	cv::resize(frame, frame, cv::Size(), 0.5, 0.5);

	//std::cout << "Processing frame: " << cap.get(cv::CAP_PROP_POS_FRAMES) << std::endl;
	
	if (cap.get(cv::CAP_PROP_POS_FRAMES) < 500)
	{
		pBgSub->apply(frame, fgMask, 0.1);
		// 최초 500 프레임 동안은 learningRate를 크게 설정하여 모델이 배경을 빠르게 구축할 수 있도록 한다. 이 기간 동안 모델이 배경을 학습하는 데 충분한 프레임이 필요하다.
		continue; // 실시간 영상이 아닌 녹화된 영상이므로 학습이 충분히 이루어질 때까지는 후속 처리(노이즈 제거, 윤곽선 검출, 화면 디스플레이 등)를 건너뛴다.
	}
	else {
		// 이후 프레임에서는 learningRate를 적절히 낮춰(예: 0.001) 모델이 일시적인 변화에 너무 민감해지는 것을 방지한다.
		// 거의 완전히 멈추면(0.00001) 노이즈까지 모두 전경으로 검출되므로, 서서히 적응할 수 있도록 0.001 ~ 0.005 정도를 부여한다.
		pBgSub->apply(frame, fgMask, 0.001);
	}
.
.
.

 

배경 모델의 배경 이미지(bgImage)를 계속 화면에 출력하도록 관련 부분을 루프 내로 옮기면 배경 이미지가 업데이트되는 것을 직접 확인할 수 있다. 위에서 사용한 영상들과는 다른 영상이지만 자동차 배기 가스가 배출되는 영상에서 업데이트되는 배경 이미지를 확인해 보자.

.
.
.
	cv::Mat bgImage;
	.
	.
	.
	while (true)
	{
	.
	.
	.
		pBgSub->getBackgroundImage(bgImage);

		cv::imshow("Frame", frame);
		cv::imshow("FG Mask", fgMask);
		cv::imshow("BG Image", bgImage);

		if (cv::waitKey(1000 / fps) >= 0)
			break;
	}
.
.
.

 

머플러 주위가 흰색 배기가스로 인해 천천히 업데이트되고 있다.

 

※ 참고

Background Subtraction with OpenCV and BGS Libraries

Background Subtraction

Improved Background-Foreground Segmentation Methods

 

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

간단한 OpenMP 병렬 프로그래밍 예제를 살펴보자.

 

우선 C/C++ - Language - Open MP Support - Yes (/openmp) 를 선택한다.

 

#include <stdio.h>
#include <omp.h>

int main() {
	// 5개의 스레드로 병렬 영역 실행
	#pragma omp parallel num_threads(5)
	{
		int id = omp_get_thread_num();
		int total = omp_get_num_threads();
		printf("Hello from thread %d out of %d threads\n", id, total);
	}

	return 0;
}

 

 

※ 참고

OpenMP C및 C++ 애플리케이션 프로그램 인터페이스

명령어

런타임 라이브러리 함수

예제

 

반응형
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
:

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

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

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

반응형

SDL3에 Dear ImGui를 사용해 보자. 아래 링크를 참고해 SDL3를 준비한다.

2025.03.30 - [C, C++] - [SDL3] Simple DirectMedia Layer 3 Setup and Getting Started - SDL3 설정 및 초기화

 

프로젝트 폴더에 위와 같이 ImGui 관련 파일을 복사한다. (lib, dll 파일은 없다)

 

프로젝트에 ImGui 필터를 추가하고 파일을 추가한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <stdio.h>
#include <Windows.h>
 
#include "imgui.h"
#include "imgui_impl_sdl3.h"
#include "imgui_impl_sdlrenderer3.h"
 
static SDL_Window* window = NULL;
static SDL_Renderer* renderer = NULL;
ImGuiIO* pio = NULL;
 
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
    SDL_SetAppMetadata("Example Renderer Clear""1.0""com.example.renderer-clear");
 
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    if (!SDL_CreateWindowAndRenderer("examples/renderer/clear"6404800&window, &renderer)) {
        SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    // Setup Dear ImGui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    // (void)io는 의미 없는 명령.  just to avoid compiler warning on unused variable
    pio = &io;
    // io가 SDL_AppIterate()에서 사용되기 때문에 전역 포인터 변수 pio에 대입.
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
    //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls
 
    // Setup Dear ImGui style
    //ImGui::StyleColorsDark();
    ImGui::StyleColorsLight();
 
    // Setup Platform/Renderer backends
    ImGui_ImplSDL3_InitForSDLRenderer(window, renderer);
    ImGui_ImplSDLRenderer3_Init(renderer);
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
{
    // Poll and handle events (inputs, window resize, etc.)
    // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
    // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
    // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
    // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
    ImGui_ImplSDL3_ProcessEvent(event);
 
    switch (event->type) {
    case SDL_EVENT_QUIT:
        return SDL_APP_SUCCESS;
    case SDL_EVENT_KEY_DOWN:
        printf("Key pressed: %s\n", SDL_GetKeyName(event->key.key));
        if (event->key.key == SDLK_ESCAPE)
            return SDL_APP_SUCCESS;
        break;
    default:
        break;
    }
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppIterate(void* appstate)
{
    // Start the Dear ImGui frame
    ImGui_ImplSDLRenderer3_NewFrame();
    ImGui_ImplSDL3_NewFrame();
    ImGui::NewFrame();
 
    // Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
    {
        static float f = 0.0f;
        static int counter = 0;
 
        ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it.
        //ImGui::SetWindowSize(ImVec2(100, 20)); // 매 프레임마다 윈도우 사이즈를 (100, 20)으로 지정한다.
        //ImGui::SetWindowSize(ImVec2(100, 20), ImGuiCond_Once); // 프로그램을 시작할때 한 번만 윈도우 사이즈를 지정한다.
        //ImGui::SetWindowSize(ImVec2(100, 20), ImGuiCond_FirstUseEver); // 프로그램을 처음 시작할때(imgui.ini파일이 존재하지 않을때)
                                                                         // 한 번만 윈도우 사이즈를 지정한다.
        ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too)
        if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
            counter++;
        ImGui::SameLine();
        ImGui::Text("counter = %d", counter);
        ImGui::Text("Application average %.3f ms/frame (%.1f FPS)"1000.0f / pio->Framerate, pio->Framerate);
        ImGui::End();
    }
    // 윈도우 없이 위치가 고정된 버튼 생성하기.
    {
        ImGui::Begin("No Window"0, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration);
        ImGui::SetWindowPos(ImVec2(1020));
        ImGui::SetWindowSize(ImVec2(10020));
        // WindowSize()는 이 예제 블럭에서 생성하려는 버튼의 사이즈를 지정하는 함수가 아니다. 버튼을 포함하는 윈도우의 사이즈다.
        // 이 예제는 윈도우가 보이지 않으므로 사이즈를 지정하지 않으면 버튼 사이즈보다 훨씬 크게 지정되서 근처의 다른 UI가
        // 이 윈도우에 가려져 제대로 동작하지 않을 수 있다. (보이기는 하지만 클릭이 되지 않는다)
        // 아니면, Begin()에서 ImGuiWindowFlags_NoBringToFrontOnFocus 옵션을 추가하면 다른 윈도우의 UI 동작을 방해하지 않는다.
        // 이 예제에서 ImGuiWindowFlags_NoInputs 옵션은 버튼을 비활성화 시키므로 사용하면 안된다.        
        ImGui::Button("Fixed Button");
        ImGui::End();
    }
 
    // Rendering
    ImGui::Render();
 
    SDL_SetRenderDrawColor(renderer, 255255255, SDL_ALPHA_OPAQUE);
    SDL_RenderClear(renderer);
    ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), renderer);
    SDL_RenderPresent(renderer);
 
    return SDL_APP_CONTINUE;
}
 
void SDL_AppQuit(void* appstate, SDL_AppResult result)
{
    // Cleanup    
    ImGui_ImplSDLRenderer3_Shutdown();
    ImGui_ImplSDL3_Shutdown();
    ImGui::DestroyContext();
 
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}
 

 

소스를 입력하고 빌드한다.

 

실행하면 GUI가 표시된다.

 

이번엔 ImGui를 이용해 이미지를 출력해 보자.

 

stb_image.h를 추가한다.
stb_image.h
0.27MB

 

Image Loading and Displaying Examples

위 링크를 참고해 소스를 작성한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#define _CRT_SECURE_NO_WARNINGS
#define STB_IMAGE_IMPLEMENTATION
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <stdio.h>
#include <Windows.h>
 
#include "imgui.h"
#include "imgui_impl_sdl3.h"
#include "imgui_impl_sdlrenderer3.h"
 
#include "stb_image.h"
 
static SDL_Window* window = NULL;
static SDL_Renderer* renderer = NULL;
ImGuiIO* pio = NULL;
 
SDL_Texture* my_texture;
int my_image_width, my_image_height;
 
bool LoadTextureFromMemory(const void* data, size_t data_size, SDL_Renderer* renderer, SDL_Texture** out_texture, int* out_width, int* out_height)
{
    int image_width = 0;
    int image_height = 0;
    int channels = 4;
    unsigned char* image_data = stbi_load_from_memory((const unsigned char*)data, (int)data_size, &image_width, &image_height, NULL4);
    if (image_data == nullptr)
    {
        fprintf(stderr, "Failed to load image: %s\n", stbi_failure_reason());
        return false;
    }
 
    SDL_Surface* surface = SDL_CreateSurfaceFrom(image_width, image_height, SDL_PIXELFORMAT_RGBA32, (void*)image_data, channels * image_width);
    if (surface == nullptr)
    {
        fprintf(stderr, "Failed to create SDL surface: %s\n", SDL_GetError());
        return false;
    }
 
    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
    if (texture == nullptr)
        fprintf(stderr, "Failed to create SDL texture: %s\n", SDL_GetError());
 
    *out_texture = texture;
    *out_width = image_width;
    *out_height = image_height;
 
    SDL_DestroySurface(surface);
    stbi_image_free(image_data);
 
    return true;
}
 
// Open and read a file, then forward to LoadTextureFromMemory()
bool LoadTextureFromFile(const char* file_name, SDL_Renderer* renderer, SDL_Texture** out_texture, int* out_width, int* out_height)
{
    FILE* f = fopen(file_name, "rb");
    if (f == NULL)
        return false;
    fseek(f, 0, SEEK_END);
    size_t file_size = (size_t)ftell(f);
    if (file_size == -1)
        return false;
    fseek(f, 0, SEEK_SET);
    void* file_data = IM_ALLOC(file_size);
    fread(file_data, 1, file_size, f);
    fclose(f);
    bool ret = LoadTextureFromMemory(file_data, file_size, renderer, out_texture, out_width, out_height);
    IM_FREE(file_data);
    return ret;
}
 
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
    SDL_SetAppMetadata("Example Renderer Clear""1.0""com.example.renderer-clear");
 
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    if (!SDL_CreateWindowAndRenderer("examples/renderer/clear"6404800&window, &renderer)) {
        SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    // Setup Dear ImGui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    // (void)io는 의미 없는 명령.  just to avoid compiler warning on unused variable
    pio = &io;
    // io가 SDL_AppIterate()에서 사용되기 때문에 전역 포인터 변수 pio에 대입.
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
    //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls
 
    // Setup Dear ImGui style
    //ImGui::StyleColorsDark();
    ImGui::StyleColorsLight();
 
    // Setup Platform/Renderer backends
    ImGui_ImplSDL3_InitForSDLRenderer(window, renderer);
    ImGui_ImplSDLRenderer3_Init(renderer);
 
    // Load our texture after initialising SDL
    bool ret = LoadTextureFromFile("palvin.jpg", renderer, &my_texture, &my_image_width, &my_image_height);
    IM_ASSERT(ret);
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
{
    // Poll and handle events (inputs, window resize, etc.)
    // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
    // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
    // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
    // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
    ImGui_ImplSDL3_ProcessEvent(event);
 
    switch (event->type) {
    case SDL_EVENT_QUIT:
        return SDL_APP_SUCCESS;
    case SDL_EVENT_KEY_DOWN:
        printf("Key pressed: %s\n", SDL_GetKeyName(event->key.key));
        if (event->key.key == SDLK_ESCAPE)
            return SDL_APP_SUCCESS;
        break;
    default:
        break;
    }
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppIterate(void* appstate)
{
    // Start the Dear ImGui frame
    ImGui_ImplSDLRenderer3_NewFrame();
    ImGui_ImplSDL3_NewFrame();
    ImGui::NewFrame();
 
    // Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
    {
        static float f = 0.0f;
        static int counter = 0;
 
        ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it.
        ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too)
        if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
            counter++;
        ImGui::SameLine();
        ImGui::Text("counter = %d", counter);
        ImGui::Text("Application average %.3f ms/frame (%.1f FPS)"1000.0f / pio->Framerate, pio->Framerate);
        ImGui::End();
    }
    // 윈도우 없이 위치가 고정된 이미지 생성하기.
    {
        ImGui::Begin("No Window"0, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoInputs);
        ImGui::SetWindowPos(ImVec2(00));
        ImGui::Text("pointer = %p", my_texture);
        ImGui::Text("size = %d x %d", my_image_width, my_image_height);
        ImGui::Image((ImTextureID)(intptr_t)my_texture, ImVec2((float)my_image_width, (float)my_image_height));
        ImGui::End();
    }
 
    // Rendering
    ImGui::Render();
 
    SDL_SetRenderDrawColor(renderer, 255255255, SDL_ALPHA_OPAQUE);
    SDL_RenderClear(renderer);
    ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), renderer);
    SDL_RenderPresent(renderer);
 
    return SDL_APP_CONTINUE;
}
 
void SDL_AppQuit(void* appstate, SDL_AppResult result)
{
    // Cleanup    
    ImGui_ImplSDLRenderer3_Shutdown();
    ImGui_ImplSDL3_Shutdown();
    ImGui::DestroyContext();
 
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}
 

 

 

실행하면 이미지가 표시된다.

 

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

C와 Python 프로그램간 이미지 데이터를 공유해 보자.

 

1) 파이썬 프로그램 데이터를 C 프로그램에 공유

mmap

 

import time
import mmap
import cv2

frame = cv2.imread("image.jpg", cv2.IMREAD_COLOR)
h, w, c = frame.shape
buffer_size = h * w * c

mm = mmap.mmap(-1, buffer_size, "Local\\MySharedMemory")

try:
    mm.write(frame.tobytes())

    while True:
        time.sleep(1000)  # Sleep to prevent busy waiting.
finally:
    mm.close()

 

#include <opencv2/opencv.hpp>
#include <Windows.h>

int main() {
    int height = 495;
    int width = 396;
    int channels = 3;
    int buffersize = height * width * channels;

    byte* buffer = new byte[buffersize];
    memset(buffer, 0, buffersize);

    HANDLE hFMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, buffersize, L"MySharedMemory");
    if (hFMap == NULL)
        return -1;

    TCHAR* PtrInFile = (TCHAR*)MapViewOfFile(hFMap, FILE_MAP_ALL_ACCESS, 0, 0, buffersize);
    if (PtrInFile == NULL)
        return -1;

    memcpy(buffer, PtrInFile, buffersize);
    // memcpy로 메모리 내용을 buffer로 복사할 필요없이 아래 Mat 생성자에서 PtrInFile을 인수로 줘도 된다.

    cv::Mat image = cv::Mat(height, width, CV_8UC3, buffer);
    if (image.empty())
        return -1;

    // 사용할 Mat이 이미 존재한다면 아래처럼 memcpy()를 사용할 수도 있다.
    //cv::Mat image(height, width, CV_8UC3); // 이미 존재하는 Mat
    //memcpy(image.data, buffer, buffersize); // buffer 사용하지 않고 메모리에서 image.data로 직접 복사해도 된다.

    cv::imshow("image", image);
    cv::waitKey(0);
        
    UnmapViewOfFile(PtrInFile);
    CloseHandle(hFMap);
    delete[] buffer;

    return 0;
}

 

 

파이썬 프로그램을 먼저 실행하고 C 프로그램을 실행한다.

 

 

2) C 프로그램 데이터를 파이썬 프로그램에 공유

 

#include <opencv2/opencv.hpp>
#include <Windows.h>

int main() {
    cv::Mat image = cv::imread("image.jpg");
    if (image.empty())
        return -1;

    int height = image.rows;
    int width = image.cols;
    int channels = image.channels();
    int buffersize = height * width * channels;

    HANDLE hFMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, buffersize, L"MySharedMemory");
    if (hFMap == NULL)
        return -1;

    TCHAR* PtrInFile = (TCHAR*)MapViewOfFile(hFMap, FILE_MAP_ALL_ACCESS, 0, 0, buffersize);
    if (PtrInFile == NULL)
        return -1;

    memcpy(PtrInFile, image.data, buffersize);
    // 버퍼를 사용하지 않고 바로 메모리에 데이터 복사.
        
    int a;
    std::cin >> a;
    // 대기

    UnmapViewOfFile(PtrInFile);
    CloseHandle(hFMap);
    
    return 0;
}

 

import mmap
import numpy as np
import cv2

height = 495
width = 396
channels = 3
buffer_size = height * width * channels

mm = mmap.mmap(-1, buffer_size, "Local\\MySharedMemory")

try:    
    buffer = mm.read(buffer_size)
    image_arr = np.frombuffer(buffer, np.ubyte)
    image = image_arr.reshape(height, width, channels)
    
    cv2.imshow("image", image)
    cv2.waitKey(0)
finally:
    mm.close()
    cv2.destroyAllWindows()

 

결과는 같다.

 

※ 참고

2025.02.16 - [OpenCV] - C# and Python OpenCV Image Data Share (Memory Mapped File)

2026.02.18 - [OpenCV] - [OpenCV] C# 에서 C++ DLL 사용하기

2026.02.19 - [OpenCV] - [OpenCV] Python 에서 C++ DLL 사용하기

 

반응형
Posted by J-sean
: