Source code for catvision.motion

"""Motion detection methods for cat vision simulation."""

import numpy as np
import cv2
from typing import List, Optional, Tuple


[docs] class MotionMixin: """ Mixin class for motion detection methods. Implements cat-specific motion detection characteristics: - Enhanced motion sensitivity (1.8x human) - Horizontal motion bias (1.5x) - Optical flow-based motion detection """
[docs] def enhanced_motion_detection( self, frame_sequence: List[np.ndarray], flow_method: str = 'lucas_kanade' ) -> List[np.ndarray]: """ Enhanced motion detection with optical flow and directional sensitivity. Cats excel at detecting motion, particularly horizontal motion, which is critical for hunting prey. Args: frame_sequence: List of consecutive frames flow_method: Optical flow method ('lucas_kanade' or 'farneback') Returns: Motion-enhanced frame sequence """ if len(frame_sequence) < 2: return frame_sequence enhanced_frames = [] for i, frame in enumerate(frame_sequence): if i == 0: enhanced_frames.append(frame) continue prev_frame = frame_sequence[i-1] # Convert to grayscale for optical flow if len(frame.shape) == 3: current_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY) else: current_gray = frame prev_gray = prev_frame # Calculate optical flow if flow_method == 'lucas_kanade': flow = self._lucas_kanade_flow(prev_gray, current_gray) elif flow_method == 'farneback': flow = cv2.calcOpticalFlowFarneback( prev_gray, current_gray, None, 0.5, 3, 15, 3, 5, 1.2, 0 ) # Convert dense flow to sparse format for consistency if flow is not None: h, w = flow.shape[:2] y_coords, x_coords = np.mgrid[0:h:10, 0:w:10] flow_vectors = flow[y_coords, x_coords] flow = flow_vectors.reshape(-1, 2) else: flow = None # Calculate motion magnitude and direction if flow is not None: motion_magnitude, motion_direction = self._analyze_motion(flow) # Apply directional sensitivity (cats excel at horizontal motion) directional_weight = self._calculate_directional_sensitivity(motion_direction) # Enhance motion areas enhanced_frame = self._apply_motion_enhancement( frame, motion_magnitude, directional_weight ) enhanced_frames.append(enhanced_frame) else: enhanced_frames.append(frame) return enhanced_frames
def _lucas_kanade_flow( self, prev_gray: np.ndarray, current_gray: np.ndarray ) -> Optional[np.ndarray]: """ Calculate Lucas-Kanade optical flow. Args: prev_gray: Previous frame in grayscale current_gray: Current frame in grayscale Returns: Flow vectors or None if detection fails """ try: # Parameters for corner detection feature_params = dict( maxCorners=100, qualityLevel=0.3, minDistance=7, blockSize=7 ) # Parameters for Lucas-Kanade optical flow lk_params = dict( winSize=(15, 15), maxLevel=2, criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03) ) # Find corners in previous frame p0 = cv2.goodFeaturesToTrack(prev_gray, mask=None, **feature_params) if p0 is not None: # Calculate optical flow p1, status, error = cv2.calcOpticalFlowPyrLK( prev_gray, current_gray, p0, None, **lk_params ) # Select good points if p1 is not None: good_new = p1[status == 1] good_old = p0[status == 1] # Calculate flow vectors flow_vectors = good_new - good_old return flow_vectors except Exception: pass return None def _analyze_motion( self, flow_vectors: np.ndarray ) -> Tuple[np.ndarray, np.ndarray]: """ Analyze motion magnitude and direction from flow vectors. Args: flow_vectors: Optical flow vectors Returns: Tuple of (magnitude, direction) arrays """ if len(flow_vectors) == 0: return np.array([]), np.array([]) # Calculate magnitude and angle magnitude = np.sqrt(flow_vectors[:, 0]**2 + flow_vectors[:, 1]**2) direction = np.arctan2(flow_vectors[:, 1], flow_vectors[:, 0]) return magnitude, direction def _calculate_directional_sensitivity(self, directions: np.ndarray) -> float: """ Calculate directional sensitivity weight (cats excel at horizontal motion). Args: directions: Array of motion directions in radians Returns: Directional sensitivity weight """ if len(directions) == 0: return 1.0 # Convert angles to horizontal bias (0° and 180° are horizontal) horizontal_angles = np.abs(np.cos(directions)) avg_horizontal_bias = np.mean(horizontal_angles) # Apply horizontal motion bias return 1.0 + (self.horizontal_motion_bias - 1.0) * avg_horizontal_bias def _apply_motion_enhancement( self, frame: np.ndarray, motion_magnitude: np.ndarray, directional_weight: float ) -> np.ndarray: """ Apply motion enhancement to frame. Args: frame: Input frame motion_magnitude: Motion magnitude values directional_weight: Directional sensitivity weight Returns: Motion-enhanced frame """ if len(motion_magnitude) == 0: return frame enhanced = frame.astype(np.float32) # Create motion enhancement mask avg_motion = np.mean(motion_magnitude) if len(motion_magnitude) > 0 else 0 enhancement_factor = ( 1.0 + (self.motion_sensitivity - 1.0) * directional_weight * min(avg_motion / 10.0, 1.0) ) # Apply enhancement globally (simplified approach) enhanced *= enhancement_factor return np.clip(enhanced, 0, 255).astype(np.uint8)
[docs] def enhance_motion_detection( self, image: np.ndarray, previous_frame: Optional[np.ndarray] = None ) -> np.ndarray: """ Enhance motion detection capabilities (legacy method). Rod cell specialization for motion detection. Args: image: Current frame previous_frame: Previous frame for motion detection Returns: Motion-enhanced image """ if previous_frame is None: return image # Convert to grayscale for motion detection if len(image.shape) == 3: current_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) prev_gray = cv2.cvtColor(previous_frame, cv2.COLOR_BGR2GRAY) else: current_gray = image prev_gray = previous_frame # Calculate frame difference diff = cv2.absdiff(current_gray, prev_gray) # Enhance motion areas motion_mask = diff > 10 # Threshold for motion detection enhanced = image.copy().astype(np.float32) if len(image.shape) == 3: for channel in range(3): enhanced[:, :, channel][motion_mask] *= self.motion_sensitivity else: enhanced[motion_mask] *= self.motion_sensitivity return np.clip(enhanced, 0, 255).astype(np.uint8)