Buscar
Social
Ofertas laborales ES
« Procesamiento masivo de datos fácil en Hadoop | Main | Este viernes termina el plazo para el envío de tácticas a la JavaCup »
martes
mar062012

Java Zone Espia Web

Nota del editor: este post describe un sencillo programa Swing que no requiere de absolutamente ninguna librería, y que se conecta con varias cámaras web que publican sus imágenes en Internet. La versión que se describe aquí es una segunda versión creada por el autor mejorando una versión publicada recientemente.

Java Zone Espia Web

Mencionaremos los arreglos hechos con respecto a la primera entrega:

 

  • Se carga la ventana mas rápido.
  • Se puede seleccionar una cámara y ver en tamaño completo.
  • Las imágenes se extienden al tamaño total de la ventana.
  • Mayor velocidad de refresco de la secuencia de imágenes.
  • Icono de cámara, que muestra que hay una cámara cargando y que se mostrara en instantes. 

 

Ahora mencionaremos algunos aspectos a mejorar:

  • Mayor cantidad de cámaras para ver.
  • Que podamos automatizar la búsqueda de cámaras activas en Internet y que el programa las muestre.
  • Poder escoger favoritos y que guarde las preferencias por usuario.
  • Mejorar la interfaz, mas opciones. 

 

Imagenes

Codigo


Clase Principal

package clases;

import java.awt.GridLayout;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Principal extends JFrame {

   public JPanel panelPrincipal;
   public ArrayList urls = new ArrayList();
   public ArrayList camaras = new ArrayList();

   public Principal() {

       urls.add("http://146.186.123.229/axis-cgi/jpg/image.cgi?resolution=352x240");
       urls.add("http://131.111.133.11/axis-cgi/jpg/image.cgi?resolution=480x360&dummy=1267804722739");
       urls.add("http://fotogermanoviseu.dyndns.info/axis-cgi/jpg/image.cgi?resolution=640x480&compression=10&color=1&clock=1&date=1");
       urls.add("http://198.82.159.134/axis-cgi/jpg/image.cgi?resolution=640x480&dummy=1152818432828");
       urls.add("http://98.238.252.97/axis-cgi/jpg/image.cgi?resolution=800x600&dummy=1280477002758");
       urls.add("http://80.24.195.19/axis-cgi/jpg/image.cgi?resolution=480x360");
    urls.add("http://216.8.159.21/axis-cgi/jpg/image.cgi?resolution=640x480");
       urls.add("http://hncam1.hn.psu.edu/axis-cgi/jpg/image.cgi?resolution=320x240");

       panelPrincipal = new JPanel();
       panelPrincipal.setLayout(new GridLayout(2, 4, 5, 5));
       for (int i = 0; i < urls.size(); i++) {
               PanelCamara pc = new PanelCamara(i,urls.get(i),this);
               panelPrincipal.add(pc);
               camaras.add(pc);
       }
       add(panelPrincipal);
   }
...
}


Clase PanelCamara

package clases;

import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.net.MalformedURLException;
import java.net.URL;

import javax.swing.ImageIcon;
import javax.swing.JPanel;

public class PanelCamara extends JPanel {

   String url = "";
   int id=0;
   Image img;
   Principal prin;
   HiloFoto hf;
   boolean seleccionado=false;

   public PanelCamara(int id,String url, Principal p) {
       this.url = url;
       this.prin=p;
       this.id=id;
       addMouseListener(new MouseListener() {

  @Override
  public void mouseReleased(MouseEvent e) {

  }

  @Override
  public void mousePressed(MouseEvent e) {

  }

  @Override
  public void mouseExited(MouseEvent e) {

  }

  @Override
  public void mouseEntered(MouseEvent e) {

  }

  @Override
  public void mouseClicked(MouseEvent e) {
   if(seleccionado){
    seleccionado=false;
   }else{
    seleccionado=true;
   }
   if(seleccionado){
    prin.DesactivarHilos();
    hf=new HiloFoto(PanelCamara.this);
          hf.start();
    prin.panelPrincipal.removeAll();
    prin.panelPrincipal.setLayout(new BorderLayout());
    prin.panelPrincipal.add(prin.camaras.get(PanelCamara.this.id));
    prin.setBounds(0, 0, 500, 400);
    prin.setLocationRelativeTo(null);
   }else{
    hf.stop();
    hf=null;
    prin.panelPrincipal.removeAll();
    prin.panelPrincipal.setLayout(new GridLayout(2, 4, 5, 5));
    prin.AgregarPaneles();
    prin.ActivarHilos();
    prin.setBounds(0, 0, 600, 250);
    prin.setLocationRelativeTo(null);
   }
  }
 });
       img = new ImageIcon(this.getClass().getResource("/lib/webcam.png")).getImage();
       this.setDoubleBuffered(true);
       IniciarHilo();
   }

   public void PararHilo(){
    hf=null;
   }

   public void IniciarHilo(){
    hf=new HiloFoto(this);
       hf.start();
   }

   @Override
   public void paintComponent(Graphics g) {
           super.paintComponents(g);
           g.drawImage(img, 0, 0, getWidth(), getHeight(), this);
   }
}

Clase HiloFoto

package clases;

import java.awt.Image;
import java.net.MalformedURLException;
import java.net.URL;

import javax.swing.ImageIcon;

public class HiloFoto extends Thread{

 PanelCamara pc;

 public HiloFoto(PanelCamara pc){
 this.pc=pc;
 }

 public void run(){
 try {
  Thread.sleep(500);
  while(true){
   pc.img.flush();
   pc.img=new ImageIcon(new URL(pc.url)).getImage();
   pc.repaint();
   pc.updateUI();
   Thread.sleep(500);
  }
 } catch (InterruptedException e) {
  e.printStackTrace();
 } catch (MalformedURLException e) {
  e.printStackTrace();
 }
 }
}

 

Descargar

El proyecto completo lo pueden descargar desde aquí, se incluyen los archivos fuentes, las imágenes y el Jar ejecutable.

Recuerden visitarnos en Java Zone

Nota: noticia enviada por JavaZone

PrintView Printer Friendly Version

EmailEmail Article to Friend

Reader Comments (23)

Nota para el autor,

No funciona a través de Proxy, echadle un ojo a esto.
http://www.javahispano.org/antiguo_javahispano_org/2010/9/27/auto-deteccion-de-proxy-para-la-jvm.html.

Hay un par de pegas en la técnica que estas utilizando (nada que no sea salvable) pero no es necesario discutirlas en público.

Por lo demás, felicitaciones.

Un saludo.

marzo 6, 2012 | Registered Commenterefrigerio

Un detalle nomás:

52.if(seleccionado){
53.seleccionado=false;
54.}else{
55.seleccionado=true;
56.}

No sería mejor algo como:
seleccionado = !seleccionado;

Saludos

marzo 6, 2012 | Unregistered Commenterdiego

Una pregunta: ¿Qué software puedes utilizar para hacer envío de imágenes y que este programa las consuma?

Estoy pensando en algo que no necesariamente sea utilizar una cámara web, sino más bien un programa que publica imágenes cada cierto tiempo o bien muestra videos.

Saludos.

marzo 6, 2012 | Unregistered CommenterRicardo

Veo un par de problemas, que te comento:

1.- Los componentes de Swing deben crearse dentro del EDT. Al igual que todos los métodos que usen el RepaintManager. En tu main() deberías usar ésto:


EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
Principal p = new Principal();
p.setVisible(true);
p.setBounds(0, 0, 600, 250);
p.setLocationRelativeTo(null);
p.setTitle("Java Zone Espia Web");
p.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
});

Lo mismo vale para los repaint, update y similares que usas dentro de los Threads.

2.- El método stop() para detener un Thread está "deprecated". Aquí tienes varias explicaciones detalladas, y métodos alternativos.

http://docs.oracle.com/javase/1.4.2/docs/guide/misc/threadPrimitiveDeprecation.html

marzo 6, 2012 | Registered Commenterchoces

Aunque no es un defecto, si usas addMouseListener(new MouseAdapter() en vez de MouseListener, no necesitas declarar todos los métodos del Listener, sino solo los que vayas a usar.
Podrías en tu caso eliminar tranquilamente todo ésto:

@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}

y dejar únicamente el método que sobreescribes, lo que haría el código más legible.

marzo 6, 2012 | Registered Commenterchoces

@diego

Hay otra manera, menos conocida y más eficiente, de hacer lo mismo:

seleccionado ^= true;

marzo 6, 2012 | Registered Commenterchoces

Yo me refería a la técnica que utiliza para acceder y manejar las cámaras, no a la codificación.

:(

marzo 6, 2012 | Registered Commenterefrigerio

@efrigerio

Creo que ha quedado claro a qué te referías. ¿A qué viene esa cara triste? ;D
Podrías comentar sobre esas técnicas, así todos aprenderíamos algo más.

marzo 6, 2012 | Registered Commenterchoces

No tiene nada de extraordinario.

El problema está en esta línea.

pc.img=new ImageIcon(new URL(pc.url)).getImage();

¿Por qué?

Porque los proxy's, e incluso algunas versiones de la JVM, tienden a cachear recursos, especialmente imágenes.

Lo que debería hacer es extender la clase URL, sobrecargar openConnection() y setear a false UseCaches antes de devolver el conector.

Ay otra forma menos invasiva, pero esta es la mas sencilla.

Otra historia es que esta técnica solo funciona con algunos modelos / marcas de CCTV en particular.

¿Que yo sepa?.
Flexwatch
Axis
Y algún modelo de Siemens

Otras marcas no tienen esta facilidad, pero te permiten hacer Streaming por lo que debes pasar por JMF (o el FMJ como te guste) y rezar para que el codec sea compatible.

Otras como Pelco directamente te hacen la vida imposible.

marzo 6, 2012 | Registered Commenterefrigerio

Esto es lo que suelo hacer, aunque lo que dices sobre los proxies me ha dado en qué pensar:


@Override
protected Void doInBackground() {
final int panelLenght = jListImages.getSize().width;
final Dimension imagePanelSize = new Dimension(panelLenght, panelLenght);
final Toolkit toolkit = Toolkit.getDefaultToolkit();
// Now creates the right scaled images
final MediaTracker tracker = new MediaTracker(MusicInfoPanel.this);
for (final Iterator<String> it = artistInfo.getImagesURL().iterator(); it.hasNext();) {
try {
final Image image = toolkit.getImage(new URL(it.next()));
tracker.addImage(image, 0);
tracker.waitForID(0);
if (!tracker.isErrorID(0)) {
final JPSimpleImagePanel imagePanel = new JPSimpleImagePanel();
imagePanel.setPreferredSize(imagePanelSize);
imagePanel.setFitStyle(ImageFitStyle.aspectRatio);
imagePanel.setImage(image);
imagesListModel.addElement(imagePanel);
}
tracker.removeImage(image, 0);
TimeUnit.MILLISECONDS.sleep(1000);
} catch (MalformedURLException | InterruptedException ex) {
JPToolsLogService.getLogger().error(ex.getMessage(), ex);
}
}
return null;
}

marzo 6, 2012 | Registered Commenterchoces

¿Ésto no es suficiente?

http://docs.oracle.com/javase/6/docs/api/java/net/URLConnection.html#setUseCaches(boolean)

marzo 6, 2012 | Registered Commenterchoces

HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setUseCaches(false);

if(usrPass != null)
{ String encoded = new sun.misc.BASE64Encoder().encode(usrPass.getBytes());
urlConnection.setRequestProperty("Authorization", "Basic " + encoded);
}

InputStream inputStream = urlConnection.getInputStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

for(int acumBufSize = 0; (urlConnection.getContentLength()-acumBufSize)!=0;)
{ byte[] buf = null;
int bufSize = 0;
if(urlConnection.getContentLength() > 0 )
{ buf = new byte[urlConnection.getContentLength()];
bufSize = inputStream.read(buf, 0, urlConnection.getContentLength());
}
else
{ buf = new byte[1024*64];
bufSize = inputStream.read(buf);
}
if(bufSize != -1)
{ outputStream.write(buf, 0, bufSize);
acumBufSize += bufSize;
}
else
acumBufSize=urlConnection.getContentLength();
}
inputStream.close();
outputStream.close();
ImageIcon image = new ImageIcon(outputStream.toByteArray());

marzo 6, 2012 | Registered Commenterefrigerio

@efrigerio

Gracias a tu sugerencia he modificado mi código, para incorporar setUseCaches(false) ;)
Todavía me queda una duda, no resuelta, respecto a Toolkit.
Por lo que sé, los métodos de Toolkit getImage y createImage, a partir de una URL, usan código nativo, no código Java.
Me pregunto si el rendimiento de esos métodos es comparable al uso de streams.

marzo 7, 2012 | Registered Commenterchoces

@choces

Nota: lo de
if(usrPass != null)
{ String encoded = new sun.misc.BASE64Encoder().encode(usrPass.getBytes());
urlConnection.setRequestProperty("Authorization", "Basic " + encoded);
}

Va de que mis cámaras están "serradas con llave" no de otra cosa.

En cuanto a tu pregunta.
Por lo que recuerdo la diferencia entre ImageIcon y toolkit.getImage está en que este ultimo funciona como un Cache L2 por lo que si la idea es hacer un simil de streaming con imagenes en remoto (que es de lo que va Java Zone Espia Web) o un tracking fotográfico de eventos en una planta (que es lo mío) no parece buena idea utilizar toolkit.getImage.

Además con esta técnica el mayor costo, no lo tienes en el decoder de la imagen o en el renderizado, si no en el acceso al recurso remoto (TCP).

Con eso en claro un par de técnicas sencillas para ganar rendimiento están en jugar con el tamaño de la imagen en la llamada al recurso, y con el formato de salida y nivel de compresión de la imagen fuente en la configuración de la cámara.

Un ejemplo de lo primero
http://fotogermanoviseu.dyndns.info/axis-cgi/jpg/image.cgi?resolution=640x480

Es más pesado que
http://fotogermanoviseu.dyndns.info/axis-cgi/jpg/image.cgi?resolution=320x240

marzo 7, 2012 | Registered Commenterefrigerio

Mi cuestión sobre Toolkit viene a cuento de lo siguiente:


public ImageIcon (byte[] imageData) {
this.image = Toolkit.getDefaultToolkit().createImage(imageData);
if (image == null) {
return;
}
Object o = image.getProperty("comment", imageObserver);
if (o instanceof String) {
description = (String) o;
}
loadImage(image);
}

protected void loadImage(Image image) {
MediaTracker mTracker = getTracker();
synchronized(mTracker) {
int id = getNextID();

mTracker.addImage(image, id);
try {
mTracker.waitForID(id, 0);
} catch (InterruptedException e) {
System.out.println("INTERRUPTED while loading Image");
}
loadStatus = mTracker.statusID(id, false);
mTracker.removeImage(image, id);

width = image.getWidth(imageObserver);
height = image.getHeight(imageObserver);
}
}

que es el código fuente en JavaSE 1.7 de uno de los constructores de ImageIcon. Todos usan Toolkit, excepto el que usa como parámetro un Image, como era de suponer.
Por eso no veo la ventaja de usar ImageIcon para obtener una imagen, a menos que se necesiten algunos de sus métodos específicos. Y más si con ello se precisa del manejo de streams, que suponen, aunque poco, más recursos.
No hace falta mencionar que el método createImage de Toolkit acepta una URL como parámetro.


Es de esperar que los mayores retardos se generen en los accesos a las URL, y que, comparados con ellos, el procesamiento para la obtención de la imagen, y su renderizado sean ridículos.

marzo 7, 2012 | Registered Commenterchoces

Si ese es el codigo real, le haria falta un repasillo de buenas practicas, como no llamar repetitivamente a un metodo que no cambia de valor en un bucle, no usar APIs internos del JDK susceptibles a cambiar (y que en el OpenJDK no funcionarían)... eso suponiendo que el tratamiento de excepciones no está reflejado por brevedad.

Aparte de eso, recordad que el código se escribe una vez y se lee muchas, por lo que es mejor escribirlo clarito para que sea facil leerlo. Como ejemplo, la condición de finalizacion del bucle del for es de todo menos clara, o llamar a una variable bufSize cuando no es en realidad el tamaño del bufer si no los bytes leidos efectivamente tampoco lo es.

marzo 7, 2012 | Unregistered CommenterMeAndMyself

@choces

je je je

Me parece haber visto (en GNU Classpath, creo) exactamente lo opuesto. es decir a Toolkit utilizando internamente a ImageIcon.

:)

En todo caso para mí la ventaja de utilizar ImageIcon está en que es un Icon.

marzo 7, 2012 | Registered Commenterefrigerio

@choces
Si te interesa el manejo de imágenes en swing que tal si le echas un ojo al soporte de HTML5 – Canvas en Form4G.

Quien sabe, puede que hasta te guste y quieras colaborar.
:)

@MeAndMyself
Como ejemplo, la condición de finalizacion del bucle del for es de todo menos clara, o llamar a una variable bufSize cuando no es en realidad el tamaño del bufer si no los bytes leidos efectivamente tampoco lo es.

Una confesión.
Ese copy and paste es mío, no del OpenJDK, tiene más de 10 años, ha pasado por más proyectos de los que recuerdo prácticamente sin modificaciones, soporta todo tipo de protocolos, y la última vez que se utilizó fue hace 3 o 4 años.

Como expresé más arriba.
Ay otra forma menos invasiva de resolver esto, pero esta es la más fácil de explicar.

Un saludo.

marzo 7, 2012 | Registered Commenterefrigerio

@efrigerio

He cargado ya Form4G en NetBeans, y... ¡No ha explotado nada... todavía! ;P
Tengo que resolver algunas dependencias, y echarle ambos ojos en profundidad.
Si crees que puedo echarte una mano... aunque mi "terreno" son las aplicaciones de escritorio con NetBeans Platform.

En ésto es en lo que ando por ahora:

http://www.javaforge.com/project/jplay

marzo 7, 2012 | Registered Commenterchoces

@choces:
Hola, muy bueno y raro lo tuyo (seleccionado ^= true;)
pregunta: ¿porque es mas eficiente? porque sino me quedo con mi versión que es mas legible ;)
¿no debería el compilador poder generar el mismo bytecode con ambas versiones?
Saludos

marzo 8, 2012 | Unregistered Commenterdiego

Las operaciones bitwise son más rápidas; pero no es raro :D

http://www.javaspecialists.co.za/archive/newsletter.do?issue=042&locale=en_US

marzo 8, 2012 | Registered Commenterchoces

@diego

Ya que te interesas por el bytecode generado en cada caso, he creado una clase de prueba para comparar los bytecode:


public class NewClass {

private boolean negate, invert;

public NewClass() {
negate = !negate;
invert ^= true;
}
}

Y éste es el resultado, con el compilador de JavaSE 1.7u3 (he eliminado lo que no interesa, dejando solamente las instrucciones de inversión)

flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."
":()V
4: aload_0
5: aload_0
6: getfield #2 // Field negate:Z
9: ifne 16
12: iconst_1
13: goto 17
16: iconst_0
17: putfield #2 // Field negate:Z
20: aload_0
21: dup
22: getfield #3 // Field invert:Z
25: iconst_1
26: ixor
27: putfield #3 // Field invert:Z
30: return

marzo 8, 2012 | Registered Commenterchoces

Ya puestos, excepto en entornos muy muy concretos, prefiero una versión legible a ahorrarme un par de bytecodes. Teniendo en cuenta que es una aplicación con tráfico de red, no creo que en este caso sea posible ni medir la diferencia.
Pero para gustos, colores.

marzo 8, 2012 | Unregistered CommenterPosYaPuestos

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>