《2025 DigiKey AI应用创意挑战赛》基于FRDM-IMX93与EtherCAT工业AIoT智能控制系统
本帖最后由 eefocus_3863048 于 2026-2-3 10:41 编辑# 《2025 DigiKey AI应用创意挑战赛》基于FRDM-IMX93与EtherCAT工业AIoT智能控制系统
## 一,项目名称
基于FRDM-IMX93与EtherCAT工业AIoT智能控制系统
## 二,项目概述
本项目设计了一套面向工业AIoT场景的智能工业边缘控制系统。该系统采用云-边-端协同架构,利用FRDM-IMX93作为EtherCAT主站负责视频监控,AI推理和危险行为报警,HPM6E作为EtherCAT从站负责实时数据采集和控制执行。系统面向工业AIoT场景,实现实时状态监测和AI安全规范实时监控的一体化解决方案。
功能特性
1. EtherCAT 工业通信
* 主站/从站通信支持
* 实时工业控制
* 报警信号输出
* 支持内核模块动态加载
2. ZLMediaKit 媒体服务器
* **RTSP/RTMP/HTTP/WebRTC** 多种协议支持
* **HLS/DASH** 自适应码流
* **GB28181** 国标协议对接
* **Web 管理界面** (Element UI)
* **RESTful API** + Swagger 文档
* **FFmpeg** 硬件加速编解码
* **x264** H.264 编码支持
3. 跌倒检测系统
* **YOLOv5 TensorFlow Lite** 边缘推理
* **NPU 加速**
* **实时视频流** RTMP 推送
* **EtherCAT 报警联动**
* **云端告警** HTTP 上报
* **本地存储** 事件图片保存
* **多模型支持** (INT8/VELA 优化)
## 三,系统框架
**整体系统架构图**!(https://www.eefocus.com/forum/data/attachment/forum/202602/02/165124w9b8cdfktvx090gv.png)
**线程与队列架构图**
!(https://www.eefocus.com/forum/data/attachment/forum/202602/02/021522oam6hoed1s1w7w7k.png)
**数据流与处理管道**
!(https://www.eefocus.com/forum/data/attachment/forum/202602/02/021540ix7ihikh97twg97a.png)
**ZLMediaKit 推流架构**
!(https://www.eefocus.com/forum/data/attachment/forum/202602/02/021654yww9qd28228ztnny.png)
**系统时序图**
!(https://www.eefocus.com/forum/data/attachment/forum/202602/02/022124kltp1t26140466zr.png)
## 四,硬件部分
**硬件部分主要有一下部分组成:**
* **EtherCat从站-HPM6E**!(https://www.eefocus.com/forum/data/attachment/forum/202602/02/021739i9fbcsr24yljvahs.png)
* **EtherCat主站-FRDM-IMX93**!(https://www.eefocus.com/forum/data/attachment/forum/202602/02/021751k83fhzn1z89ff3f3.png)
**硬件连接图:**
!(https://www.eefocus.com/forum/data/attachment/forum/202602/02/022330sdgk949264drrrk1.png)
## 五,软件部分
#### FRDM-IMX93核心代码
```
# camera_simple_imx93_with_cloud_and_streaming.py
import argparse
import time
import os
import numpy as np
import cv2
import signal
import sys
import subprocess
import threading
import queue
import atexit
import json
import urllib.request
import urllib.error
import tflite_runtime.interpreter as tflite
from datetime import datetime
def load_labels(filename):
"""Load label file, ignore empty lines"""
labels = []
with open(filename, 'r') as f:
for line in f:
line = line.strip()
if line:# Only add non-empty lines
labels.append(line)
return labels
class SimpleCameraInference:
"""Simple camera inference for IMX93 with cloud server integration"""
def __init__(self, model_file, label_file, conf_thres=0.25,
delegate_path=None, camera_device="/dev/video0",
rtmp_url=None, ethercat_enable=False,
cloud_server_url=None, device_id="imx93_camera_001",
location="Industrial Area A"):
self.conf_thres = conf_thres
self.camera_device = camera_device
self.rtmp_url = rtmp_url
self.ethercat_enable = ethercat_enable
self.cloud_server_url = cloud_server_url
self.device_id = device_id
self.location = location
self.running = True
# Alarm status tracking
self.alarm_status = 0# 0: No alarm, 1: Alarm
self.last_alarm_status = 0
self.alarm_count = 0
self.alarm_threshold = 3# Need 3 consecutive detections
# Cloud alert tracking
self.last_cloud_alert_time = 0
self.cloud_alert_cooldown = 30# Minimum 30 seconds between cloud alerts
self.cloud_alert_count = 0
# Load labels
self.labels = load_labels(label_file)
print(f"Loaded {len(self.labels)} labels: {self.labels}")
# Load model
print(f"Loading model: {model_file}")
interpreter_options = {}
if delegate_path and os.path.exists(delegate_path):
print(f"Using Ethos-U delegate: {delegate_path}")
try:
delegate =
interpreter_options['experimental_delegates'] = delegate
except Exception as e:
print(f"Delegate error: {e}")
self.interpreter = tflite.Interpreter(
model_path=model_file,
**interpreter_options
)
self.interpreter.allocate_tensors()
# Get model info
self.input_details = self.interpreter.get_input_details()
self.output_details = self.output_details = self.interpreter.get_output_details()
self.input_shape = self.input_details['shape']
self.input_height = self.input_shape
self.input_width = self.input_shape
print(f"Model input: {self.input_width}x{self.input_height}")
# Cloud server status
if self.cloud_server_url:
print(f"Cloud server enabled: {self.cloud_server_url}")
self.test_cloud_connection()
# FFmpeg streaming
self.ffmpeg_process = None
if self.rtmp_url:
self.init_ffmpeg_stream()
# EtherCAT communication
if self.ethercat_enable:
self.init_ethercat()
# Initialize camera with direct V4L2
self.init_camera()
# Statistics
self.frame_count = 0
self.total_inference_time = 0
self.last_stats_time = time.time()
# Create save directory
self.save_dir = "saved_frames"
os.makedirs(self.save_dir, exist_ok=True)
# Heartbeat thread for cloud server
if self.cloud_server_url:
self.heartbeat_interval = 60# Send heartbeat every 60 seconds
self.heartbeat_thread = threading.Thread(target=self.heartbeat_worker, daemon=True)
self.heartbeat_thread.start()
print(f"Heartbeat thread started (interval: {self.heartbeat_interval}s)")
# Streaming queue
self.stream_queue = queue.Queue(maxsize=10)
self.stream_thread = None
if self.rtmp_url:
self.start_stream_thread()
# Register cleanup
atexit.register(self.cleanup)
def test_cloud_connection(self):
"""Test connection to cloud server"""
try:
url = f"{self.cloud_server_url}/api/health"
req = urllib.request.Request(url, method='GET')
response = urllib.request.urlopen(req, timeout=5)
data = json.loads(response.read().decode('utf-8'))
print(f"✓ Cloud server connection successful: {data.get('status')}")
return True
except Exception as e:
print(f"✗ Cloud server connection failed: {e}")
return False
def send_to_cloud(self, alert_data):
"""Send alert data to cloud server"""
if not self.cloud_server_url:
return False
current_time = time.time()
if current_time - self.last_cloud_alert_time < self.cloud_alert_cooldown:
print(f"Cloud alert cooldown: {int(self.cloud_alert_cooldown - (current_time - self.last_cloud_alert_time))}s remaining")
return False
try:
url = f"{self.cloud_server_url}/api/alerts"
# Ensure required fields
alert_data['device_id'] = self.device_id
alert_data['location'] = self.location
alert_data['timestamp'] = datetime.now().isoformat()
# Convert to JSON
json_data = json.dumps(alert_data).encode('utf-8')
# Create request
req = urllib.request.Request(url, data=json_data, method='POST')
req.add_header('Content-Type', 'application/json')
# Send request
response = urllib.request.urlopen(req, timeout=10)
result = json.loads(response.read().decode('utf-8'))
self.last_cloud_alert_time = current_time
self.cloud_alert_count += 1
print(f"✓ Alert sent to cloud: {result.get('message')}")
return True
except urllib.error.URLError as e:
print(f"✗ Network error sending to cloud: {e}")
except Exception as e:
print(f"✗ Error sending to cloud: {e}")
return False
def heartbeat_worker(self):
"""Send periodic heartbeat to cloud server"""
while self.running:
try:
time.sleep(self.heartbeat_interval)
if self.cloud_server_url:
heartbeat_data = {
'device_id': self.device_id,
'event_type': 'heartbeat',
'timestamp': datetime.now().isoformat(),
'location': self.location,
'message': 'Device heartbeat',
'status': 'active',
'fps': 0,# Will be updated
'alarm_status': self.alarm_status,
'cloud_alerts_sent': self.cloud_alert_count
}
# Get FPS if available
if hasattr(self, 'last_fps'):
heartbeat_data['fps'] = self.last_fps
self.send_to_cloud(heartbeat_data)
except Exception as e:
print(f"Heartbeat error: {e}")
time.sleep(30)# Wait longer on error
def init_ffmpeg_stream(self):
"""Initialize FFmpeg streaming process"""
print(f"\nInitializing FFmpeg stream to: {self.rtmp_url}")
# Stream settings
width = 640
height = 480
fps = 15
# FFmpeg command
ffmpeg_cmd = [
'ffmpeg',
'-y',# Overwrite output files
'-f', 'rawvideo',
'-vcodec', 'rawvideo',
'-pix_fmt', 'bgr24',
'-s', f'{width}x{height}',
'-r', str(fps),
'-i', '-',# Read from stdin
'-c:v', 'libx264',
'-preset', 'ultrafast',
'-tune', 'zerolatency',
'-pix_fmt', 'yuv420p',
'-f', 'flv',
self.rtmp_url
]
try:
self.ffmpeg_process = subprocess.Popen(
ffmpeg_cmd,
stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
print("FFmpeg stream initialized successfully")
except Exception as e:
print(f"Failed to initialize FFmpeg stream: {e}")
self.ffmpeg_process = None
def init_ethercat(self):
"""Initialize EtherCAT communication"""
print("\nInitializing EtherCAT communication...")
# Check ethercat command
try:
result = subprocess.run(['which', 'ethercat'],
capture_output=True, text=True)
if result.returncode != 0:
print("Warning: ethercat command not found")
self.ethercat_enable = False
return
# Test EtherCAT
test_cmd = ['ethercat', 'slaves']
result = subprocess.run(test_cmd, capture_output=True, text=True)
if result.returncode == 0:
print("EtherCAT initialized successfully")
print(f"Slaves: {result.stdout}")
# Reset alarm state
self.set_ethercat_alarm(0)
else:
print(f"EtherCAT test failed: {result.stderr}")
self.ethercat_enable = False
except Exception as e:
print(f"EtherCAT initialization error: {e}")
self.ethercat_enable = False
def set_ethercat_alarm(self, alarm_value):
"""Set EtherCAT alarm value (0=no alarm, 1=alarm)"""
if not self.ethercat_enable:
return False
try:
if alarm_value == 0:
cmd = ['ethercat', 'download', '-p', '0', '--type', 'uint16', '0x7010', '0', '0x00']
else:
cmd = ['ethercat', 'download', '-p', '0', '--type', 'uint16', '0x7010', '0', '0x01']
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
status = "ALARM" if alarm_value == 1 else "NORMAL"
print(f"EtherCAT output set: {status}")
return True
else:
print(f"EtherCAT command failed: {result.stderr}")
return False
except Exception as e:
print(f"EtherCAT communication error: {e}")
return False
def start_stream_thread(self):
"""Start streaming thread"""
if not self.ffmpeg_process:
return
def stream_worker():
while self.running:
try:
# Get frame from queue with timeout
frame = self.stream_queue.get(timeout=1)
if frame is not None and self.ffmpeg_process:
try:
# Write to FFmpeg
self.ffmpeg_process.stdin.write(frame.tobytes())
except Exception as e:
print(f"Stream write error: {e}")
break
except queue.Empty:
continue
except Exception as e:
print(f"Stream thread error: {e}")
break
self.stream_thread = threading.Thread(target=stream_worker, daemon=True)
self.stream_thread.start()
print("Streaming thread started")
def push_to_stream(self, frame):
"""Push frame to streaming queue"""
if self.ffmpeg_process and self.stream_queue.qsize() < 5:
try:
# Resize for streaming
stream_frame = cv2.resize(frame, (640, 480))
self.stream_queue.put_nowait(stream_frame)
except queue.Full:
pass# Skip frame if queue is full
except Exception as e:
print(f"Push to stream error: {e}")
def init_camera(self):
"""Initialize camera using direct V4L2 device path"""
print(f"\nInitializing camera: {self.camera_device}")
# Try different approaches
try:
# Method 1: Direct device path with V4L2
print("Trying direct V4L2 device path...")
self.cap = cv2.VideoCapture(self.camera_device, cv2.CAP_V4L2)
if not self.cap.isOpened():
# Method 2: Try as index if path is numeric
print("Trying as camera index...")
try:
idx = int(self.camera_device.split('/dev/video')[-1])
self.cap = cv2.VideoCapture(idx, cv2.CAP_V4L2)
except:
pass
if not self.cap.isOpened():
# Method 3: Try default
print("Trying default camera...")
self.cap = cv2.VideoCapture(0, cv2.CAP_V4L2)
if self.cap.isOpened():
print("Camera opened successfully!")
# Set basic properties
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
self.cap.set(cv2.CAP_PROP_FPS, 15)
# Get actual properties
self.camera_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
self.camera_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = self.cap.get(cv2.CAP_PROP_FPS)
print(f"Camera resolution: {self.camera_width}x{self.camera_height}")
print(f"Camera FPS: {fps:.1f}")
# Calculate scaling
self.scale_x = self.camera_width / self.input_width
self.scale_y = self.camera_height / self.input_height
# Test read
ret, frame = self.cap.read()
if ret:
print(f"Test frame read: {frame.shape}")
else:
print("Warning: Could not read test frame")
return
except Exception as e:
print(f"Camera initialization error: {e}")
print("ERROR: Could not initialize camera")
print("Switching to test mode...")
self.cap = None
self.test_mode = True
def get_test_frame(self):
"""Generate test frame for demonstration"""
frame = np.zeros((480, 640, 3), dtype=np.uint8)
# Add timestamp
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
cv2.putText(frame, f"TEST MODE | {timestamp}",
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
# Add some test objects
t = time.time()
# Person
px = 320 + int(100 * np.sin(t))
py = 240 + int(80 * np.cos(t * 0.7))
cv2.rectangle(frame, (px-50, py-100), (px+50, py+100), (0, 255, 0), 2)
cv2.putText(frame, "person: 0.85", (px-50, py-110),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
# Fall (occasionally)
if int(t) % 5 == 0:
fx = 160 + int(50 * np.cos(t * 0.5))
fy = 360 + int(30 * np.sin(t * 0.3))
cv2.rectangle(frame, (fx-40, fy-60), (fx+40, fy+60), (0, 0, 255), 2)
cv2.putText(frame, "fall: 0.72", (fx-40, fy-70),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
return frame
def get_frame(self):
"""Get frame from camera or generate test frame"""
if self.cap and self.cap.isOpened():
ret, frame = self.cap.read()
if ret and frame is not None and frame.size > 0:
return frame
# If camera fails, generate test frame
return self.get_test_frame()
def preprocess(self, frame):
"""Preprocess frame for model"""
# Resize to model input
frame_resized = cv2.resize(frame, (self.input_width, self.input_height))
# Convert BGR to RGB
frame_rgb = cv2.cvtColor(frame_resized, cv2.COLOR_BGR2RGB)
# For uint8 model
input_data = frame_rgb.astype(np.uint8)
input_data = np.expand_dims(input_data, axis=0)
return frame, input_data
def inference(self, input_data):
"""Run inference"""
self.interpreter.set_tensor(self.input_details['index'], input_data)
start_time = time.perf_counter()
self.interpreter.invoke()
inference_time = (time.perf_counter() - start_time) * 1000
output = self.interpreter.get_tensor(self.output_details['index'])
return output, inference_time
def postprocess(self, output, frame_shape):
"""Post-process inference results - FIXED VERSION"""
detections = []
# Get predictions
predictions = output# Shape:
# Dequantize if needed
if self.output_details.get('quantization', (0, 0)) != 0:
scale, zero_point = self.output_details['quantization']
# Dequantize predictions
predictions = (predictions.astype(np.float32) - zero_point) * scale
# Calculate combined confidence for all predictions
# Format:
obj_conf = predictions[:, 4]
class_prob = predictions[:, 6]
combined_conf = obj_conf * class_prob
# Filter by confidence threshold
valid_mask = combined_conf > self.conf_thres
valid_predictions = predictions
# Limit the number of predictions to process for performance
max_predictions = min(100, len(valid_predictions))
for i in range(max_predictions):
pred = valid_predictions
# Extract values by index (correct way)
x_center_norm = pred
y_center_norm = pred
width_norm = pred
height_norm = pred
obj_conf_val = pred
class_id = int(pred)# class_id is already an integer index
class_prob_val = pred
# Combined confidence
conf = obj_conf_val * class_prob_val
# Convert to pixel coordinates
if hasattr(self, 'scale_x'):
x_center_px = x_center_norm * self.input_width * self.scale_x
y_center_px = y_center_norm * self.input_height * self.scale_y
width_px = width_norm * self.input_width * self.scale_x
height_px = height_norm * self.input_height * self.scale_y
else:
# Test mode scaling
scale_factor = frame_shape / self.input_width
x_center_px = x_center_norm * self.input_width * scale_factor
y_center_px = y_center_norm * self.input_height * scale_factor
width_px = width_norm * self.input_width * scale_factor
height_px = height_norm * self.input_height * scale_factor
# Calculate bounding box
x1 = int(x_center_px - width_px / 2)
y1 = int(y_center_px - height_px / 2)
x2 = int(x_center_px + width_px / 2)
y2 = int(y_center_px + height_px / 2)
# Clamp to image boundaries
x1 = max(0, x1)
y1 = max(0, y1)
x2 = min(frame_shape, x2)
y2 = min(frame_shape, y2)
if x2 <= x1 or y2 <= y1:
continue
if class_id < len(self.labels):
class_name = self.labels
else:
class_name = f"class_{class_id}"
detections.append({
'bbox': ,
'confidence': float(conf),
'class_id': class_id,
'class_name': class_name
})
# Apply simple Non-Maximum Suppression to remove overlapping boxes
if len(detections) > 1:
detections = self.simple_nms(detections)
return detections
def simple_nms(self, detections, iou_threshold=0.45):
"""Simple Non-Maximum Suppression"""
if len(detections) == 0:
return []
# Sort by confidence (highest first)
sorted_detections = sorted(detections, key=lambda x: x['confidence'], reverse=True)
keep = []
while sorted_detections:
# Take the detection with highest confidence
current = sorted_detections.pop(0)
keep.append(current)
if not sorted_detections:
break
# Calculate IoU with remaining detections
remaining = []
current_bbox = np.array(current['bbox'])
current_area = (current_bbox - current_bbox) * (current_bbox - current_bbox)
for det in sorted_detections:
det_bbox = np.array(det['bbox'])
det_area = (det_bbox - det_bbox) * (det_bbox - det_bbox)
# Calculate intersection
x1 = max(current_bbox, det_bbox)
y1 = max(current_bbox, det_bbox)
x2 = min(current_bbox, det_bbox)
y2 = min(current_bbox, det_bbox)
if x2 <= x1 or y2 <= y1:
# No overlap
remaining.append(det)
continue
intersection = (x2 - x1) * (y2 - y1)
union = current_area + det_area - intersection
iou = intersection / union if union > 0 else 0
# Keep if IoU is below threshold
if iou < iou_threshold:
remaining.append(det)
sorted_detections = remaining
return keep
def draw_results(self, frame, detections, stats):
"""Draw results on frame"""
# Draw detections
for det in detections:
x1, y1, x2, y2 = det['bbox']
# Color coding: green for person, red for fall
if det['class_name'] == 'person':
color = (0, 255, 0)# Green
elif det['class_name'] == 'fall':
color = (0, 0, 255)# Red
else:
color = (255, 255, 0)# Yellow for other classes
# Draw bounding box
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
# Draw label with confidence
label = f"{det['class_name']}: {det['confidence']:.2f}"
# Calculate text size for background
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 0.5
thickness = 2
(text_width, text_height), baseline = cv2.getTextSize(label, font, font_scale, thickness)
# Draw text background
cv2.rectangle(frame, (x1, y1 - text_height - 5),
(x1 + text_width, y1), color, -1)# -1 means filled
# Draw text
cv2.putText(frame, label, (x1, y1 - 5),
font, font_scale, (255, 255, 255), thickness)
# Draw statistics
y_offset = 30
stats_text = [
f"FPS: {stats['FPS']}",
f"Inference: {stats['Inference']}",
f"Persons: {stats['Persons']}",
f"Falls: {stats['Falls']}",
f"Frame: {stats['Frame']}",
f"Alarm: {'ON' if self.alarm_status == 1 else 'OFF'}",
f"Cloud: {'ON' if self.cloud_server_url else 'OFF'}",
f"Stream: {'ON' if self.rtmp_url else 'OFF'}",
f"EtherCAT: {'ON' if self.ethercat_enable else 'OFF'}"
]
for i, text in enumerate(stats_text):
cv2.putText(frame, text, (10, y_offset + i * 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
# Draw alarm indicator
if self.alarm_status == 1:
cv2.putText(frame, "ALARM! FALL DETECTED!",
(frame.shape//2 - 150, 50),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 3)
# Draw red border
cv2.rectangle(frame, (5, 5), (frame.shape-5, frame.shape-5), (0, 0, 255), 10)
return frame
def handle_alarm(self, fall_detected, detections, frame):
"""Handle fall alarm logic with cloud notification"""
# Update alarm counter
if fall_detected:
self.alarm_count = min(self.alarm_count + 1, self.alarm_threshold)
else:
self.alarm_count = max(self.alarm_count - 1, 0)
# Determine new alarm status
new_alarm_status = 1 if self.alarm_count >= self.alarm_threshold else 0
# Check if alarm status changed
alarm_changed = new_alarm_status != self.alarm_status
self.alarm_status = new_alarm_status
# If alarm just triggered, send to cloud
if alarm_changed and self.alarm_status == 1:
print(f"⚠️ALARM TRIGGERED! Sending to cloud...")
# Calculate confidence for fall detection
fall_confidences = for d in detections if d['class_name'] == 'fall']
max_confidence = max(fall_confidences) if fall_confidences else 0.0
# Prepare cloud alert data
cloud_alert = {
'device_id': self.device_id,
'event_type': 'fall_detected',
'timestamp': datetime.now().isoformat(),
'fall_count': len( == 'fall']),
'person_count': len( == 'person']),
'confidence': max_confidence,
'location': self.location,
'message': f'Fall detected at {self.location}',
'alarm_status': self.alarm_status,
'camera_resolution': f'{frame.shape}x{frame.shape}'
}
# Send to cloud server
success = self.send_to_cloud(cloud_alert)
if success:
# Save frame with fall
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"fall_{timestamp}.jpg"
save_path = os.path.join(self.save_dir, filename)
cv2.imwrite(save_path, frame)
print(f"⚠️Fall frame saved: {filename}")
# Update EtherCAT output if changed
if alarm_changed and self.ethercat_enable:
success = self.set_ethercat_alarm(self.alarm_status)
if success:
print(f"EtherCAT alarm set to: {self.alarm_status}")
# If alarm just cleared, send recovery notification
if alarm_changed and self.alarm_status == 0 and self.last_alarm_status == 1:
print(f"✓ Alarm cleared")
if self.cloud_server_url:
recovery_alert = {
'device_id': self.device_id,
'event_type': 'alarm_cleared',
'timestamp': datetime.now().isoformat(),
'message': f'Fall alarm cleared at {self.location}',
'location': self.location
}
self.send_to_cloud(recovery_alert)
# Update last alarm status
self.last_alarm_status = self.alarm_status
def run(self):
"""Main loop"""
print("\n" + "="*60)
print("Industrial Fall Detection - IMX93 with Cloud Integration")
print(f"Device ID: {self.device_id}")
print(f"Location: {self.location}")
print(f"Cloud Server: {self.cloud_server_url if self.cloud_server_url else 'Disabled'}")
print(f"RTMP Stream: {self.rtmp_url if self.rtmp_url else 'Disabled'}")
print(f"EtherCAT Alarm: {'Enabled' if self.ethercat_enable else 'Disabled'}")
print("="*60)
print("Press 'q' or ESC to exit")
print("="*60)
stats_interval = 2.0
last_stats_print = time.time()
try:
while self.running:
# Get frame
frame = self.get_frame()
# Preprocess
original_frame, input_data = self.preprocess(frame)
# Inference
output, inference_time = self.inference(input_data)
# Post-process
detections = self.postprocess(output, frame.shape)
# Count falls and persons
fall_count = sum(1 for d in detections if d['class_name'] == 'fall')
fall_detected = fall_count > 0
# Handle alarm logic with cloud notification
self.handle_alarm(fall_detected, detections, original_frame)
# Update statistics
self.frame_count += 1
self.total_inference_time += inference_time
# Calculate FPS
if inference_time > 0:
fps = 1000 / inference_time
self.last_fps = fps
else:
fps = 0
self.last_fps = 0
# Count detections
person_count = sum(1 for d in detections if d['class_name'] == 'person')
# Create stats
stats = {
'FPS': f'{fps:.1f}',
'Inference': f'{inference_time:.1f}ms',
'Persons': person_count,
'Falls': fall_count,
'Frame': self.frame_count
}
# Draw results
frame_with_results = self.draw_results(original_frame.copy(), detections, stats)
# Push to streaming queue
if self.rtmp_url:
self.push_to_stream(frame_with_results)
# Display (if not in no-display mode)
if hasattr(self, 'show_display') and self.show_display:
cv2.imshow('Fall Detection', frame_with_results)
key = cv2.waitKey(1) & 0xFF
if key == ord('q') or key == 27:
break
# Print statistics periodically
current_time = time.time()
if current_time - last_stats_print >= stats_interval:
avg_inference = self.total_inference_time / self.frame_count if self.frame_count > 0 else 0
cloud_info = f"Cloud: {self.cloud_alert_count}" if self.cloud_server_url else ""
print(f"[{datetime.now().strftime('%H:%M:%S')}] "
f"FPS: {fps:.1f}, "
f"Inference: {inference_time:.1f}ms, "
f"Persons: {person_count}, Falls: {fall_count}, "
f"Alarm: {'ON' if self.alarm_status == 1 else 'OFF'} "
f"{cloud_info}")
# Reset counters for next interval
self.frame_count = 0
self.total_inference_time = 0
last_stats_print = current_time
# Small delay
time.sleep(0.01)
except KeyboardInterrupt:
print("\nExiting...")
finally:
# Cleanup
self.cleanup()
# Print final stats
print("\n" + "="*60)
print("Session ended")
if self.cloud_server_url:
print(f"Cloud alerts sent: {self.cloud_alert_count}")
print("="*60)
def cleanup(self):
"""Cleanup resources"""
print("\nCleaning up resources...")
# Stop camera
if self.cap:
self.cap.release()
# Stop FFmpeg stream
if self.ffmpeg_process:
try:
self.ffmpeg_process.stdin.close()
self.ffmpeg_process.wait(timeout=2)
print("FFmpeg stream closed")
except:
self.ffmpeg_process.terminate()
# Reset EtherCAT alarm
if self.ethercat_enable and self.alarm_status == 1:
print("Resetting EtherCAT alarm to OFF")
self.set_ethercat_alarm(0)
# Send final heartbeat
if self.cloud_server_url:
final_heartbeat = {
'device_id': self.device_id,
'event_type': 'shutdown',
'timestamp': datetime.now().isoformat(),
'message': f'Device {self.device_id} shutting down',
'location': self.location,
'cloud_alerts_sent': self.cloud_alert_count
}
try:
self.send_to_cloud(final_heartbeat)
except:
pass
# Close OpenCV windows
cv2.destroyAllWindows()
print("Cleanup completed")
def main():
parser = argparse.ArgumentParser(description='Industrial Fall Detection with Cloud Integration')
parser.add_argument('-m', '--model', required=True, help='TFLite model file')
parser.add_argument('-l', '--labels', default='labels.txt', help='Label file')
parser.add_argument('-d', '--delegate', default='/usr/lib/libethosu_delegate.so',
help='Ethos-U delegate path')
parser.add_argument('-c', '--camera', default='/dev/video0',
help='Camera device (e.g., /dev/video0)')
parser.add_argument('--rtmp', help='RTMP stream URL (e.g., rtmp://server/live/stream)')
parser.add_argument('--cloud', help='Cloud server URL (e.g., http://115.190.220.148:8000)')
parser.add_argument('--device-id', default='imx93_camera_001',
help='Device identifier for cloud server')
parser.add_argument('--location', default='Industrial Area A',
help='Location description for alerts')
parser.add_argument('--ethercat', action='store_true',
help='Enable EtherCAT alarm output')
parser.add_argument('--conf', type=float, default=0.25,
help='Confidence threshold')
parser.add_argument('--no-display', action='store_true',
help='Do not display window')
args = parser.parse_args()
# Check files
if not os.path.exists(args.model):
print(f"ERROR: Model file not found: {args.model}")
sys.exit(1)
if not os.path.exists(args.labels):
print("Creating default labels file...")
with open(args.labels, 'w') as f:
f.write("person\n")
f.write("fall\n")
# Check delegate
if args.delegate and not os.path.exists(args.delegate):
print(f"WARNING: Delegate not found: {args.delegate}")
args.delegate = None
# Check FFmpeg
if args.rtmp:
try:
subprocess.run(['which', 'ffmpeg'], capture_output=True, check=True)
except:
print("WARNING: ffmpeg not found, streaming disabled")
args.rtmp = None
try:
detector = SimpleCameraInference(
model_file=args.model,
label_file=args.labels,
conf_thres=args.conf,
delegate_path=args.delegate,
camera_device=args.camera,
rtmp_url=args.rtmp,
ethercat_enable=args.ethercat,
cloud_server_url=args.cloud,
device_id=args.device_id,
location=args.location
)
# Add display flag
detector.show_display = not args.no_display
detector.run()
except Exception as e:
print(f"ERROR: {e}")
import traceback
traceback.print_exc()
if __name__ == '__main__':
main()
```
运行
```
python3 camera_simple_imx93_with_cloud_and_streaming.py \
--model vela_models/best_int8_quantized_vela.tflite \
--labels labels.txt \
--cloud http://115.190.220.148:8000 \
--device-id "imx93_camera_01" \
--location "Assembly Line Station 3" \
--rtmprtmp://127.0.0.1/live/test --ethercat
```
#### 云服务器端代码
服务器
```
#!/usr/bin/env python3
"""
Industrial Fall Detection Cloud Server - English Version
No Chinese characters to avoid encoding issues
"""
import json
import os
import time
import logging
from datetime import datetime
from http.server import HTTPServer, BaseHTTPRequestHandler
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class AlertServer(BaseHTTPRequestHandler):
"""Simple HTTP server for fall detection alerts"""
def do_GET(self):
"""Handle GET requests"""
if self.path == '/':
# Return HTML dashboard
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
html = self.generate_dashboard()
self.wfile.write(html.encode('utf-8'))
elif self.path == '/api/health':
# Health check endpoint
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {
'status': 'ok',
'time': datetime.now().isoformat(),
'service': 'fall-detection-server'
}
self.wfile.write(json.dumps(response).encode('utf-8'))
elif self.path == '/api/alerts':
# Get alert list
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
alerts = self.get_alerts()
self.wfile.write(json.dumps(alerts, ensure_ascii=False).encode('utf-8'))
else:
self.send_response(404)
self.end_headers()
def do_POST(self):
"""Handle POST requests"""
if self.path == '/api/alerts':
try:
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
# Parse JSON data
alert_data = json.loads(post_data.decode('utf-8'))
# Save alert
self.save_alert(alert_data)
# Log to console
logger.info(f"Alert received: {alert_data.get('device_id', 'unknown')}")
# Return success
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {
'status': 'success',
'message': 'Alert received',
'timestamp': datetime.now().isoformat()
}
self.wfile.write(json.dumps(response).encode('utf-8'))
except Exception as e:
logger.error(f"Error processing alert: {e}")
self.send_response(500)
self.end_headers()
else:
self.send_response(404)
self.end_headers()
def save_alert(self, alert_data):
"""Save alert to file"""
try:
# Create data directory
os.makedirs('data', exist_ok=True)
# Generate filename
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
device_id = alert_data.get('device_id', 'unknown').replace('/', '_')
filename = f"data/alert_{device_id}_{timestamp}.json"
# Add server timestamp
alert_data['server_time'] = datetime.now().isoformat()
alert_data['server_timestamp'] = int(time.time())
# Save to file
with open(filename, 'w', encoding='utf-8') as f:
json.dump(alert_data, f, indent=2, ensure_ascii=False)
# Also append to log file
log_file = 'data/alerts.log'
with open(log_file, 'a', encoding='utf-8') as f:
f.write(f"{alert_data['server_time']} | {alert_data.get('device_id')} | {alert_data.get('event_type')}\n")
return True
except Exception as e:
logger.error(f"Error saving alert: {e}")
return False
def get_alerts(self):
"""Get recent alerts"""
alerts = []
try:
if os.path.exists('data'):
for filename in os.listdir('data'):
if filename.startswith('alert_') and filename.endswith('.json'):
filepath = os.path.join('data', filename)
try:
with open(filepath, 'r', encoding='utf-8') as f:
alert = json.load(f)
alerts.append(alert)
except:
continue
# Sort by timestamp (newest first)
alerts.sort(key=lambda x: x.get('server_timestamp', 0), reverse=True)
return alerts[:50]# Return last 50 alerts
except Exception as e:
logger.error(f"Error getting alerts: {e}")
return []
def generate_dashboard(self):
"""Generate HTML dashboard"""
alerts = self.get_alerts()
html = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fall Detection Monitor</title>
<style>
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{ font-family: Arial, sans-serif; background: #f5f5f5; color: #333; }}
.container {{ max-width: 1200px; margin: 0 auto; padding: 20px; }}
.header {{ background: #2c3e50; color: white; padding: 2rem; border-radius: 10px; margin-bottom: 2rem; }}
.stats {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }}
.stat-card {{ background: white; padding: 1.5rem; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); text-align: center; }}
.stat-card h3 {{ font-size: 2rem; color: #3498db; margin-bottom: 0.5rem; }}
.alerts {{ background: white; border-radius: 10px; padding: 1.5rem; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
.alert-item {{ border-bottom: 1px solid #eee; padding: 1rem 0; }}
.alert-item:last-child {{ border-bottom: none; }}
.alert-time {{ color: #666; font-size: 0.9rem; }}
.alert-device {{ color: #333; font-weight: bold; }}
.alert-type.fall {{ color: #e74c3c; }}
.alert-type.heartbeat {{ color: #2ecc71; }}
.alert-details {{ margin-top: 0.5rem; color: #555; }}
.last-update {{ text-align: center; margin-top: 1rem; color: #999; }}
</style>
<script>
// Auto refresh every 10 seconds
setTimeout(function() {{ location.reload(); }}, 10000);
// Send test alert
function testAlert() {{
fetch('/api/alerts', {{
method: 'POST',
headers: {{'Content-Type': 'application/json'}},
body: JSON.stringify({{
device_id: 'test_device_001',
timestamp: new Date().toISOString(),
event_type: 'fall_detected',
fall_count: 1,
person_count: 1,
confidence: 0.85,
location: 'Test Area',
message: 'Test alert for demonstration'
}})
}})
.then(response => response.json())
.then(data => {{
alert('Test alert sent: ' + data.message);
setTimeout(() => location.reload(), 500);
}});
}}
</script>
</head>
<body>
<div class="container">
<div class="header">
<h1>Fall Detection Monitoring System</h1>
<p>Real-time monitoring of fall detection devices</p>
</div>
<div class="stats">
<div class="stat-card">
<h3>{len(alerts)}</h3>
<p>Total Alerts</p>
</div>
<div class="stat-card">
<h3>{sum(1 for a in alerts if a.get('event_type') == 'fall_detected')}</h3>
<p>Fall Events</p>
</div>
<div class="stat-card">
<h3>{len(set(a.get('device_id', '') for a in alerts))}</h3>
<p>Active Devices</p>
</div>
<div class="stat-card">
<h3>Online</h3>
<p>System Status</p>
</div>
</div>
<div style="text-align: center; margin-bottom: 20px;">
<button onclick="testAlert()" style="padding: 10px 20px; background: #3498db; color: white; border: none; border-radius: 5px; cursor: pointer; margin-right: 10px;">
Send Test Alert
</button>
<button onclick="location.reload()" style="padding: 10px 20px; background: #27ae60; color: white; border: none; border-radius: 5px; cursor: pointer;">
Refresh Data
</button>
</div>
<div class="alerts">
<h2 style="margin-bottom: 1rem;">Recent Alert Records</h2>
{"".join() if alerts else '<p style="color: #999; text-align: center;">No alerts yet</p>'}
</div>
<div class="last-update">
<p>Last update: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | System running normally</p>
</div>
</div>
</body>
</html>"""
return html
def generate_alert_item(self, alert):
"""Generate HTML for a single alert item"""
event_type = alert.get('event_type', 'unknown')
timestamp = alert.get('timestamp', alert.get('server_time', 'Unknown time'))
# Format time
if 'T' in timestamp:
time_display = timestamp.replace('T', ' ').split('.')
else:
time_display = timestamp
# Alert styling
if event_type == 'fall_detected':
alert_class = "fall"
alert_icon = "⚠️"
alert_color = "#e74c3c"
else:
alert_class = "heartbeat"
alert_icon = "💚"
alert_color = "#2ecc71"
return f"""
<div class="alert-item" style="border-left: 4px solid {alert_color}; padding-left: 1rem;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<span class="alert-time">{time_display}</span>
<span class="alert-device">{alert.get('device_id', 'Unknown device')}</span>
</div>
<span class="alert-type {alert_class}">{alert_icon} {event_type}</span>
</div>
<div class="alert-details">
<strong>Location:</strong> {alert.get('location', 'Unknown')} |
<strong>Falls:</strong> {alert.get('fall_count', 0)} |
<strong>Persons:</strong> {alert.get('person_count', 0)} |
<strong>Confidence:</strong> {alert.get('confidence', 0):.2f}
{f'<br><strong>Message:</strong> {alert.get("message", "")}' if alert.get("message") else ""}
</div>
</div>
"""
def log_message(self, format, *args):
"""Override logging to use our logger"""
logger.info("%s - %s", self.address_string(), format % args)
def run_server(port=8000):
"""Run the HTTP server"""
server_address = ('0.0.0.0', port)
httpd = HTTPServer(server_address, AlertServer)
print("=" * 60)
print("Fall Detection Cloud Server - English Version")
print(f"Start time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Local: http://127.0.0.1:{port}")
print(f"Network: http://192.168.27.59:{port}")
print(f"Public: http://115.190.220.148:{port}")
print("=" * 60)
print("Endpoints:")
print(f"GET/ - Web dashboard")
print(f"GET/api/health- Health check")
print(f"GET/api/alerts- Get alerts")
print(f"POST /api/alerts- Send alert")
print("=" * 60)
print("Press Ctrl+C to stop")
print("=" * 60)
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nServer stopped")
except Exception as e:
logger.error(f"Server error: {e}")
if __name__ == '__main__':
run_server(8000)
```
启动脚本
```
#!/bin/bash
echo "Starting Fall Detection Server..."
echo "=================================="
# Go to script directory
cd "$(dirname "$0")"
# Check Python
if ! command -v python3 &> /dev/null; then
echo "Error: Python3 not found"
echo "Please install: sudo apt install python3"
exit 1
fi
# Kill any existing process on port 8000
echo "Checking port 8000..."
if command -v lsof &> /dev/null; then
lsof -ti:8000 | xargs kill -9 2>/dev/null || true
elif command -v fuser &> /dev/null; then
fuser -k 8000/tcp 2>/dev/null || true
fi
# Create data directory
mkdir -p data
# Get IP address
IP=$(hostname -I | awk '{print $1}' 2>/dev/null)
if [ -z "$IP" ]; then
IP="127.0.0.1"
fi
echo ""
echo "Server starting on:"
echo "Local: http://localhost:8000"
echo "Network:http://$IP:8000"
echo "Public: http://115.190.220.148:8000"
echo ""
echo "Press Ctrl+C to stop"
echo ""
# Start server
python3 cloud_server_en.py
```
监控脚本
```
#!/bin/bash
echo "Server Status Check"
echo "==================="
echo ""
# Check process
echo "1. Check Server Process:"
ps aux | grep -E "python.*cloud_server" | grep -v grep
if [ $? -eq 0 ]; then
echo "✓ Server is running"
else
echo "✗ Server is not running"
fi
echo ""
# Check port
echo "2. Check Port 8000:"
if command -v ss &> /dev/null; then
ss -tlnp | grep :8000
elif command -v netstat &> /dev/null; then
netstat -tlnp | grep :8000
fi
if [ $? -eq 0 ]; then
echo "✓ Port 8000 is listening"
else
echo "✗ Port 8000 is not listening"
fi
echo ""
# Test API
echo "3. Test API Connection:"
curl -s http://localhost:8000/api/health 2>/dev/null | grep -q status
if [ $? -eq 0 ]; then
echo "✓ API is working"
curl -s http://localhost:8000/api/health | python3 -m json.tool 2>/dev/null
else
echo "✗ API is not responding"
fi
echo ""
# Check data directory
echo "4. Check Data Directory:"
if [ -d "data" ]; then
echo "✓ Data directory exists"
echo "Alert files: $(ls -la data/alert_*.json 2>/dev/null | wc -l)"
echo "Log file: $(wc -l < data/alerts.log 2>/dev/null || echo 0) lines"
else
echo "✗ Data directory does not exist"
fi
echo ""
# URLs
echo "5. Access URLs:"
echo "Local: http://localhost:8000"
echo "Public: http://115.190.220.148:8000"
echo ""
```
部署步骤
```
#Set permissions
chmod +x start_simple.sh
chmod +x check_server.sh
chmod +x cloud_server_en.py
#Start the server
./start_simple.sh
```
## 六,视频演示
推理结果:
!(https://www.eefocus.com/forum/data/attachment/forum/202602/02/043737ob1ywuvuz68o6vgp.jpg)
ZLmediakit流媒体服务器:
!(https://www.eefocus.com/forum/data/attachment/forum/202602/02/043808gr2zhpefeoeuttyr.png)
云平台:
!(https://www.eefocus.com/forum/data/attachment/forum/202602/02/043937fqkmkrqpycsbqqq7.png)
实时推理演示:
!(https://www.eefocus.com/forum/data/attachment/forum/202602/03/104116qhbdvdhenhtdattb.png)
<iframe src="//player.bilibili.com/player.html?isOutside=true&aid=115997168110106&bvid=BV1H768B9Eao&cid=35760508163&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
源码:https://gitee.com/cool-ditch-repair/frdm-imx93-ether-cat.git
过程贴:(https://www.nxpic.org.cn/module/forum/thread-810725-1-1.html)
(https://www.nxpic.org.cn/module/forum/thread-810726-1-1.html)
(https://www.nxpic.org.cn/module/forum/thread-810727-1-1.html)
页:
[1]