@@ -154,87 +154,115 @@ private void loadBirdSpeciesNames(Context context) throws IOException {
154154 public List <BirdDetection > detectBirds (Bitmap bitmap ) {
155155 List <BirdDetection > results = new ArrayList <>();
156156 frameCounter ++;
157-
157+
158+ Tensor yoloInput = null ;
159+ EValue [] yoloOutputs = null ;
160+
158161 try {
159162 // Cleanup old detection history every 30 frames
160163 if (frameCounter % 30 == 0 ) {
161164 cleanupOldDetections ();
162165 }
163-
164- Tensor yoloInput = preprocessForYolo (bitmap );
166+
167+ yoloInput = preprocessForYolo (bitmap );
165168 if (yoloInput == null ) {
166169 return results ;
167170 }
168-
169- EValue [] yoloOutputs = yoloModule .forward (EValue .from (yoloInput ));
171+
172+ yoloOutputs = yoloModule .forward (EValue .from (yoloInput ));
170173 if (yoloOutputs == null || yoloOutputs .length == 0 ) {
171174 return results ;
172175 }
173-
176+
174177 List <Detection > detections = parseYoloV8OutputOptimized (yoloOutputs , bitmap .getWidth (), bitmap .getHeight ());
175-
178+
176179 if (DEBUG_OUTPUT ) {
177180 Log .d (TAG , "Raw detections before NMS: " + detections .size ());
178181 }
179-
182+
180183 // Apply enhanced NMS
181- List <Detection > filteredDetections = applyEnhancedNMS (detections );
182-
184+ List <Detection > nmsDetections = applyEnhancedNMS (detections );
185+
183186 if (DEBUG_OUTPUT ) {
184- Log .d (TAG , "Detections after enhanced NMS: " + filteredDetections .size ());
187+ Log .d (TAG , "Detections after NMS: " + nmsDetections .size ());
185188 }
186-
187- // Apply temporal stability tracking
188- List <Detection > stableDetections = applyTemporalStabilityTracking (filteredDetections );
189-
190- // Limit to MAX_DETECTIONS with quality ranking
191- if (stableDetections .size () > MAX_DETECTIONS ) {
192- // Sort by confidence and take top detections
193- Collections .sort (stableDetections , (a , b ) -> Float .compare (b .confidence , a .confidence ));
194- stableDetections = stableDetections .subList (0 , MAX_DETECTIONS );
195- }
196-
197- // Process stable detections only
198- for (Detection detection : stableDetections ) {
189+
190+ // Classify each detection
191+ for (Detection detection : nmsDetections ) {
199192 try {
200- if (validateBirdDetectionEnhanced (bitmap , detection .boundingBox )) {
201- String [] speciesResult = classifyBird (bitmap , detection .boundingBox );
202- float classifierConfidence = Float .parseFloat (speciesResult [1 ]);
203-
204- // Stricter classifier requirement for false positive reduction
205- if (classifierConfidence > 0.5f ) {
206- // Weighted average favoring detection confidence
207- float finalConfidence = (detection .confidence * 0.8f + classifierConfidence * 0.2f );
208-
209- // Check if this detection is stable over time
210- DetectionHistory history = detectionHistory .get (detection .locationKey );
211- boolean isStable = history != null && history .isStable ();
212-
213- results .add (new BirdDetection (
214- detection .boundingBox ,
215- speciesResult [0 ],
216- finalConfidence ,
217- isStable
218- ));
219-
220- if (DEBUG_OUTPUT ) {
221- Log .d (TAG , "FINAL BIRD: " + speciesResult [0 ] + " conf=" + finalConfidence + " stable=" + isStable );
222- }
223- }
193+ Bitmap croppedBird = cropBitmap (bitmap , detection .boundingBox );
194+ if (croppedBird == null ) continue ;
195+
196+ String species = classifyBird (croppedBird );
197+
198+ // Recycle the cropped bitmap immediately after classification
199+ if (!croppedBird .isRecycled ()) {
200+ croppedBird .recycle ();
201+ }
202+
203+ // Update detection history
204+ DetectionHistory history = detectionHistory .get (detection .locationKey );
205+ if (history == null ) {
206+ history = new DetectionHistory ();
207+ detectionHistory .put (detection .locationKey , history );
208+ }
209+ history .addConfidence (detection .confidence );
210+
211+ // Apply temporal bonus for stable detections
212+ float finalConfidence = detection .confidence ;
213+ if (history .isStable ()) {
214+ finalConfidence = Math .min (1.0f , detection .confidence + TEMPORAL_BONUS );
215+ }
216+
217+ BirdDetection birdDetection = new BirdDetection (
218+ detection .boundingBox ,
219+ species ,
220+ finalConfidence ,
221+ history .isStable ()
222+ );
223+ results .add (birdDetection );
224+
225+ if (DEBUG_OUTPUT ) {
226+ Log .d (TAG , String .format ("Bird detected: %s (%.2f) at [%.0f,%.0f,%.0f,%.0f] stable=%b" ,
227+ species , finalConfidence ,
228+ detection .boundingBox .left , detection .boundingBox .top ,
229+ detection .boundingBox .right , detection .boundingBox .bottom ,
230+ history .isStable ()));
224231 }
225232 } catch (Exception e ) {
226- Log .e (TAG , "Failed to classify detection" , e );
233+ Log .e (TAG , "Error classifying detection" , e );
227234 }
228235 }
229-
236+
237+ if (DEBUG_OUTPUT ) {
238+ Log .d (TAG , "Final bird detections: " + results .size ());
239+ }
240+
230241 } catch (Exception e ) {
231- Log .e (TAG , "Bird detection failed" , e );
232- }
233-
234- if (DEBUG_OUTPUT ) {
235- Log .d (TAG , "TOTAL FINAL RESULTS: " + results .size ());
242+ Log .e (TAG , "Error in detectBirds" , e );
243+ } finally {
244+ // CRITICAL: Release tensors to prevent memory leak
245+ if (yoloInput != null ) {
246+ try {
247+ yoloInput .close ();
248+ } catch (Exception e ) {
249+ Log .e (TAG , "Error closing yoloInput" , e );
250+ }
251+ }
252+
253+ if (yoloOutputs != null ) {
254+ for (EValue output : yoloOutputs ) {
255+ if (output != null ) {
256+ try {
257+ output .close ();
258+ } catch (Exception e ) {
259+ Log .e (TAG , "Error closing output" , e );
260+ }
261+ }
262+ }
263+ }
236264 }
237-
265+
238266 return results ;
239267 }
240268
@@ -315,6 +343,39 @@ private void cleanupOldDetections() {
315343 }
316344 }
317345
346+ /**
347+ * Release all resources and destroy models.
348+ * Call this when the pipeline is no longer needed.
349+ */
350+ public void close () {
351+ Log .d (TAG , "Closing BirdDetectionPipeline and releasing resources" );
352+
353+ try {
354+ if (yoloModule != null ) {
355+ yoloModule .destroy ();
356+ yoloModule = null ;
357+ }
358+ } catch (Exception e ) {
359+ Log .e (TAG , "Error destroying yoloModule" , e );
360+ }
361+
362+ try {
363+ if (classifierModule != null ) {
364+ classifierModule .destroy ();
365+ classifierModule = null ;
366+ }
367+ } catch (Exception e ) {
368+ Log .e (TAG , "Error destroying classifierModule" , e );
369+ }
370+
371+ // Clear detection history
372+ if (detectionHistory != null ) {
373+ detectionHistory .clear ();
374+ }
375+
376+ Log .d (TAG , "BirdDetectionPipeline closed successfully" );
377+ }
378+
318379 private Tensor preprocessForYolo (Bitmap bitmap ) {
319380 try {
320381 if (bitmap == null || bitmap .isRecycled ()) {
@@ -595,4 +656,4 @@ private Bitmap cropBitmap(Bitmap bitmap, RectF box) {
595656 public void cleanup () {
596657 detectionHistory .clear ();
597658 }
598- }
659+ }
0 commit comments