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

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

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

반응형

1개 이상의 동영상 파일을 로드하고 재생해 보자.

 

아래 코드는 각각의 영상을 독립적으로 처리하기 때문에 시간 표시도 개별적으로 되고 앞뒤 이동도 제한된다.

 

#include <iostream>
#include <vector>
#include <filesystem>
#include <opencv2/opencv.hpp>

int main()
{
	std::vector<std::string> videoFiles;
	std::string folderPath = "./";

	for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(folderPath))
	{
		if (entry.is_regular_file() && entry.path().extension() == ".mkv")
		{
			videoFiles.push_back(entry.path().string());
		}
	}

	std::cout << "Found " << videoFiles.size() << " .mkv files in the folder:" << std::endl;
	for (const std::string& file : videoFiles)
	{
		std::cout << file << std::endl;
	}

	std::vector<cv::VideoCapture> videoCaptures;
	for (const std::string& file : videoFiles)
	{
		cv::VideoCapture cap(file);
		if (!cap.isOpened())
		{
			std::cerr << "Error opening video file: " << file << std::endl;
			continue;
		}
		videoCaptures.push_back(std::move(cap)); // Move the VideoCapture object into the vector
	}

	std::cout << "Successfully opened " << videoCaptures.size() << " video files." << std::endl;

	cv::Mat frame;
	double timestamp = 0.0;
	bool quit = false;
	std::string time;
	int key = 0;

	for (size_t i = 0; i < videoCaptures.size(); ++i)
	{
		std::cout << "Playing video: " << videoFiles[i] << std::endl;
		videoCaptures[i] >> frame;

		while (!frame.empty())
		{
			timestamp = videoCaptures[i].get(cv::CAP_PROP_POS_MSEC); // Get the current timestamp
			time = std::to_string(timestamp / 1000.0);
			time = time.substr(0, time.find(".") + 3); // Keep only 2 decimal places

			cv::putText(frame, time, cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 255, 0), 2);
			cv::imshow("Video", frame);

			key = cv::waitKey(33);
			if (key == 27)
			{
				quit = true;
				break;
			}
			else if (key == 'f')
				// 5초 앞으로
				videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, timestamp + 5.0 * 1000.0);
			else if (key == 'b')
				 // 5초 뒤로
				videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, timestamp - 5.0 * 1000.0);

			videoCaptures[i] >> frame;
		}

		if (quit)
			break;
	}

	for (cv::VideoCapture& cap : videoCaptures)
		cap.release();

	cv::destroyAllWindows();

	return 0;
}

 

f: 5초 앞으로

b: 5초 뒤로

 

 

아래 코드는 각각의 영상을 하나의 영상인 것처럼 시간을 표시하고 5초 앞뒤로 이동할 때도 각 영상을 자연스럽게 이동한다.

 

#include <iostream>
#include <vector>
#include <filesystem>
#include <opencv2/opencv.hpp>

int main()
{
	std::vector<std::string> videoFiles;
	std::string folderPath = "./";

	for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(folderPath))
	{
		if (entry.is_regular_file() && entry.path().extension() == ".mkv")
		{
			videoFiles.push_back(entry.path().string());
		}
	}

	std::cout << "Found " << videoFiles.size() << " .mkv files in the folder:" << std::endl;
	for (const std::string& file : videoFiles)
	{
		std::cout << file << std::endl;
	}

	std::vector<cv::VideoCapture> videoCaptures;
	for (const std::string& file : videoFiles)
	{
		cv::VideoCapture cap(file);
		if (!cap.isOpened())
		{
			std::cerr << "Error opening video file: " << file << std::endl;
			continue;
		}
		videoCaptures.push_back(std::move(cap)); // Move the VideoCapture object into the vector
	}

	std::cout << "Successfully opened " << videoCaptures.size() << " video files." << std::endl;

	cv::Mat frame;
	double timestamp = 0.0;
	bool quit = false;
	std::string time;
	double duration = 0.0;
	double move = 5000.0; // 5 seconds in milliseconds
	int key = 0;

	for (size_t i = 0; i < videoCaptures.size(); ++i)
	{
		std::cout << "Playing video: " << videoFiles[i] << std::endl;
		videoCaptures[i] >> frame;

		duration = 0.0;
		for (int j = 0; j < i; j++) {
			duration += (videoCaptures[j].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[j].get(cv::CAP_PROP_FPS));
		}
		duration *= 1000.0; // Convert to milliseconds

		while (!frame.empty())
		{
			timestamp = duration + videoCaptures[i].get(cv::CAP_PROP_POS_MSEC); // Get the current timestamp
			time = std::to_string(timestamp / 1000.0);
			time = time.substr(0, time.find(".") + 3); // Keep only 2 decimal places

			cv::putText(frame, time, cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 255, 0), 2);
			cv::imshow("Video", frame);

			key = cv::waitKey(33);
			if (key == 27)
			{
				quit = true;
				break;
			}
			else if (key == 'f')
			{
				// 현재 영상에서 5초 앞으로 가는 것이 가능한 경우, 5초 앞으로 이동
				if (videoCaptures[i].get(cv::CAP_PROP_POS_MSEC) + move < videoCaptures[i].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[i].get(cv::CAP_PROP_FPS) * 1000.0)
					videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, videoCaptures[i].get(cv::CAP_PROP_POS_MSEC) + move);
				// 현재 영상에서 5초 앞으로 가는 것이 불가능한 경우, 가능한 만큼만 이동하고 다음 영상으로 이동해서 남은 시간만큼 앞으로 이동
				else if (i < videoCaptures.size() - 1)
				{
					videoCaptures[i + 1].set(cv::CAP_PROP_POS_MSEC, move - (videoCaptures[i].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[i].get(cv::CAP_PROP_FPS) * 1000.0 - videoCaptures[i].get(cv::CAP_PROP_POS_MSEC)));

					// 현재 영상(i)을 나중에 다시 재생할 때 처음부터 재생될 수 있도록 위치를 0으로 초기화하는건 불필요.
					//videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, 0.0);

					break;
				}
				else
				{
					//videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, videoCaptures[i].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[i].get(cv::CAP_PROP_FPS) * 1000.0);
				}

			}
			else if (key == 'b')
			{
				// 현재 영상에서 5초 뒤로 가는 것이 가능한 경우, 5초 뒤로 이동
				if (videoCaptures[i].get(cv::CAP_PROP_POS_MSEC) > move)
					videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, videoCaptures[i].get(cv::CAP_PROP_POS_MSEC) - move);
				// 현재 영상에서 5초 뒤로 가는 것이 불가능한 경우, 가능한 만큼만 이동하고 이전 영상으로 이동해서 남은 시간만큼 뒤로 이동
				else if (i > 0)
				{
					videoCaptures[i - 1].set(cv::CAP_PROP_POS_MSEC, videoCaptures[i - 1].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[i - 1].get(cv::CAP_PROP_FPS) * 1000.0 - (move - videoCaptures[i].get(cv::CAP_PROP_POS_MSEC)));

					// 현재 영상(i)을 곧(5초 이내) 다시 재생할 때 처음부터 재생될 수 있도록 위치를 0으로 초기화.
					videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, 0.0);

					i -= 2; // 다음 루프에서 i++ 되므로 -2

					break;
				}
				else
				{
					videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, 0.0);
				}
			}

			videoCaptures[i] >> frame;
		}

		if (quit)
			break;
	}

	for (cv::VideoCapture& cap : videoCaptures)
		cap.release();

	cv::destroyAllWindows();

	return 0;
}

 

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

마우스로 마스크 포인트의 삽입, 삭제, 이동을 구현해 보자.

 

#include <iostream>
#include <opencv2/opencv.hpp>

cv::Point nearPointOnSegment(cv::Point2f p, cv::Point2f a, cv::Point2f b) {
	float l2 = std::powf(a.x - b.x, 2) + std::powf(a.y - b.y, 2); // 선분의 길이 제곱
	if (l2 == 0.0f) // a와 b가 같은 경우
		return cv::Point();

	// 선분 위로 점 p의 사영(Projection) 위치 t 계산(투영된 지점의 비율)
	// t = [(p-a) dot (b-a)] / |b-a|^2
	float t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / l2;

	// t가 0~1 사이면 선분 위(0이면 a, 1이면 b), 그렇지 않으면 선분 밖
	// 0보다 작으면 a쪽, 1보다 크면 b쪽
	// 선분 밖에 사영된 점은 무시
	if (t < 0.0f || t > 1.0f)
		return cv::Point();

	// 선분 위에 사영된 점 위치 계산
	cv::Point2f projection = a + t * (b - a);
	//std::cout << "Projection Point: (" << projection.x << ", " << projection.y << ")" << std::endl;

	// 사영된 점과 원래 점 p 사이의 거리가 10 픽셀 이상이면 무시
	if (std::sqrt(std::powf(p.x - projection.x, 2) + std::powf(p.y - projection.y, 2)) > 10)
		return cv::Point();

	// 선분 위의 사영된 점 반환
	return projection;
}

void onMouse(int event, int x, int y, int flags, void* userdata)
{
	static std::vector<cv::Point> points; // Static vector to store points across function calls
	cv::Scalar pointColor(0, 255, 0); // Green color for drawing points
	cv::Scalar lineColor(0, 0, 255); // Red color for drawing lines
	static cv::Mat* img = (cv::Mat*)userdata; // Cast the user data to a pointer to cv::Mat

	// 이미지 복사본 생성
	static cv::Mat originalimg = cv::imread("palvin1.png");

	// 마스크 생성
	static cv::Mat mask1 = cv::Mat::zeros((*img).size(), CV_8UC1);
	static cv::Mat mask2 = cv::Mat::zeros((*img).size(), CV_8UC1);
	static cv::Mat mask = cv::Mat::zeros((*img).size(), CV_8UC1);

	cv::Point newPoint(x, y);

	static bool clickedOnExistingPoint = false; // Flag to track if the click was on an existing point
	static int clickedExistingPointIndex = -1; // Index of the existing point that was clicked
	static bool clickHold = false;

	bool inserted = false;

	switch (event)
	{
	case cv::EVENT_LBUTTONDOWN:
		// 기존 점과 동일한 위치에 클릭된 경우 (범위는 8x8 픽셀)
		if (!points.empty())
			for (int i = 0; i < points.size(); i++)
				if (newPoint.x - 4 < points[i].x && newPoint.x + 4 > points[i].x &&
					newPoint.y - 4 < points[i].y && newPoint.y + 4 > points[i].y)
				{
					// 이미 존재하는 점을 클릭하면 우클릭으로 제거할 수 있도록 인덱스 저장
					clickedExistingPointIndex = i;
					clickedOnExistingPoint = true; // Set the flag to indicate that an existing point was clicked

					// 이동하려는 목적일 수 있으므로, 클릭이 유지되는 동안 점을 이동할 수 있도록 clickHold 플래그 설정
					clickHold = true;

					std::cout << "Clicked on existing point: " << i << " (" << points[i].x << ", "
						<< points[i].y << ")" << std::endl;

					return;
				}


		// 마우스 왼쪽 버튼이 클릭되었을 때, 새로운 점을 추가하거나 선분 사이에 점을 삽입

		// 선분 사이에 점 추가
		// 최소 2개의 점이 있어야 선분이 형성되므로, 2개 이상의 점이 있을 때만 선분 사이에 점을 추가
		if (points.size() >= 2)
		{
			for (int i = 0; i < points.size() - 1; i++)
			{
				cv::Point2f a = points[i];
				cv::Point2f b = points[i + 1];
				cv::Point projection = nearPointOnSegment(newPoint, a, b);
				if (projection == cv::Point()) // 사영된 점이 선분 밖이거나 너무 멀리 떨어져 있으면
					continue;
				else
				{
					points.insert(points.begin() + 1 + i, projection); // 새로운 점을 선분 사이에 삽입
					std::cout << "Point inserted: (" << projection.x << ", " << projection.y << ")" << std::endl;

					originalimg.copyTo(*img); // 이미지 갱신
					inserted = true;

					break;
				}
			}
		}

		// 선분 사이에 점이 추가되지 않았다면, 새로운 점을 추가
		if (!inserted && !points.empty() && newPoint != points.back())
		{
			points.push_back(newPoint);
			std::cout << "Point added: (" << x << ", " << y << ")" << std::endl;

			// 이전에 클릭된 점에 대한 정보를 리셋, 삭제시 전에 클릭된 점이 삭제되지 않도록 한다
			// 이 부분이 없으면, 이미 존재하던 점을 클릭한 후 다른 곳을 클릭해 새로운 점을 생성하고 우클릭으로 삭제하면,
			// 새로운 점이 삭제되지 않고 이전에 클릭된 점이 삭제되는 문제가 발생한다
			clickedExistingPointIndex = -1;
			clickedOnExistingPoint = false;
		}
		// 첫 번째 점 추가
		else if (points.empty())
		{
			points.push_back(newPoint);
			std::cout << "First point added: (" << x << ", " << y << ")" << std::endl;
		}

		break;

	case cv::EVENT_LBUTTONUP:
		// 클릭이 끝났으므로, 클릭이 유지되는 동안 점을 이동할 수 있도록 설정한 clickHold 플래그를 false로 리셋
		clickHold = false;

		break;

	case cv::EVENT_MOUSEMOVE:
		// 클릭이 유지되는 동안 점을 이동할 수 있도록 설정한 clickHold 플래그가 true인 경우
		if (clickHold && clickedOnExistingPoint && clickedExistingPointIndex != -1)
		{
			points[clickedExistingPointIndex] = newPoint; // Move the existing point to the new location
			//std::cout << "Moving point: " << clickedExistingPointIndex << " (" << x << ", " << y << ")" << std::endl;

			originalimg.copyTo(*img, mask); // 마스크를 적용하여 원본 이미지에서 점과 선이 있는 부분만 갱신
			// *img = cv::imread("palvin1.png");
			// 이런 식으로 매번 이미지를 다시 로드하는게 오래 걸려서 점 이동시 버벅이는거 같아 마스크를 적용해
			// 원본 이미지에서 복사해 봤지만 별 효과는 없다.
			// cv::imshow()가 이런 작업에 최적화되어 있지 않은 것 같기도 하다. 점 이동시 버벅이는 문제는 해결되지 않았다.
		}
		break;

	case cv::EVENT_RBUTTONDOWN:
		if (!points.empty())
		{
			// 점이 삽입된 상태에서 우클릭이 발생하면 삽입된 점을 제거, 또는 기존 점을 클릭한 상태에서 우클릭이 발생하면
			// 클릭된 점을 제거
			if (clickedOnExistingPoint)
			{
				std::cout << "Clicked point removed: " << clickedExistingPointIndex << " (" <<
					points[clickedExistingPointIndex].x << ", " << points[clickedExistingPointIndex].y << ")" <<std::endl;
				points.erase(points.begin() + clickedExistingPointIndex); // Remove the last added point
				clickedExistingPointIndex = -1; // Reset the last added point index
				clickedOnExistingPoint = false; // Reset the added flag
			}
			// 아니면 마지막으로 추가된 점을 제거
			else
			{
				points.pop_back();
				std::cout << "Last point removed" << std::endl;
			}

			originalimg.copyTo(*img); // 이미지 갱신
		}
		break;
	}

	// Draw points
	for (const cv::Point& point : points)
		cv::circle(*(cv::Mat*)userdata, point, 4, pointColor, -1); // userdata로도 접근 가능

	// Draw lines between points
	if (points.size() >= 2)
		for (int i = 0; i < points.size() - 1; i++)
			cv::line(*img, points[i], points[i + 1], lineColor, 2);

	// 빨간색 점과 초록색 선분이 있는 부분을 흰색으로, 나머지 부분을 검은색으로 하는 마스크 생성
	cv::inRange(*img, cv::Scalar(0, 0, 255), cv::Scalar(0, 0, 255), mask1);
	cv::inRange(*img, cv::Scalar(0, 255, 0), cv::Scalar(0, 255, 0), mask2);
	mask = mask1 + mask2;

	//cv::imshow("mask", mask);
	//cv::imshow("originalimg", originalimg);
	cv::imshow("image", *(cv::Mat*)userdata);
}

int main()
{
	cv::Mat image = cv::imread("palvin1.png");
	cv::namedWindow("image");
	cv::setMouseCallback("image", onMouse, (void*)&image);

	cv::imshow("image", image);

	cv::waitKey(0);
	cv::destroyAllWindows();

	return 0;
}

 

 

 

 

 

 

 

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

두 점으로 이루어지는 선분과 한 점 사이의 거리 및 수선의 발을 찾아보자.

 

#include <opencv2/opencv.hpp>
#include <iostream>

float distPointToSegment(cv::Point2f p, cv::Point2f a, cv::Point2f b) {
	float l2 = std::powf(a.x - b.x, 2) + std::powf(a.y - b.y, 2); // 선분의 길이 제곱
	if (l2 == 0.0f) // a와 b가 같은 경우
		return std::sqrt(std::powf(p.x - a.x, 2) + std::powf(p.y - a.y, 2)); // 점과 a 사이 거리

	// 선분 위로 점 p의 사영(Projection) 위치 t 계산(투영된 지점의 비율)
	// t = [(p-a) dot (b-a)] / |b-a|^2
	float t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / l2;

	// t가 0~1 사이면 선분 위(0이면 a, 1이면 b), 그렇지 않으면 선분 밖
	// 0보다 작으면 a쪽, 1보다 크면 b쪽
	if (t < 0.0f)
		return std::sqrt(std::powf(p.x - a.x, 2) + std::powf(p.y - a.y, 2));
	if (t > 1.0f)
		return std::sqrt(std::powf(p.x - b.x, 2) + std::powf(p.y - b.y, 2));

	// 선분 위에 사영된 점 위치 계산
	cv::Point2f projection = a + t * (b - a);
	std::cout << "Projection Point: (" << projection.x << ", " << projection.y << ")" << std::endl;

	// 점 p와 사영된 점 사이의 거리 계산
	return std::sqrt(std::powf(p.x - projection.x, 2) + std::powf(p.y - projection.y, 2));
}

int main() {
	cv::Point2f p(100, 0); // 점 p
	cv::Point2f a(0, 0);   // 선분 시작점 a
	cv::Point2f b(100, 100); // 선분 끝점 b

	float distance = distPointToSegment(p, a, b);
	std::cout << "Point to Segment Distance: " << distance << std::endl;

	return 0;
}

 

 

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

라인 위의 모든 점의 좌표를 찾아보자.

 

#include <iostream>
#include <opencv2/opencv.hpp>

int main() {
	cv::Mat img = cv::Mat::zeros(480, 640, CV_8UC3);
	cv::Point p1(100, 100);
	cv::Point p2(500, 300);

	// LineIterator 생성 (이미지, 시작점, 끝점, 8-connectivity)
	cv::LineIterator it(img, p1, p2, 8);
	// connectivity: Pixel connectivity of the iterator.
	// Valid values are 4 (iterator can move up, down, left and right)
	// and 8 (iterator can also move diagonally).
	//
	// The number of pixels along the line is stored in LineIterator::count.
	// The method LineIterator::pos returns the current position in the image

	std::vector<cv::Point> linePoints;
	for (int i = 0; i < it.count; i++, ++it) {
		// 현재 위치의 좌표를 벡터에 저장
		linePoints.push_back(it.pos());

		// 선 그리기
		img.at<cv::Vec3b>(it.pos()) = cv::Vec3b(0, 255, 0); // 초록색
	}

	// cv::norm 함수는 벡터의 크기를 계산하는 함수로, 두 점 사이의 거리를 계산할 때 사용할 수 있다.
	//double distance = cv::norm(p2 - p1);
	// 또는 cv::sqrt 함수를 사용하여 직접 계산할 수도 있다.
	cv::Point2f diff = p2 - p1;
	double distance = cv::sqrt(diff.x * diff.x + diff.y * diff.y);
	std::cout << "p1과 p2 사이의 거리: " << distance << std::endl;

	std::cout << "라인 위의 포인트 개수: " << linePoints.size() << std::endl;
	std::cout << linePoints << std::endl;

	cv::imshow("Line", img);
	cv::waitKey(0);

	return 0;
}

 

 

 

※ 참고

LineIterator Class

 

반응형
Posted by J-sean
:

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