RFID Gate with Human Direction Detection System

An RFID gate system with human direction detection capabilities. The system is built with Spring Boot as the main server, connected to an RFID reader via plain sockets, and utilizes FastAPI with WebSocket for YOLO-based detection. It can detect human movement directions using YOLO and record the corresponding item access directions.

Features

  • RFID Reader + Java (Plain Socket communication): Communicates with the RFID reader using plain socket communication to handle data efficiently with low-spec hardware.
  • Main Server (Java Spring Boot): Reads data from the RFID reader via plain sockets, saves data to Redis, logs to MongoDB, and handles actions from the frontend. Capable of communicating with other microservices via WebSocket.
  • Python Flask Server: Integrates YOLO and DeepSort tracker to detect the direction (left or right) of a person and returns the direction to the main server through a REST API.
  • React Frontend: Displays real-time camera images and table data. Allows modification of RFID antenna power and communicates with the main server using WebSocket.
  • Python Speaker Script: Plays sounds based on the count of RFID tags being scanned. Communicates with the main server using WebSocket.

Workflow

  1. User Interaction: A user with items that have RFID tags approaches the gate from either the right or left side.
  2. RFID Scanning: The RFID antenna continuously scans the RFID tags, recording them in Redis and adding entries to MongoDB for logging purposes.
  3. Data Recording: When an RFID tag is scanned, it is stored in Redis with a lifetime of 6 seconds, corresponding to the scan zone length.
  4. Direction Detection: If RFID tags are present in the Redis pool and human direction data is received from the Flask server, the system retrieves all relevant RFID data from Redis and saves it to MongoDB. These tags are then moved to a cooldown pool for 30 seconds to prevent duplicate scanning.
  5. Duplicate Prevention: The cooldown mechanism ensures that RFID tags are auto-filtered out during the cooldown period, preventing duplicate scans after a movement is detected.
  6. Frontend Display: Users can view the recorded results in real-time on the React frontend, including camera images and table data.
  7. Log Access: Users can access all log data and data transmission history, as the main server provides API calls to third-party services.
  8. Antenna Control: Users have the ability to adjust the RFID antenna power or toggle it on and off directly from the webpage.

Technology

  • RFID Reader: Hardware component for scanning RFID tags.
  • Java: For socket communication with the RFID reader.
  • Java Spring Boot: Backend framework for the main server.
  • Redis: In-memory data store for fast data access.
  • MongoDB: NoSQL database for logging and data storage.
  • Python Flask: Backend framework for YOLO detection.
  • YOLO (You Only Look Once): Real-time object detection system.
  • DeepSort Tracker: Object tracking algorithm.
  • React: Frontend library for building user interfaces.
  • WebSocket: Protocol for real-time communication between frontend and backend.
  • Python: For scripting the speaker functionality.

Development Insights

  • WebSocket Real-Time Video Streaming: Implementing real-time video streaming with WebSocket proved challenging due to lag and delays. Ultimately, streaming images via Python Flask and rendering them on the React frontend provided a more reliable solution.
  • Low-Spec RFID Reader Constraints: The RFID reader had limited RAM and CPU resources, necessitating the use of plain socket communication instead of WebSocket, which requires additional libraries and resources.
  • Multiple People Movement Detection: Even with DeepSort tracking, detecting multiple people passing through the gate simultaneously from opposite directions was difficult due to the lack of x, y, z-axis data from the RFID reader.
  • Camera Detection Stability: YOLO sometimes failed to detect humans correctly when they moved quickly, causing incorrect direction calculations. Implemented a buffer zone (0-10 and 90-100 on the x-axis) to improve detection accuracy and prevent false movement triggers.

tracks = tracker.update_tracks(detections, frame=frame)

current_ids = set()
for track in tracks:
    if not track.is_confirmed() or track.time_since_update > 1:
        continue

    track_id = track.track_id
    current_ids.add(track_id)
    bbox = track.to_tlbr()
    x1, y1, x2, y2 = map(int, bbox)  # Human Box Coordinate
    object_center = ((x1 + x2) // 2, (y1 + y2) // 2)

    cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
    cv2.putText(frame, f"ID: {track_id}", (x1, y1 - 10),
                cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)

    if track_id not in movement_states:  # Cannot just calculate the movement with 'perfect' in and exist axis
        if object_center[0] < frame_width // 3:
            movement_states[track_id] = {
                'start': 'left', 'end': None, 'detected': True, 'direction': None, 'enterTime': datetime.now(timezone.utc)}
        elif object_center[0] > frame_width * 2 // 3:
            movement_states[track_id] = {
                'start': 'right', 'end': None, 'detected': True, 'direction': None, 'enterTime': datetime.now(timezone.utc)}
        else:
            movement_states[track_id] = {
                'start': 'middle', 'end': None, 'detected': True, 'direction': None, 'enterTime': datetime.now(timezone.utc)}

    state = movement_states[track_id]
    if object_center[0] > frame_width * 2 // 3:
        state['end'] = 'right'
        state['direction'] = 'right'
    elif object_center[0] < frame_width // 3:
        state['end'] = 'left'
        state['direction'] = 'left'

    state['detected'] = True
  • Handling Incomplete Movements: Needed to manage cases where a person enters from one side but does not pass through the gate, ensuring that movement is not falsely triggered. Achieved this through DeepSort tracking and movement state management, ensuring that only movements from the designated start to end sides are recognized.
for track_id in list(movement_states.keys()):
    state = movement_states[track_id]
    if track_id not in current_ids:
        state['detected'] = False
        if has_disappeared(track_id, movement_states):
            enter_time = state['enterTime']
            exit_time = datetime.now(timezone.utc)

            # Check if starting from left and need to leave from right
            if state['start'] == 'left' and state['end'] == 'right':
                print(f"Person {track_id} moved from Left to Right")
                asyncio.run(send_movement_data(
                    uri_for_movement, "right", enter_time, exit_time))
            elif state['start'] == 'right' and state['end'] == 'left':
                print(f"Person {track_id} moved from Right to Left")
                asyncio.run(send_movement_data(
                    uri_for_movement, "left", enter_time, exit_time))
            del movement_states[track_id]