OpenCV C++ vs python

2025. 1. 15. 14:36c,c++

아래 내용은 이미지/영상 처리 파이프라인을 C++과 Python 각각으로 구현하고, 멀티스레드를 적용해 성능 차이를 비교해보기 위한 예시 가이드입니다. 실제 구현 시에는 상황에 맞춰 코드를 조정하거나, 더 복잡한 기능(예: 객체 검출, 딥러닝 추론 등)을 추가할 수 있습니다. 여기서는 간단한 예시로써 프레임 단위로 블러(blur) 처리를 적용하는 코드를 살펴보고자 합니다.


1. 파이프라인 개요

  1. 영상 소스 준비
    • 예: mp4 동영상 파일
    • 고화질(예: 1920×1080)로 준비하면 CPU 사용량이 더 커서 성능 차이를 확인하기 쉬움
  2. 프레임 단위 읽기
    • OpenCV를 사용해 동영상 파일로부터 한 프레임씩 읽어옴
  3. 이미지 처리(블러 등)
    • 단순 예시: cv::GaussianBlur (C++) / cv2.GaussianBlur (Python)
    • GPU가 아닌 CPU에서만 처리하도록 설정
    • 멀티스레드를 적용해 여러 프레임(또는 영상의 구간)을 나눠서 병렬 처리
  4. 처리 시간 / CPU 사용률 / 메모리 사용량 측정
    • C++: <chrono> 라이브러리 사용
    • Python: time 혹은 timeit 등으로 측정
    • 시스템 차원에서 CPU 점유율이나 메모리는 htop(리눅스) 또는 Windows Task Manager 등으로 관찰 가능
  5. 스레드 개수 변화
    • 1개, 2개, 4개, 8개 등으로 스레드 수를 바꿔가며 측정
    • 프레임 처리 속도(FPS) 또는 전체 처리 시간(초) 비교

2. C++ 예시

아래 코드는 간단히 영상의 모든 프레임에 블러를 적용한 후, 결과를 저장하지 않고(또는 별도 처리 없이) 처리하는 예시입니다. 실제로는 처리된 영상을 cv::VideoWriter로 저장하거나, 별도의 로직을 추가할 수 있습니다.

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <thread>
#include <chrono>

void processFrames(const cv::Mat& inputFrame, cv::Mat& outputFrame) {
    // 예시: 가우시안 블러
    cv::GaussianBlur(inputFrame, outputFrame, cv::Size(15, 15), 0);
}

int main() {
    // 동영상 파일 열기
    cv::VideoCapture cap("test_video.mp4");
    if (!cap.isOpened()) {
        std::cerr << "Failed to open video." << std::endl;
        return -1;
    }

    // 총 프레임 수 및 fps
    int totalFrames = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_COUNT));
    double fps = cap.get(cv::CAP_PROP_FPS);
    std::cout << "Total Frames: " << totalFrames << std::endl;
    std::cout << "FPS: " << fps << std::endl;

    // 모든 프레임을 미리 메모리에 로드 (메모리 사용량 많아질 수 있음)
    std::vector<cv::Mat> frames(totalFrames);
    for (int i = 0; i < totalFrames; ++i) {
        cap >> frames[i];
        if (frames[i].empty()) break;
    }
    cap.release();

    // 결과 저장할 벡터
    std::vector<cv::Mat> processedFrames(totalFrames);

    // 스레드 수 설정 (예: 4개)
    int numThreads = 4;

    // 타이머 시작
    auto start = std::chrono::high_resolution_clock::now();

    // 처리 함수 (람다식) - startIdx부터 endIdx-1까지 처리
    auto worker = [&](int startIdx, int endIdx) {
        for (int i = startIdx; i < endIdx; ++i) {
            if (!frames[i].empty()) {
                processFrames(frames[i], processedFrames[i]);
            }
        }
    };

    // 스레드를 담아둘 벡터
    std::vector<std::thread> threads;
    threads.reserve(numThreads);

    // 할당할 작업량 계산
    int chunkSize = totalFrames / numThreads;
    int remainder = totalFrames % numThreads;

    int currentStart = 0;
    for (int t = 0; t < numThreads; ++t) {
        int currentEnd = currentStart + chunkSize;
        // 나머지 프레임이 있으면 분배
        if (t < remainder) {
            currentEnd += 1;
        }

        threads.emplace_back(worker, currentStart, currentEnd);
        currentStart = currentEnd;
    }

    // 모든 스레드 합류
    for (auto &th : threads) {
        if (th.joinable()) {
            th.join();
        }
    }

    // 타이머 종료
    auto end = std::chrono::high_resolution_clock::now();
    double elapsedSec = std::chrono::duration<double>(end - start).count();
    std::cout << "Processing time: " << elapsedSec << " seconds" << std::endl;

    return 0;
}

C++ 코드 설명

  1. 동영상 로드
    • cv::VideoCapture cap("test_video.mp4");
    • 전체 프레임 수, FPS 등 메타정보 얻기
  2. 프레임 일괄 로드
    • std::vector<cv::Mat> frames(totalFrames);
    • 동영상에서 각 프레임을 벡터에 저장
  3. 멀티스레드 적용
    • std::thread를 여러 개 생성
    • 각 스레드는 일부 구간(startIdx~endIdx)의 프레임만 처리
  4. 처리 함수
    • processFrames()에서 cv::GaussianBlur를 적용
  5. 시간 측정
    • <chrono>로 시작~종료 시점까지 걸린 시간 계산
  6. 결과 저장
    • 여기서는 단순히 processedFrames 벡터에 저장하고 끝.
      실제로는 cv::VideoWriter를 통해 영상 파일로 저장 가능

3. Python 예시

아래 예시는 기본적으로 같은 로직을 Python에 옮긴 코드입니다. Python은 GIL(Global Interpreter Lock) 때문에 CPU-bound 연산의 경우, 멀티스레드를 쓰더라도 스레드 간에 실제 병렬 실행이 아닌 시분할(Thread Switching) 방식이 될 수 있습니다.
따라서 멀티프로세싱을 사용하거나, 내부적으로 C/C++로 작성된 NumPy/ OpenCV 부분이 병렬 처리되도록 기대할 수 있습니다.

import cv2
import time
import multiprocessing

def process_frame(frame):
    # 가우시안 블러
    return cv2.GaussianBlur(frame, (15, 15), 0)

def worker(frames_slice, output_dict, index):
    processed = []
    for f in frames_slice:
        if f is not None:
            processed.append(process_frame(f))
        else:
            processed.append(None)
    output_dict[index] = processed

if __name__ == "__main__":
    cap = cv2.VideoCapture("test_video.mp4")
    if not cap.isOpened():
        print("Failed to open video.")
        exit(1)

    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    fps = cap.get(cv2.CAP_PROP_FPS)
    print("Total Frames:", total_frames)
    print("FPS:", fps)

    # 모든 프레임 메모리에 로드
    frames = []
    for _ in range(total_frames):
        ret, f = cap.read()
        if not ret:
            frames.append(None)
        else:
            frames.append(f)
    cap.release()

    # 멀티프로세싱: CPU 코어 수에 맞춰 설정
    num_processes = 4

    start_time = time.time()

    # 나눠 처리할 구간 계산
    chunk_size = total_frames // num_processes
    remainder = total_frames % num_processes

    processes = []
    manager = multiprocessing.Manager()
    output_dict = manager.dict()

    current_start = 0
    for i in range(num_processes):
        current_end = current_start + chunk_size
        if i < remainder:
            current_end += 1

        slice_frames = frames[current_start:current_end]
        p = multiprocessing.Process(target=worker, args=(slice_frames, output_dict, i))
        processes.append(p)
        p.start()

        current_start = current_end

    for p in processes:
        p.join()

    # output_dict에 각 프로세스별 결과가 저장됨
    # 최종적으로 합치려면 index 순서대로 병합
    processed_frames = []
    for i in range(num_processes):
        processed_frames.extend(output_dict[i])

    elapsed_time = time.time() - start_time
    print("Processing time:", elapsed_time, "seconds")

Python 코드 설명

  1. 동영상 로드
    • cv2.VideoCapture("test_video.mp4")
    • 모든 프레임을 리스트 frames에 저장
  2. 멀티프로세싱
    • multiprocessing.Process를 여러 개 띄우고, 각 프로세스가 일부 프레임 구간을 처리
    • 결과는 multiprocessing.Manager().dict()를 통해 공유 영역에 저장
  3. 처리 함수
    • process_frame()에서 cv2.GaussianBlur 적용
  4. 결과 병합
    • 프로세스별로 나뉘어 처리된 데이터를 모아서 processed_frames에 확장
  5. 시간 측정
    • time.time() 사용

 

4. 성능 비교 아이디어

  1. 스레드(프로세스) 개수 변화
    • C++: 1, 2, 4, 8개 등으로 numThreads를 바꿔가며 테스트
    • Python: num_processes를 1, 2, 4, 8 등으로 바꿔가며 측정
  2. 다양한 해상도 / 다양한 영상 길이
    • HD(1280×720), Full HD(1920×1080), 4K(3840×2160) 등
    • 1분짜리, 10분짜리 등 영상 길이도 달리 테스트
  3. 처리 알고리즘 변경
    • 단순 블러 → 영상 내 객체 탐지, 얼굴 인식, 에지 검출 등 CPU 부담이 큰 작업
    • OpenCV DNN 모듈 등을 이용해 딥러닝 추론 테스트
  4. 프로파일링
    • C++: perf, gprof, Visual Studio Performance Tools 등
    • Python: cProfile, line_profiler, py-spy 등
  5. 결과 정리
    • 총 처리 시간(초)
    • 평균 FPS(= 총 프레임 수 / 총 처리 시간)
    • CPU 사용률(%)
    • 메모리 사용량(MB)

5. 주의사항

  • 메모리 사용량: 모든 프레임을 한꺼번에 메모리에 읽어들이면 시스템 메모리 많이 소모 가능. 상황에 따라 스트리밍 방식(프레임 하나씩 읽고 처리 후 저장)을 써야 할 수도 있음.
  • 디버깅 난이도: 멀티스레딩/멀티프로세싱은 디버그가 복잡하므로, 간단한 버전부터 차근차근 확장
  • GPU 활용: CUDA, OpenCL 등의 GPU 가속을 사용하면 CPU-바운드 성능 비교와는 또 다른 결과가 나올 수 있음
  • GIL: Python은 GIL 때문에 CPU 연산을 멀티스레드로 병렬 처리하기 어렵습니다. 멀티프로세싱을 활용하거나, C/C++ 라이브러리 자체에서 멀티스레딩을 해주는 방식을 사용해야 합니다.

 

 

C++ (1,4,8,16)

스레드 1개
스레드 4개
스레드 8개
스레드 16개

Python (1,4,8,16)

프로세스 1개
프로세스 4개
프로세스 8개
프로세스 16개

360p영상 C++(스레드) Python(멀티프로세싱)
1개 0.233199 1.980214
4개 0.255602 1.808919
8개 0.199378 1.896996
16개 0.182297 1.727693

 

C++과 파이썬은 거의 10배의 속도차이가 났다. 하지만 각각 스레드나 프로세스의 개수에 따라 유의미한 큰차이는 나지 않았다.

4k영상 C++(스레드) Python(멀티프로세싱)
1개 1.14439 17.99318814277649
16개 1.14424 14.656228303909302

 

4k영상을 돌려봤을때 C++과 Python의 차이는 더욱 커졌다. 그리고 C++의 멀티스레드 같은 경우엔 1개나 16개나 차이가 나지 않았지만 Python의 멀티 프로세싱은 3초라는 큰 차이가 났다.

 

왜 Python은 이렇게 차이 나는가?

  • 네이티브 코드 vs. 인터프리터: C++은 컴파일된 바이너리를 직접 실행하는 반면, Python은 인터프리터가 중간에서 동작.
  • 멀티프로세스 오버헤드: Python에선 “스레드”가 아니라 “프로세스”를 늘려야 GIL을 우회 → 프로세스 생성/통신 비용이 큼.
  • 데이터 구조/관리: Python의 자료 구조(list, dict, pickle 등)은 C++ STL에 비해 추상화 비용이 더 커서, 대량의 데이터(예: 4K 프레임)를 처리할 때 오버헤드가 쌓임.

전체 결론

  1. C++ 측
    • 가우시안 블러 같은 영상 처리 연산은 내부적으로 매우 최적화된 네이티브 코드여서, 1스레드만으로도 1.14초 정도면 끝남.
    • 스레드를 16개로 늘려도 작업량이 크지 않거나 병렬화 오버헤드가 커서 처리 시간이 거의 그대로.
  2. Python 측
    • 1개 프로세스로 처리하면 C++ 대비 훨씬 더 큰(약 18초) 시간이 걸리지만,
    • 멀티프로세싱(16개)으로 바꾸니 약 14.66초로 줄어들어 병렬화 이득은 존재.
    • 다만, C++ 네이티브 성능에 비해 여전히 큰 격차가 있다.