반응형
1. 강의 개요
이번 강의에서는 YOLO(You Only Look Once) 모델을 사용하여 객체 탐지 및 추적 애플리케이션을 제작합니다.
YOLO는 실시간 객체 탐지에 널리 사용되는 딥러닝 모델로,
이미지나 비디오 프레임에서 사람, 차량, 동물 등의 객체를 탐지할 수 있습니다.
이 강의에서는 YOLO 모델과 OpenCvSharp을 연동하여 실시간으로 객체를 탐지하고 화면에 표시하는 방법을 배웁니다.
2. 학습 목표
- YOLO 모델을 사용한 객체 탐지 구현
- 비디오 스트리밍 프레임에서 객체 탐지 및 경계 상자 표시
- OpenCvSharp과 YOLO 모델 파일 연동
- 실시간 성능 최적화
3. 기능 요구사항
필수 기능
1️⃣ YOLO 모델 로드:
- YOLO 모델 가중치 파일과 설정 파일 로드
2️⃣ 객체 탐지:
- 비디오 프레임에서 객체를 탐지하고 경계 상자(Bounding Box)를 표시
3️⃣ 실시간 스트리밍:
- 웹캠 또는 비디오 파일에서 실시간 탐지
4️⃣ UI 구성 및 동작:
- 탐지 결과를 PictureBox에 실시간으로 표시
4. 실습: YOLO 기반 객체 탐지 애플리케이션 제작
1️⃣ 사전 준비
- YOLO 모델 파일 다운로드:
- YOLOv4 또는 YOLOv5 모델 파일 다운로드
- 필요한 파일:
- yolov4.weights (모델 가중치)
- yolov4.cfg (모델 설정)
- coco.names (객체 이름 파일)
- YOLOv4 다운로드 링크
- NuGet 패키지 설치:
- Visual Studio에서 OpenCvSharp4 및 OpenCvSharp4.Windows 패키지를 설치합니다.
2️⃣ 폼 구성
- 폼(Form) 이름: Form1
- 컨트롤 배치:
컨트롤 타입 이름 위치 크기
Button | btnStartStream | 폼 상단 왼쪽 | (100 x 30) |
Button | btnStopStream | 폼 상단 중앙 | (100 x 30) |
PictureBox | pictureBox | 폼 하단 전체 | (500 x 400) |
📌 폼 디자인 예시:
--------------------------------------------------
| [Start Stream] [Stop Stream] |
--------------------------------------------------
| [PictureBox - 객체 탐지 결과] |
--------------------------------------------------
3️⃣ 코드 작성
(1) YOLO 모델 로드
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenCvSharp;
using OpenCvSharp.Dnn;
namespace WindowsFormsApp_YOLODetection
{
public partial class Form1 : Form
{
private Net _net; // YOLO 네트워크 객체
private VideoCapture _videoCapture;
private CancellationTokenSource _cancellationTokenSource;
private readonly string[] _classLabels; // 객체 이름
private readonly Scalar[] _colors; // 클래스별 경계 상자 색상
public Form1()
{
InitializeComponent();
// 클래스 이름 파일 읽기
_classLabels = File.ReadAllLines("coco.names");
// 랜덤 색상 생성
_colors = new Scalar[_classLabels.Length];
var random = new Random();
for (int i = 0; i < _colors.Length; i++)
{
_colors[i] = new Scalar(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255));
}
// YOLO 모델 로드
_net = CvDnn.ReadNetFromDarknet("yolov4.cfg", "yolov4.weights");
_net.SetPreferableBackend(Backend.OPENCV);
_net.SetPreferableTarget(Target.CPU);
}
}
}
(2) 실시간 스트리밍 및 객체 탐지
// 실시간 스트리밍 시작
private async void btnStartStream_Click(object sender, EventArgs e)
{
_videoCapture = new VideoCapture(0); // 0번 웹캠
if (!_videoCapture.IsOpened())
{
MessageBox.Show("웹캠을 열 수 없습니다.", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
_cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = _cancellationTokenSource.Token;
await Task.Run(() =>
{
using (Mat frame = new Mat())
{
while (!_cancellationTokenSource.Token.IsCancellationRequested)
{
_videoCapture.Read(frame);
if (frame.Empty()) continue;
var detectedFrame = DetectObjects(frame);
DisplayFrame(detectedFrame);
Cv2.WaitKey(1);
}
}
});
}
// YOLO를 사용한 객체 탐지
private Mat DetectObjects(Mat frame)
{
Mat blob = CvDnn.BlobFromImage(frame, 1 / 255.0, new OpenCvSharp.Size(416, 416), new Scalar(0, 0, 0), true, false);
_net.SetInput(blob);
var outputLayers = GetOutputLayers();
var outputs = new List<Mat>();
_net.Forward(outputs, outputLayers);
PostProcess(frame, outputs);
return frame;
}
// YOLO의 출력 계층 이름 가져오기
private IEnumerable<string> GetOutputLayers()
{
var names = _net.GetUnconnectedOutLayersNames();
return names;
}
// 탐지된 객체를 프레임에 표시
private void PostProcess(Mat frame, List<Mat> outputs)
{
const float confidenceThreshold = 0.5f; // 신뢰도 임계값
const float nmsThreshold = 0.4f; // NMS(Non-Maximum Suppression) 임계값
var boxes = new List<Rect>();
var classIds = new List<int>();
var confidences = new List<float>();
for (int i = 0; i < outputs.Count; i++)
{
var data = outputs[i].ToArray<float>();
for (int j = 0; j < data.Length / 85; j++) // COCO 데이터셋 기준 85 요소
{
float confidence = data[j * 85 + 4];
if (confidence > confidenceThreshold)
{
var scores = data.Skip(j * 85 + 5).Take(80).ToArray();
int classId = Array.IndexOf(scores, scores.Max());
float score = scores[classId];
if (score > confidenceThreshold)
{
int centerX = (int)(data[j * 85 + 0] * frame.Width);
int centerY = (int)(data[j * 85 + 1] * frame.Height);
int width = (int)(data[j * 85 + 2] * frame.Width);
int height = (int)(data[j * 85 + 3] * frame.Height);
int left = centerX - width / 2;
int top = centerY - height / 2;
boxes.Add(new Rect(left, top, width, height));
classIds.Add(classId);
confidences.Add(score);
}
}
}
}
// NMS로 겹치는 상자 제거
int[] indices = CvDnn.NMSBoxes(boxes, confidences, confidenceThreshold, nmsThreshold);
foreach (int index in indices)
{
var box = boxes[index];
Cv2.Rectangle(frame, box, _colors[classIds[index]], 2);
Cv2.PutText(frame, $"{_classLabels[classIds[index]]} {confidences[index]:0.00}",
new Point(box.X, box.Y - 5), HersheyFonts.HersheySimplex, 0.5, _colors[classIds[index]]);
}
}
// 프레임 표시
private void DisplayFrame(Mat frame)
{
if (frame.Empty()) return;
var bitmap = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(frame);
pictureBox.Invoke(new Action(() =>
{
pictureBox.Image?.Dispose();
pictureBox.Image = bitmap;
}));
}
// 스트리밍 종료
private void btnStopStream_Click(object sender, EventArgs e)
{
_cancellationTokenSource?.Cancel();
_videoCapture?.Release();
pictureBox.Image = null;
}
4️⃣ 실행 결과
1️⃣ 실시간 웹캠 스트리밍
- "Start Stream" 클릭 → 웹캠에서 실시간 비디오 스트리밍 시작
2️⃣ 객체 탐지 및 경계 상자 표시
- 탐지된 객체 주위에 경계 상자와 이름 표시
3️⃣ 스트리밍 종료
- "Stop Stream" 클릭 → 스트리밍 종료
5. 주요 개념 요약
- YOLO 모델: 실시간 객체 탐지를 위한 딥러닝 모델
- CvDnn: OpenCV의 DNN(Deep Neural Network) 모듈
- BlobFromImage: 입력 이미지를 YOLO에 맞는 포맷으로 변환
- NMSBoxes: Non-Maximum Suppression으로 겹치는 경계 상자 제거
📌 #CSharp #WindowsForms #YOLO #객체탐지 #OpenCvSharp #DNN
반응형
'📁 [4] 개발자 정보 & 코드 노트 > C#' 카테고리의 다른 글
C# Windows Forms 강의 83편: 스케줄 관리 애플리케이션 제작 - 캘린더 및 알림 기능 구현 (0) | 2025.04.28 |
---|---|
C# Windows Forms 강의 82편: 자연어 처리 기반 채팅봇 제작 - ChatGPT API 활용 (0) | 2025.04.27 |
C# Windows Forms 강의 80편: 비디오 처리 및 실시간 스트리밍 - OpenCvSharp 활용 (0) | 2025.04.24 |
C# Windows Forms 강의 80편: 비디오 처리 및 실시간 스트리밍 - OpenCvSharp 활용 (0) | 2025.04.23 |
C# Windows Forms 강의 79편: 이미지 처리 애플리케이션 제작 - OpenCvSharp 활용 (0) | 2025.04.22 |