웹캠 영상에서 움직임이 발생하는 영역을 찾아내고, 그 중심점으로 마우스 커서를 실시간 이동
1. 핵심 동작 원리
이 프로그램은 크게 세 가지 단계로 작동한다.
- 차영상(Frame Differencing) 감지: 이전 프레임과 현재 프레임의 차이를 계산하여 움직임이 있는 픽셀을 찾아낸다.
- absdiff(grayprev, gray, diff); 함수가 핵심
- 좌표 평활화(Smoothing): 마우스 커서가 너무 가늘게 떨리는 것을 방지하기 위해 deque 자료구조를 사용하여 최근 7개 좌표의 평균값을 계산한다.
- 윈도우 좌표 변환: 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 |