|
5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 | 6 | <title>YOLOv8 Object Detection</title>
|
7 | 7 | <style>
|
8 |
| - /* 웹캠 영상과 탐지 결과를 화면에 맞게 표시 */ |
9 |
| - video { |
10 |
| - width: 100%; |
11 |
| - max-width: 640px; |
12 |
| - display: block; |
13 |
| - margin: auto; |
| 8 | + #webcam-container { |
| 9 | + display: flex; |
| 10 | + justify-content: center; |
| 11 | + align-items: center; |
| 12 | + margin-top: 20px; |
14 | 13 | }
|
15 |
| - canvas { |
16 |
| - position: absolute; |
17 |
| - top: 0; |
18 |
| - left: 0; |
19 |
| - z-index: 2; |
| 14 | + #webcam { |
| 15 | + border: 2px solid black; |
20 | 16 | }
|
21 | 17 | </style>
|
22 | 18 | </head>
|
23 | 19 | <body>
|
24 |
| - <h1>YOLOv8 Object Detection with WebCam</h1> |
25 |
| - |
26 |
| - <!-- 웹캠 비디오 출력 --> |
27 |
| - <video id="videoElement" autoplay playsinline></video> |
28 |
| - |
29 |
| - <!-- 추론을 위한 캔버스 (객체 탐지 결과 표시용) --> |
30 |
| - <canvas id="canvas"></canvas> |
31 |
| - |
32 |
| - <!-- 알림 소리 --> |
33 |
| - <audio id="alertSound" src="alert_sound.mp3" preload="auto"></audio> |
34 |
| - |
35 |
| - <script src="https://cdn.jsdelivr.net/npm/onnxjs/dist/onnx.min.js"></script> |
| 20 | + <h1>YOLOv8 Object Detection</h1> |
| 21 | + <div id="webcam-container"> |
| 22 | + <video id="webcam" width="640" height="480" autoplay></video> |
| 23 | + </div> |
| 24 | + <script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@latest/dist/onnxruntime-web.min.js"></script> |
36 | 25 | <script>
|
37 |
| - // 웹캠 비디오 스트림을 가져오는 함수 |
38 |
| - async function setupWebcam() { |
39 |
| - const video = document.getElementById('videoElement'); |
40 |
| - const constraints = { video: { facingMode: 'user' } }; |
41 |
| - |
42 |
| - // 웹캠 권한 요청 |
43 |
| - const stream = await navigator.mediaDevices.getUserMedia(constraints); |
44 |
| - video.srcObject = stream; |
45 |
| - video.play(); |
46 |
| - } |
47 |
| - |
48 |
| - // 모델 로드 및 추론을 위한 ONNX 세션 생성 |
49 |
| - const session = new onnx.InferenceSession(); |
| 26 | + // ONNX model path |
| 27 | + const modelPath = 'yolov8_model.onnx'; // Make sure this path is correct |
50 | 28 |
|
51 |
| - // 모델 파일 경로 |
52 |
| - const modelPath = 'yolov8_model.onnx'; // 변환한 YOLOv8 모델 파일 경로 |
| 29 | + // Global variables for sound and state |
| 30 | + let alertSound = new Audio('alert_sound.mp3'); // Replace with actual sound file path |
| 31 | + let isPlaying = false; |
53 | 32 |
|
54 |
| - async function loadModel() { |
55 |
| - await session.loadModel(modelPath); |
56 |
| - console.log("YOLOv8 모델 로드 완료"); |
57 |
| - } |
58 |
| - |
59 |
| - // 웹캠 비디오를 캔버스에 그리기 |
60 |
| - const canvas = document.getElementById('canvas'); |
| 33 | + // Initialize webcam |
| 34 | + const video = document.getElementById('webcam'); |
| 35 | + const canvas = document.createElement('canvas'); |
61 | 36 | const ctx = canvas.getContext('2d');
|
62 | 37 |
|
63 |
| - // 탐지된 객체의 클래스에 따라 박스를 그리는 함수 |
64 |
| - function drawBoundingBoxes(predictions) { |
65 |
| - ctx.clearRect(0, 0, canvas.width, canvas.height); |
66 |
| - |
67 |
| - predictions.forEach(prediction => { |
68 |
| - const [x, y, width, height] = prediction.box; |
69 |
| - ctx.strokeStyle = 'red'; |
70 |
| - ctx.lineWidth = 2; |
71 |
| - ctx.strokeRect(x, y, width, height); |
72 |
| - |
73 |
| - // 객체 레이블을 박스 위에 출력 |
74 |
| - ctx.font = '16px Arial'; |
75 |
| - ctx.fillStyle = 'red'; |
76 |
| - ctx.fillText(prediction.className, x, y > 10 ? y - 5 : 10); |
| 38 | + // Access webcam |
| 39 | + navigator.mediaDevices.getUserMedia({ video: true }) |
| 40 | + .then((stream) => { |
| 41 | + video.srcObject = stream; |
| 42 | + video.play(); |
| 43 | + }) |
| 44 | + .catch((error) => { |
| 45 | + console.error('Error accessing webcam:', error); |
77 | 46 | });
|
78 |
| - } |
79 | 47 |
|
80 |
| - // 객체 탐지 및 소리 재생을 위한 함수 |
81 |
| - async function detectObjects(frame) { |
82 |
| - // ONNX.js에서 처리할 수 있도록 텐서로 변환 |
83 |
| - const inputTensor = new onnx.Tensor(new Float32Array(frame.data), 'float32', [1, 3, frame.height, frame.width]); |
| 48 | + // Load ONNX model |
| 49 | + let session; |
84 | 50 |
|
85 |
| - // 모델 추론 |
86 |
| - const output = await session.run([inputTensor]); |
87 |
| - |
88 |
| - // 추론 결과에서 탐지된 객체들 |
89 |
| - const boxes = output.values().next().value.data; |
90 |
| - |
91 |
| - // 탐지된 객체를 화면에 그리기 |
92 |
| - const predictions = parsePredictions(boxes); |
93 |
| - drawBoundingBoxes(predictions); |
94 |
| - |
95 |
| - // '담배'가 탐지되었는지 확인하여 소리 재생 |
96 |
| - const cigaretteDetected = predictions.some(pred => pred.className === 'cigarette'); |
97 |
| - if (cigaretteDetected) { |
98 |
| - document.getElementById('alertSound').play(); |
| 51 | + async function loadModel() { |
| 52 | + try { |
| 53 | + session = await ort.InferenceSession.create(modelPath); |
| 54 | + console.log("Model loaded successfully"); |
| 55 | + startDetection(); |
| 56 | + } catch (err) { |
| 57 | + console.error("Error loading model:", err); |
99 | 58 | }
|
100 | 59 | }
|
101 | 60 |
|
102 |
| - // 추론 결과 파싱 (YOLOv8 모델의 출력 형식에 따라 다를 수 있음) |
103 |
| - function parsePredictions(boxes) { |
104 |
| - const predictions = []; |
105 |
| - for (let i = 0; i < boxes.length; i += 6) { |
106 |
| - const classId = boxes[i + 5]; // 클래스 ID (YOLOv8) |
107 |
| - const confidence = boxes[i + 4]; // 탐지 확신도 |
108 |
| - |
109 |
| - if (confidence > 0.5) { // 확신도가 50% 이상일 경우에만 |
110 |
| - const box = boxes.slice(i, i + 4); // [x, y, width, height] |
111 |
| - const className = getClassName(classId); |
112 |
| - predictions.push({ box, className }); |
113 |
| - } |
| 61 | + loadModel(); |
| 62 | + |
| 63 | + // Start object detection |
| 64 | + async function startDetection() { |
| 65 | + canvas.width = video.width; |
| 66 | + canvas.height = video.height; |
| 67 | + |
| 68 | + function detectObjects() { |
| 69 | + ctx.drawImage(video, 0, 0, canvas.width, canvas.height); |
| 70 | + let frame = canvas.toDataURL('image/jpeg'); |
| 71 | + |
| 72 | + // Convert image data to tensor |
| 73 | + let tensor = preprocessImage(frame); |
| 74 | + |
| 75 | + // Run inference |
| 76 | + session.run([tensor]).then((output) => { |
| 77 | + const boxes = output[0].data; // Adjust according to output format |
| 78 | + const confidences = output[1].data; // Adjust according to output format |
| 79 | + |
| 80 | + // Check if cigarette is detected |
| 81 | + let cigaretteDetected = false; |
| 82 | + |
| 83 | + // Loop through detections and draw boxes |
| 84 | + boxes.forEach((box, index) => { |
| 85 | + const confidence = confidences[index]; |
| 86 | + if (confidence > 0.5) { // Adjust the confidence threshold |
| 87 | + const [x, y, width, height] = box; |
| 88 | + if (/* check if the class is cigarette */) { |
| 89 | + cigaretteDetected = true; |
| 90 | + drawBoundingBox(x, y, width, height); |
| 91 | + } |
| 92 | + } |
| 93 | + }); |
| 94 | + |
| 95 | + // Play alert sound if cigarette is detected |
| 96 | + if (cigaretteDetected && !isPlaying) { |
| 97 | + alertSound.play(); |
| 98 | + isPlaying = true; |
| 99 | + } else if (!cigaretteDetected && isPlaying) { |
| 100 | + alertSound.pause(); |
| 101 | + isPlaying = false; |
| 102 | + } |
| 103 | + |
| 104 | + requestAnimationFrame(detectObjects); |
| 105 | + }).catch((error) => { |
| 106 | + console.error("Error during inference:", error); |
| 107 | + }); |
114 | 108 | }
|
115 |
| - return predictions; |
116 |
| - } |
117 | 109 |
|
118 |
| - // 클래스 ID에 해당하는 클래스 이름을 반환 |
119 |
| - function getClassName(classId) { |
120 |
| - const classNames = ['cigarette', 'other_class']; // 모델 학습 시 설정한 클래스 목록 |
121 |
| - return classNames[classId] || 'Unknown'; |
| 110 | + detectObjects(); |
122 | 111 | }
|
123 | 112 |
|
124 |
| - // 비디오 프레임 처리 및 탐지 실행 |
125 |
| - function processFrame() { |
126 |
| - const video = document.getElementById('videoElement'); |
127 |
| - canvas.width = video.videoWidth; |
128 |
| - canvas.height = video.videoHeight; |
129 |
| - |
130 |
| - const context = video.getContext('2d'); |
131 |
| - context.drawImage(video, 0, 0, canvas.width, canvas.height); |
132 |
| - |
133 |
| - // 실시간 객체 탐지 |
134 |
| - detectObjects(context.getImageData(0, 0, canvas.width, canvas.height)); |
135 |
| - |
136 |
| - // 비디오 프레임을 계속해서 처리하도록 설정 |
137 |
| - requestAnimationFrame(processFrame); |
| 113 | + // Preprocess webcam frame and convert to tensor |
| 114 | + function preprocessImage(frame) { |
| 115 | + // Convert frame to tensor here (resize, normalize, etc.) |
| 116 | + // This depends on the expected input format for your model |
| 117 | + // Example: You might need to resize the frame, normalize, etc. |
| 118 | + let tensor = new ort.Tensor('float32', new Float32Array(frame), [1, 3, 640, 640]); // Adjust the shape as per model requirement |
| 119 | + return tensor; |
138 | 120 | }
|
139 | 121 |
|
140 |
| - // 초기 설정 |
141 |
| - async function start() { |
142 |
| - await loadModel(); |
143 |
| - await setupWebcam(); |
144 |
| - processFrame(); |
| 122 | + // Draw bounding box on canvas |
| 123 | + function drawBoundingBox(x, y, width, height) { |
| 124 | + ctx.beginPath(); |
| 125 | + ctx.rect(x, y, width, height); |
| 126 | + ctx.lineWidth = 3; |
| 127 | + ctx.strokeStyle = 'red'; |
| 128 | + ctx.fillStyle = 'red'; |
| 129 | + ctx.stroke(); |
145 | 130 | }
|
146 |
| - |
147 |
| - start(); // 실행 |
148 | 131 | </script>
|
149 | 132 | </body>
|
150 | 133 | </html>
|
0 commit comments