jarondon82 commited on
Commit
b22d5df
·
1 Parent(s): 3617555

Mejorar la comunicación entre backend y frontend para la detección facial

Browse files
Files changed (1) hide show
  1. streamlit_app.py +292 -107
streamlit_app.py CHANGED
@@ -131,66 +131,132 @@ def main():
131
  print(f"Detecting faces with confidence threshold: {conf_threshold}")
132
 
133
  # Obtener dimensiones de la imagen
 
 
 
 
134
  h, w = frame.shape[:2]
135
 
136
- # Crear un blob de la imagen (redimensionada a 300x300 y normalizada)
137
- # IMPORTANTE: Los valores de media (104.0, 177.0, 123.0) son específicos
138
- # para el modelo res10_300x300_ssd_iter_140000.caffemodel entrenado en Caffe
139
- blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 1.0,
140
- (300, 300), (104.0, 177.0, 123.0))
141
-
142
- # Pasar el blob a través de la red
143
- net.setInput(blob)
144
 
145
- # Realizar la detección (forward pass)
146
  try:
147
- detections = net.forward()
148
- print(f"Shape of detection output: {detections.shape}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  except Exception as e:
150
- print(f"Error al procesar la imagen con el modelo DNN: {str(e)}")
151
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
- # Variable para almacenar las cajas delimitadoras
154
- bboxes = []
155
-
156
- # Procesar cada detección
157
- detection_count = 0
158
- for i in range(detections.shape[2]):
159
- # Extraer la confianza (probabilidad) de la detección
160
- confidence = detections[0, 0, i, 2]
161
 
162
- # Filtrar detecciones débiles por confianza
163
- if confidence > conf_threshold:
164
- detection_count += 1
165
- # La red da las coordenadas de la caja normalizadas entre 0 y 1
166
- # Multiplicamos por ancho y alto para obtener coordenadas en píxeles
167
- box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
168
-
169
- # Convertir a enteros
170
- x1, y1, x2, y2 = box.astype("int")
171
-
172
- # Garantizar que las coordenadas estén dentro de los límites de la imagen
173
- x1, y1 = max(0, x1), max(0, y1)
174
- x2, y2 = min(w, x2), min(h, y2)
175
-
176
- # Imprimir información de depuración
177
- print(f"Detección #{detection_count}: confianza={confidence:.3f}, bbox=[{x1},{y1},{x2},{y2}]")
178
-
179
- # Saltar cajas inválidas (por ejemplo, con ancho o alto negativo)
180
- width, height = x2 - x1, y2 - y1
181
- if width <= 0 or height <= 0:
182
- print(f"Saltando caja inválida con dimensiones: {width}x{height}")
183
- continue
184
-
185
- # Añadir la caja y la confianza a la lista de resultados
186
- bboxes.append([x1, y1, x2, y2, confidence])
187
-
188
- # Dar feedback sobre el número de detecciones
189
- print(f"Total de detecciones con confianza > {conf_threshold}: {detection_count}")
190
- print(f"Total de cajas válidas: {len(bboxes)}")
191
-
192
- # Devolver None si no hay detecciones, o la lista de bboxes en caso contrario
193
- return bboxes if bboxes else None
194
 
195
  # Function for processing face detections
196
  def process_face_detections(frame, detections, conf_threshold=0.5, bbox_color=(0, 255, 0)):
@@ -2490,6 +2556,9 @@ def main():
2490
  displayCtx.fillText('Rectángulo de Prueba', 210, 90);
2491
  console.log('Rectángulo inicial de prueba dibujado');
2492
 
 
 
 
2493
  // Configuración dinámica del FPS (desde Streamlit)
2494
  const captureDelay = 1000 / %s;
2495
 
@@ -2528,6 +2597,9 @@ def main():
2528
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
2529
  displayCtx.drawImage(video, 0, 0, display.width, display.height);
2530
 
 
 
 
2531
  // Dibujar un rectángulo de prueba en cada captura
2532
  drawTestRect();
2533
 
@@ -2542,26 +2614,62 @@ def main():
2542
  }
2543
  }
2544
 
2545
- // Debug para todos los mensajes recibidos
2546
- window.addEventListener('message', function(event) {
2547
- console.log('Mensaje recibido:', event.data);
2548
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2549
 
2550
- // Recibir datos de detección de rostros y dibujarlos
2551
  window.addEventListener('message', function(event) {
2552
- try {
 
 
 
2553
  const message = event.data;
 
2554
 
2555
- // Verificar si es un mensaje con cajas de rostros
2556
- if (message && message.type === 'faceBoxes') {
2557
- console.log('Recibido datos de cajas:', message.boxes);
2558
- const boxes = message.boxes;
2559
 
2560
  // Limpiar el canvas y dibujar el frame actual
2561
  displayCtx.drawImage(video, 0, 0, display.width, display.height);
2562
 
2563
  // Dibujar cada caja
2564
- if (boxes && boxes.length > 0) {
2565
  console.log(`Dibujando ${boxes.length} cajas de rostros`);
2566
  boxes.forEach(box => {
2567
  if (box && box.length >= 5) {
@@ -2604,13 +2712,20 @@ def main():
2604
  displayCtx.font = 'bold 18px Arial';
2605
  displayCtx.fillText('No se detectan rostros', 20, 30);
2606
  console.log('No hay cajas para dibujar o formato inválido');
 
 
 
 
 
2607
  }
2608
 
2609
  // Dibuja un rectángulo de prueba siempre, para verificar que el canvas funciona
2610
  drawTestRect();
2611
  }
2612
- } catch (error) {
2613
- console.error('Error al procesar mensaje:', error);
 
 
2614
  }
2615
  });
2616
 
@@ -2697,60 +2812,130 @@ def main():
2697
  # Imprimir en el servidor para depuración
2698
  print(f"Cajas de rostros detectadas: {bbox_list}")
2699
 
2700
- # Crear script para enviar datos al componente JavaScript
2701
  face_boxes_js = f"""
2702
  <script>
2703
- console.log("Enviando cajas de rostros al componente: {bbox_list}");
2704
-
2705
- // Usar setTimeout para asegurarse de que el componente está inicializado
2706
- setTimeout(function() {{
2707
- window.parent.postMessage(
2708
- {{
2709
- type: 'faceBoxes',
2710
- boxes: {json.dumps(bbox_list)}
2711
- }},
2712
- '*'
2713
- );
2714
 
2715
- // También enviar un evento directo al iframe si existe
2716
- try {{
2717
- const frames = window.parent.document.getElementsByTagName('iframe');
2718
- for(let i = 0; i < frames.length; i++) {{
2719
- frames[i].contentWindow.postMessage(
2720
- {{
2721
- type: 'faceBoxes',
2722
- boxes: {json.dumps(bbox_list)}
2723
- }},
2724
- '*'
2725
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2726
  }}
2727
- console.log("Mensaje enviado a todos los iframes");
2728
- }} catch(e) {{
2729
- console.error("Error enviando mensaje directo a iframes:", e);
2730
  }}
2731
- }}, 200);
 
 
 
 
 
 
 
 
2732
  </script>
2733
  """
2734
 
2735
  # Inyectar el script en la página
2736
- st.components.v1.html(face_boxes_js, height=0, width=0)
2737
 
2738
- # Agregar un script para dibujar un rectángulo de prueba,
2739
- # independientemente de si hay detecciones o no
2740
- test_rect_js = """
2741
  <script>
2742
- console.log("Enviando señal de rectángulo de prueba");
2743
- setTimeout(function() {
2744
- window.parent.postMessage(
2745
- {
2746
- type: 'testRect'
2747
- },
2748
- '*'
2749
- );
2750
- }, 100);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2751
  </script>
2752
  """
2753
- st.components.v1.html(test_rect_js, height=0, width=0)
2754
  except Exception as e:
2755
  st.error(f"Error processing camera frame: {str(e)}")
2756
  st.info("Camera continues to run. Processing will be attempted on next frame.")
 
131
  print(f"Detecting faces with confidence threshold: {conf_threshold}")
132
 
133
  # Obtener dimensiones de la imagen
134
+ if frame is None or frame.size == 0:
135
+ print("Error: Empty frame received in detect_face_dnn")
136
+ return None
137
+
138
  h, w = frame.shape[:2]
139
 
140
+ # Verificar que el modelo esté cargado correctamente
141
+ if net is None:
142
+ print("Error: Face detection model not loaded")
143
+ return None
 
 
 
 
144
 
 
145
  try:
146
+ # Intentamos con el DNN
147
+ # Crear un blob de la imagen (redimensionada a 300x300 y normalizada)
148
+ # IMPORTANTE: Los valores de media (104.0, 177.0, 123.0) son específicos
149
+ # para el modelo res10_300x300_ssd_iter_140000.caffemodel entrenado en Caffe
150
+ blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 1.0,
151
+ (300, 300), (104.0, 177.0, 123.0))
152
+
153
+ # Pasar el blob a través de la red
154
+ net.setInput(blob)
155
+
156
+ # Realizar la detección (forward pass)
157
+ try:
158
+ detections = net.forward()
159
+ print(f"Shape of detection output: {detections.shape}")
160
+ except Exception as e:
161
+ print(f"Error al procesar la imagen con el modelo DNN: {str(e)}")
162
+ # Intentar con Haar Cascades como alternativa
163
+ return detect_face_haar(frame, conf_threshold)
164
+
165
+ # Variable para almacenar las cajas delimitadoras
166
+ bboxes = []
167
+
168
+ # Procesar cada detección
169
+ detection_count = 0
170
+ for i in range(detections.shape[2]):
171
+ # Extraer la confianza (probabilidad) de la detección
172
+ confidence = detections[0, 0, i, 2]
173
+
174
+ # Filtrar detecciones débiles por confianza
175
+ if confidence > conf_threshold:
176
+ detection_count += 1
177
+ # La red da las coordenadas de la caja normalizadas entre 0 y 1
178
+ # Multiplicamos por ancho y alto para obtener coordenadas en píxeles
179
+ box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
180
+
181
+ # Convertir a enteros
182
+ x1, y1, x2, y2 = box.astype("int")
183
+
184
+ # Garantizar que las coordenadas estén dentro de los límites de la imagen
185
+ x1, y1 = max(0, x1), max(0, y1)
186
+ x2, y2 = min(w, x2), min(h, y2)
187
+
188
+ # Imprimir información de depuración
189
+ print(f"Detección #{detection_count}: confianza={confidence:.3f}, bbox=[{x1},{y1},{x2},{y2}]")
190
+
191
+ # Saltar cajas inválidas (por ejemplo, con ancho o alto negativo)
192
+ width, height = x2 - x1, y2 - y1
193
+ if width <= 0 or height <= 0:
194
+ print(f"Saltando caja inválida con dimensiones: {width}x{height}")
195
+ continue
196
+
197
+ # Añadir la caja y la confianza a la lista de resultados
198
+ bboxes.append([x1, y1, x2, y2, confidence])
199
+
200
+ # Dar feedback sobre el número de detecciones
201
+ print(f"Total de detecciones con confianza > {conf_threshold}: {detection_count}")
202
+ print(f"Total de cajas válidas: {len(bboxes)}")
203
+
204
+ # Si no se encontraron rostros con DNN, intentar con Haar cascades como fallback
205
+ if not bboxes:
206
+ print("No se encontraron rostros con DNN, intentando con Haar cascades...")
207
+ return detect_face_haar(frame, conf_threshold)
208
+
209
+ # Devolver None si no hay detecciones, o la lista de bboxes en caso contrario
210
+ return bboxes if bboxes else None
211
+
212
  except Exception as e:
213
+ print(f"Error general en detect_face_dnn: {str(e)}")
214
+ # En caso de error, usar Haar cascades como fallback
215
+ return detect_face_haar(frame, conf_threshold)
216
+
217
+ # Función alternativa para detectar rostros usando Haar Cascades
218
+ def detect_face_haar(frame, conf_threshold=0.3):
219
+ """Detecta rostros usando Haar Cascades como método de respaldo"""
220
+ try:
221
+ # Carga el clasificador Haar Cascade para rostros (debería estar cargado globalmente,
222
+ # pero lo hacemos aquí para asegurar que esté disponible)
223
+ if 'haar_face_cascade' not in st.session_state:
224
+ cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
225
+ st.session_state.haar_face_cascade = cv2.CascadeClassifier(cascade_path)
226
+ print(f"Haar cascade loaded from {cascade_path}")
227
+
228
+ # Convertir a escala de grises
229
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
230
+
231
+ # Ecualizar el histograma para mejorar contraste
232
+ gray = cv2.equalizeHist(gray)
233
+
234
+ # Detectar rostros - la confianza se controla con minNeighbors
235
+ # Usamos el umbral de confianza como guía para minNeighbors
236
+ min_neighbors = max(3, int(conf_threshold * 10))
237
+ faces = st.session_state.haar_face_cascade.detectMultiScale(
238
+ gray,
239
+ scaleFactor=1.1,
240
+ minNeighbors=min_neighbors,
241
+ minSize=(30, 30),
242
+ flags=cv2.CASCADE_SCALE_IMAGE
243
+ )
244
 
245
+ print(f"Haar cascade found {len(faces)} faces")
 
 
 
 
 
 
 
246
 
247
+ # Convertir al formato [x1, y1, x2, y2, confidence]
248
+ # Note que Haar Cascades no proporciona un valor de confianza real
249
+ bboxes = []
250
+ for (x, y, w, h) in faces:
251
+ # Usamos un valor fijo de confianza de 0.8 para Haar detections
252
+ # Esto es arbitrario pero útil para el procesamiento posterior
253
+ bboxes.append([x, y, x+w, y+h, 0.8])
254
+
255
+ return bboxes if bboxes else None
256
+
257
+ except Exception as e:
258
+ print(f"Error en detect_face_haar: {str(e)}")
259
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
 
261
  # Function for processing face detections
262
  def process_face_detections(frame, detections, conf_threshold=0.5, bbox_color=(0, 255, 0)):
 
2556
  displayCtx.fillText('Rectángulo de Prueba', 210, 90);
2557
  console.log('Rectángulo inicial de prueba dibujado');
2558
 
2559
+ // Arreglo para almacenar las últimas cajas recibidas
2560
+ let lastBoxes = [];
2561
+
2562
  // Configuración dinámica del FPS (desde Streamlit)
2563
  const captureDelay = 1000 / %s;
2564
 
 
2597
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
2598
  displayCtx.drawImage(video, 0, 0, display.width, display.height);
2599
 
2600
+ // Dibujar cajas almacenadas (para mantener las cajas entre frames)
2601
+ drawStoredBoxes();
2602
+
2603
  // Dibujar un rectángulo de prueba en cada captura
2604
  drawTestRect();
2605
 
 
2614
  }
2615
  }
2616
 
2617
+ // Función para dibujar las cajas almacenadas
2618
+ function drawStoredBoxes() {
2619
+ if (lastBoxes && lastBoxes.length > 0) {
2620
+ console.log(`Redibujando ${lastBoxes.length} cajas almacenadas`);
2621
+ lastBoxes.forEach(box => {
2622
+ if (box && box.length >= 5) {
2623
+ const [x1, y1, x2, y2, confidence] = box;
2624
+
2625
+ // Dibujar rectángulo
2626
+ displayCtx.strokeStyle = '#00FF00'; // Verde brillante
2627
+ displayCtx.lineWidth = 6; // Línea más gruesa
2628
+ displayCtx.strokeRect(x1, y1, x2-x1, y2-y1);
2629
+
2630
+ // Añadir un relleno semitransparente para mayor visibilidad
2631
+ displayCtx.fillStyle = 'rgba(0, 255, 0, 0.2)';
2632
+ displayCtx.fillRect(x1, y1, x2-x1, y2-y1);
2633
+
2634
+ // Añadir un fondo para el texto
2635
+ displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
2636
+ displayCtx.fillRect(x1, y1-25, 140, 25);
2637
+
2638
+ // Dibujar etiqueta con fuente más grande
2639
+ displayCtx.fillStyle = '#FFFF00'; // Amarillo brillante
2640
+ displayCtx.font = 'bold 18px Arial';
2641
+ displayCtx.fillText(`Rostro: ${confidence.toFixed(2)}`, x1+5, y1-5);
2642
+ }
2643
+ });
2644
+
2645
+ // Añadir contador de rostros
2646
+ displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
2647
+ displayCtx.fillRect(10, 10, 200, 30);
2648
+ displayCtx.fillStyle = '#FFFFFF';
2649
+ displayCtx.font = 'bold 18px Arial';
2650
+ displayCtx.fillText(`Rostros: ${lastBoxes.length}`, 20, 30);
2651
+ }
2652
+ }
2653
 
2654
+ // Debug para todos los mensajes recibidos
2655
  window.addEventListener('message', function(event) {
2656
+ console.log('>>> Mensaje recibido:', event.data);
2657
+
2658
+ // Verificar si es un mensaje con cajas de rostros
2659
+ if (event.data && event.data.type === 'faceBoxes') {
2660
  const message = event.data;
2661
+ console.log('🔴 Recibido mensaje de cajas faciales:', message);
2662
 
2663
+ const boxes = message.boxes;
2664
+ if (boxes && Array.isArray(boxes)) {
2665
+ // Guardar cajas para redibujar en cada frame
2666
+ lastBoxes = boxes;
2667
 
2668
  // Limpiar el canvas y dibujar el frame actual
2669
  displayCtx.drawImage(video, 0, 0, display.width, display.height);
2670
 
2671
  // Dibujar cada caja
2672
+ if (boxes.length > 0) {
2673
  console.log(`Dibujando ${boxes.length} cajas de rostros`);
2674
  boxes.forEach(box => {
2675
  if (box && box.length >= 5) {
 
2712
  displayCtx.font = 'bold 18px Arial';
2713
  displayCtx.fillText('No se detectan rostros', 20, 30);
2714
  console.log('No hay cajas para dibujar o formato inválido');
2715
+
2716
+ // Limpiar cajas almacenadas si se recibe explícitamente un array vacío
2717
+ if (!message.forceDisplay) {
2718
+ lastBoxes = [];
2719
+ }
2720
  }
2721
 
2722
  // Dibuja un rectángulo de prueba siempre, para verificar que el canvas funciona
2723
  drawTestRect();
2724
  }
2725
+ } else if (event.data && event.data.type === 'testRect') {
2726
+ // Si solo recibimos una señal de prueba, dibujar el rectángulo
2727
+ console.log('Recibido señal para dibujar rectángulo de prueba');
2728
+ drawTestRect();
2729
  }
2730
  });
2731
 
 
2812
  # Imprimir en el servidor para depuración
2813
  print(f"Cajas de rostros detectadas: {bbox_list}")
2814
 
2815
+ # Crear script para enviar datos al componente JavaScript - Versión mejorada con múltiples métodos
2816
  face_boxes_js = f"""
2817
  <script>
2818
+ // Versión mejorada para envío garantizado de detecciones faciales
2819
+ (function() {{
2820
+ console.log("DETECCIÓN FACIAL: Enviando datos de {len(bbox_list)} rostros");
2821
+
2822
+ const message = {{
2823
+ type: 'faceBoxes',
2824
+ boxes: {json.dumps(bbox_list)},
2825
+ timestamp: Date.now()
2826
+ }};
 
 
2827
 
2828
+ // Función para enviar mensaje a todos los destinos posibles
2829
+ function broadcastMessage() {{
2830
+ console.log("Intentando enviar mensaje a todos los destinos posibles");
2831
+
2832
+ // 1. Enviar directo al window
2833
+ try {{
2834
+ window.postMessage(message, '*');
2835
+ console.log("Mensaje enviado a window");
2836
+ }} catch(e) {{
2837
+ console.error("Error enviando a window:", e);
2838
+ }}
2839
+
2840
+ // 2. Enviar al parent (desde iframe)
2841
+ try {{
2842
+ window.parent.postMessage(message, '*');
2843
+ console.log("Mensaje enviado a parent");
2844
+ }} catch(e) {{
2845
+ console.error("Error enviando a parent:", e);
2846
+ }}
2847
+
2848
+ // 3. Enviar a todos los iframes en la página
2849
+ try {{
2850
+ const frames = document.getElementsByTagName('iframe');
2851
+ console.log(`Encontrados ${frames.length} iframes`);
2852
+ for(let i = 0; i < frames.length; i++) {{
2853
+ try {{
2854
+ frames[i].contentWindow.postMessage(message, '*');
2855
+ console.log(`Mensaje enviado a iframe[${i}]`);
2856
+ }} catch(e) {{
2857
+ console.error(`Error enviando a iframe[${i}]:`, e);
2858
+ }}
2859
+ }}
2860
+ }} catch(e) {{
2861
+ console.error("Error accediendo a iframes:", e);
2862
+ }}
2863
+
2864
+ // 4. Enviar a todos los iframes en el parent
2865
+ try {{
2866
+ const parentFrames = window.parent.document.getElementsByTagName('iframe');
2867
+ console.log(`Encontrados ${parentFrames.length} iframes en parent`);
2868
+ for(let i = 0; i < parentFrames.length; i++) {{
2869
+ try {{
2870
+ parentFrames[i].contentWindow.postMessage(message, '*');
2871
+ console.log(`Mensaje enviado a parent.iframe[${i}]`);
2872
+ }} catch(e) {{
2873
+ console.error(`Error enviando a parent.iframe[${i}]:`, e);
2874
+ }}
2875
+ }}
2876
+ }} catch(e) {{
2877
+ console.error("Error accediendo a iframes de parent:", e);
2878
  }}
 
 
 
2879
  }}
2880
+
2881
+ // Llamar inmediatamente
2882
+ broadcastMessage();
2883
+
2884
+ // Reintentar varias veces para asegurar la entrega
2885
+ setTimeout(broadcastMessage, 100);
2886
+ setTimeout(broadcastMessage, 500);
2887
+ setTimeout(broadcastMessage, 1000);
2888
+ }})();
2889
  </script>
2890
  """
2891
 
2892
  # Inyectar el script en la página
2893
+ components.html(face_boxes_js, height=0, width=0)
2894
 
2895
+ # Agregar un script para forzar la visualización de los rectángulos
2896
+ # y actualizar el estado del canvas incluso si no hay detecciones
2897
+ force_display_js = """
2898
  <script>
2899
+ (function() {
2900
+ console.log("Forzando actualización del rectángulo de prueba");
2901
+
2902
+ const noDetectionMessage = {
2903
+ type: 'faceBoxes',
2904
+ boxes: [],
2905
+ timestamp: Date.now(),
2906
+ forceDisplay: true
2907
+ };
2908
+
2909
+ // Enviar a todos los destinos posibles
2910
+ try { window.parent.postMessage(noDetectionMessage, '*'); } catch(e) {}
2911
+ try { window.postMessage(noDetectionMessage, '*'); } catch(e) {}
2912
+
2913
+ try {
2914
+ const frames = document.getElementsByTagName('iframe');
2915
+ for(let i = 0; i < frames.length; i++) {
2916
+ try { frames[i].contentWindow.postMessage(noDetectionMessage, '*'); } catch(e) {}
2917
+ }
2918
+ } catch(e) {}
2919
+
2920
+ // Enviar señal de prueba para verificar comunicación
2921
+ const testMessage = {
2922
+ type: 'testRect',
2923
+ timestamp: Date.now()
2924
+ };
2925
+
2926
+ try { window.parent.postMessage(testMessage, '*'); } catch(e) {}
2927
+ try { window.postMessage(testMessage, '*'); } catch(e) {}
2928
+
2929
+ try {
2930
+ const frames = document.getElementsByTagName('iframe');
2931
+ for(let i = 0; i < frames.length; i++) {
2932
+ try { frames[i].contentWindow.postMessage(testMessage, '*'); } catch(e) {}
2933
+ }
2934
+ } catch(e) {}
2935
+ })();
2936
  </script>
2937
  """
2938
+ components.html(force_display_js, height=0, width=0)
2939
  except Exception as e:
2940
  st.error(f"Error processing camera frame: {str(e)}")
2941
  st.info("Camera continues to run. Processing will be attempted on next frame.")