공부 노트/연구회

[연구회/OpenCV] 실시간 움직임 추적 마우스 컨트롤러

미로910 2026. 3. 3. 22:42

웹캠 영상에서 움직임이 발생하는 영역을 찾아내고, 그 중심점으로 마우스 커서를 실시간 이동

 

1. 핵심 동작 원리

이 프로그램은 크게 세 가지 단계로 작동한다.

  1. 차영상(Frame Differencing) 감지: 이전 프레임과 현재 프레임의 차이를 계산하여 움직임이 있는 픽셀을 찾아낸다.
    • absdiff(grayprev, gray, diff); 함수가 핵심
  2. 좌표 평활화(Smoothing): 마우스 커서가 너무 가늘게 떨리는 것을 방지하기 위해 deque 자료구조를 사용하여 최근 7개 좌표의 평균값을 계산한다.
  3. 윈도우 좌표 변환: OpenCV 창 내부의 좌표를 Windows 전체 화면 좌표로 변환하여 SetCursorPos로 마우스를 제어한다.
#include "opencv2/opencv.hpp"
#include <iostream>
#include <windows.h>
#include <vector>
#include <numeric>
#include <deque>

/*
웹캠을 사용하여 움직임을 감지하고 -> 움직임이 있는 영역의 중심 좌표로 마우스 커서를 실시간으로 이동
*/

using namespace cv;
using namespace std;

int main() {
    VideoCapture capture(0); // 카메라 (비디오 캡처 객체 생성)
    if (!capture.isOpened()) {
        cout << "ERROR: unable to open camera" << std::endl;
        return -1;
    }
/*
frame: 현재 시점의 컬러 프레임
prev: 이전 시점의 컬러 프레임
gray: 현재 시점의 흑백(grayscale) 프레임
grayprev: 이전 시점의 흑백 프레임
diff: 두 흑백 프레임 간의 차이를 저장할 프레임
*/

    Mat frame, prev, gray, grayprev, diff; // 비디오  프레임을 저장할 객체 선언
    capture >> prev; // prev에 저장
    cvtColor(prev, grayprev, COLOR_BGR2GRAY); // 흑백 변환

    const char* WINDOW_NAME = "main"; // 창 이름을 상수로 관리
    namedWindow(WINDOW_NAME, 1); // 창 생성

    const int SMOOTHING_WINDOW_SIZE = 7; // 좌표 7개 (평균)
    deque<Point> recent_centers;

    HWND hwnd = NULL; // 창 핸들을 저장할 변수

    while (true) {
        capture >> frame; // 새로운 프레임 저장
        if (frame.empty()) break;

        // --- 창 핸들을 한 번만 가져오도록 수정 ---
        if (hwnd == NULL) {
            // "main"이라는 이름의 창을 찾아 핸들(고유 ID)을 가져옵니다.
            hwnd = FindWindowA(NULL, WINDOW_NAME);
        }
        // ------------------------------------

        cvtColor(frame, gray, COLOR_BGR2GRAY); // 흑백 변환 -> gray에 저장
        absdiff(grayprev, gray, diff); // 움직임 감지 (절댓값 계산 -> diff 저장)

        int minx = frame.cols, maxx = 0, miny = frame.rows, maxy = 0;

        // 픽셀 순회
        // 픽셀 > 30 움직임
        for (int y = 0; y < diff.rows; y++) {
            for (int x = 0; x < diff.cols; x++) {
                if (diff.at<uchar>(y, x) > 30) {
                    if (x < minx) minx = x;
                    if (x > maxx) maxx = x;
                    if (y < miny) miny = y;
                    if (y > maxy) maxy = y;
                }
            }
        }

        // 움직임 감지 되었을 때만 실행
        if (maxx > minx && maxy > miny) {
            rectangle(frame, Point(minx, miny), Point(maxx, maxy), Scalar(0, 0, 255), 2); // frame에 감지 -> 빨간색 사각형

            // 현재 중심점 계산
            Point current_center(minx + (maxx - minx) / 2, miny + (maxy - miny) / 2);
            
            // 최근 중심점 목록 생신
            // SMOOTHING_WINDOW_SIZE(7개)를 초과하면 가장 오래된 데이터 삭제 (7개 좌표만 유지)
            recent_centers.push_back(current_center);
            if (recent_centers.size() > SMOOTHING_WINDOW_SIZE) {
                recent_centers.pop_front();
            }

            // 평균 중심점 계산
            Point smoothed_center(0, 0);
            for (const auto& pt : recent_centers) {
                smoothed_center += pt;
            }
            smoothed_center.x /= recent_centers.size();
            smoothed_center.y /= recent_centers.size();


            // 화면 좌표로 변환 및 마우스 커서 이동
            if (hwnd != NULL) {
                // 1. 창의 클라이언트 영역(실제 영상이 나오는 부분)의 시작점을 찾습니다.
                POINT client_top_left = { 0, 0 };
                ClientToScreen(hwnd, &client_top_left); // 창 좌표(0,0)을 스크린 좌표로 변환

                // 2. 창의 스크린 위치 + 사각형의 창 내부 위치 = 최종 스크린 위치 (화면 절대 좌표 계산)
                int final_screen_x = client_top_left.x + smoothed_center.x;
                int final_screen_y = client_top_left.y + smoothed_center.y;

                // 3. 계산된 최종 위치로 마우스 커서를 이동시킵니다.
                SetCursorPos(final_screen_x, final_screen_y);
            }
       
        }

        imshow(WINDOW_NAME, frame);

        if (waitKey(30) == 27) break;

        grayprev = gray.clone();
    }
    capture.release();
    destroyAllWindows();
    return 0;
}

📸 실행 결과 및 동작 설명

프로그램을 실행하면 위 스크린샷과 같이 두 개의 창(main, Grayscale Cam)

1. 실시간 움직임 영역 표시 (main 창)

  • 빨간색 사각형(Bounding Box): 웹캠 영상에서 이전 프레임과 비교해 움직임이 발생한 지점을 찾아 실시간으로 감싸준다.
  • 중심점 추적: 사각형의 정중앙 좌표를 계산하여 마우스 커서의 위치로 사용한다.

2. 영상 처리 과정 (Grayscale Cam 창)

  • 정확한 움직임 계산을 위해 원본 컬러 영상을 흑백으로 변환한 상태이다.
  • 이 흑백 영상의 픽셀 값 차이를 분석하여 어디가 움직였는지"를 컴퓨터가 판단하게 된다.

3. 마우스 커서 제어 현황

  • 영상 속 사각형이 움직이면 윈도우의 마우스 커서가 해당 위치를 부드럽게 따라간다.
  • 평균값 필터(Smoothing) 덕분에 미세한 떨림 없이 안정적으로 커서가 이동한다.

'공부 노트 > 연구회' 카테고리의 다른 글

[연구회/OpenCV] 원본 사진 흑백 사진으로 출력하기  (0) 2025.09.15
[개념] OpenCV란?  (1) 2025.09.15
[기초] class 개념  (1) 2025.09.15