MokaByte 91- Dicembre 2004 
Cattura di immagini da una webcam con JMF
di
Vincenzo Viola

Questo mese vedremo come catturare le immagini componenti il flusso live prodotto da una webcam utilizzando le JMF API. All'utente sarà consentito controllare questo processo tramite un'apposita GUI


Introduzione
Naturalmente condizione indispensabile per poter catturare immagini da una webcam utilizzando JMF, è quello di aver installato sul proprio pc una webcam compatibile con JMF. Se la vostra webcam ha ormai qualche anno, le probabilità che questo sia vero non sono molte, comunque potete verificare sul sito di Sun, a questo link:
http://java.sun.com/products/java-media/jmf/2.1.1/formats.html#Capturers
dove appunto c'è una lista un pò datata dei device supportati. In generale i dispositivi più moderni delle maggiori case produttrici sono quasi tutti compatibili, almeno su piattaforma Windows, poichè sono quasi sempre dotati dei drivers VFW. Su piattaforma Linux affinchè la webcam funzioni c'è bisogno dei drivers Video4Linux; sul sito http://www.exploits.org/v4l/ potete scaricarne per diversi tipi di webcam.

Nell'articolo verranno analizzate quasi esclusivamente le API legate all'uso specifico della webcam, mentre si daranno per note le funzionalità di JMF già trattate negli articoli precedenti e sarà appenna accennata la realizzazione in Java della GUI, di cui peraltro è spiegato il funzionamento e riportato il codice, ma che non è oggetto di questa serie di articoli.

 

L'algoritmo di cattura
L'algoritmo di cattura è costituito dai seguenti 5 passi:

  1. Ricercare una webcam installata nel sistema.
  2. Creare un Player associato al dispositivo.
  3. Per il flusso video prodotto dalla webcam:
    - Confrontare il formato con i formati supportati.
    - Se c'è riscontro impostare tale formato.
  4. Grabbare un frame componente il flusso video.
  5. Convertire il frame in un'immagine e salvarlo in formato JPEG.

Poiché vogliamo che tutta l'elaborazione sia eseguita attraverso un'interfaccia, struttureremo le operazioni nel seguente modo: quando viene lanciata l'applicazione viene ricercata la webcam e aperta una finestra che ci mostra quello che la cam sta riprendendo (Figura 1).


Figura 1
- Interfaccia grafica: finestra di avvio


L'utente a questo punto può scegliere la risoluzione del video (Figura 2), secondo quello che la webcam permette, e può scegliere, attraverso un'apposito tasto di catturare un'immagine.


Figura 2
- Interfaccia grafica: scelta della risoluzione

In quest'ultimo caso l'immagine catturata verrà mostrata in un'altra finestra, alla chiusura della quale verrà chiesto all'utente dove e con quale nome salvare l'immagine (Figura 3).


Figura 3
- Interfaccia grafica: cattura e salvataggio dell'immagine.

Ricerca della webcam
Per trovare la webcam installata nel nostro sistema utilizziamo il CaptureDeviceManager, il quale, attraverso un registro e un meccanismo di query per localizzare i dispositivi, restituisce, per i dispositivi di cattura presenti, una serie di informazioni, che costituiscono un CaptureDeviceInfo.
In particolare utilizzando il metodo getDeviceList(Format format) si ottiene una lista di oggetti CaptureDeviceInfo corrispondenti ai dispositivi in grado di catturare dati nel formato richiesto. Con getDeviceList(null) i dispositivi di cattura considerati saranno tutti quelli disponibili.
Tra le informazioni contenute nel CaptureDeviceInfo c'è sicuramente il nome della camera, che si ottiene tramite il metodo CaptureDeviceInfo.getName(). Se questo nome comincia con "vfw" il dispositivo è dotato dei drivers che ne consentono la compatibilità con JMF su sistema operativo Windows. Non è detto però che il nome cominci con "vfw", perciò utilizziamo la classe com.sun.media.protocol.vfw.VFWCapture che ricerca proprio i dispositivi dotati di questo tipo di drivers. La classe com.sun.media.protocol.vfw.VFWSourceStream invece fornisce il CaptureDeviceInfo di tali dispositivi.
Se invece siete su Linux, l'unica cosa che si può fare è vedere se il nome della camera inizia con "Video4Linux".
Le operazioni su descritte vengono eseguite all'interno della classe autodetect():

public CaptureDeviceInfo autoDetect () {
  Vector list = CaptureDeviceManager.getDeviceList(null);
  CaptureDeviceInfo devInfo = null;
  if (list != null ) {
    String name;
    for (int i=0; i<list.size(); i++ ) {
      devInfo = (CaptureDeviceInfo)list.elementAt (i);
      name = devInfo.getName();
      if (name.startsWith("vfw:")) {
        break;
      }
    }
    if (devInfo != null && devInfo.getName().startsWith("vfw:")) {
      return ( devInfo );
    } else {
    for (int i = 0; i < 10; i++ ) {
      try {
        name = VFWCapture.capGetDriverDescriptionName(i);
        if (name != null && name.length() > 1) {
          devInfo = VFWSourceStream.autoDetect(i);
            if (devInfo != null) {
              return (devInfo );
            }
        }
      }
      
catch (Exception ioEx) {
        statusBar.setText ( "Errore nella ricerca della
        webcam : " + ioEx.getMessage());
      }
    }
    return (null);
  }
  } else {
    return (null);
  }
}

 

Creazione di un Player associato alla webcam e impostazione del formato
Una volta che abbiamo ottenuto le informazioni sulla webcam installata nel sistema, abbiamo bisogno di un controllo sul flusso da essa prodotto. Questo controllo ce lo fornisce il Player.
Sappiamo già che un Player necessita di un MediaLocator, che, per quanto riguarda la webcam, ci è dato dal metodo CaptureDeviceInfo.getLocator(). Poiché per le operazioni che andremo ad eseguire sul Player, avremo bisogno che esso si trovi nello stato Realized, lo creiamo utilizzando il metodo Manager.createRealizedPlayer(MediaLocator ml), che crea il Player, chiama il metodo realize su di esso e aspetta finché non è nello stato Realized.
A questo punto dopo aver startato il Player e ottenuto da esso i formati video supportati da JMF, li si vanno a confrontare con quelli che la webcam supporta, iniziando da quello attualmente impostato. Quando questo confronto produce un risultato positivo, il formato comune viene impostato per la visualizzazione nella GUI. All'utente poi, come già detto, sarà possibile cambiare il formato all'interno dell'interfaccia, e di conseguenza cambierà la visualizzazione.
Dalla webcam la lista dei formati supportati si ottiene con CaptureDeviceInfo.getFormats().

 

Grabbing e salvataggio di un'immagine
Queste operazioni sono del tutto analoghe a quelle eseguite per il grabbing dei frame di un video, con la differenza che questa volta il video non è su file ma in streaming.
Il controllo sul grabbing però lo otteniamo dal Player, e tutto quello che cambia nell'utilizzare un file piuttosto che una webcam, è, come abbiamo già visto, solo il MediaLocator.
Quando nella GUI, viene premuto il tasto "Cattura immagine", viene richiamato il metodo grabFrameBuffer(), che ci rende disponibile l'attuale frame all'interno di un buffer, dopo aver ottenuto il controllo sul grabbing con (FrameGrabbingControl) Player.getControl ("javax.media.control.FrameGrabbingControl") e dopo aver grabbato il frame con FrameGrabbingControl.grabFrame():

public Buffer grabFrameBuffer() {
  if (player != null) {
    FrameGrabbingControl fgc = (FrameGrabbingControl)player.getControl
    (javax.media.control.FrameGrabbingControl");
    if (fgc != null) {
      return (fgc.grabFrame());
    } else {
      System.err.println ("Error : FrameGrabbingControl is null");
      return (null);
    }
  } else {
    System.err.println("Error : Player is null");
  return (null);
  }
}

A questo punto il frame viene mostrato in una nuova finestra. Chiudendo questa finestra si passa al salvataggio del frame in formato JPEG, attraverso il metodo saveJPEG(String filename). Si noti che in questo metodo abbiamo impostato la qualità dell'immagine (0 - 1) al massimo. Volendo, questo parametro può essere variato, ottenendo immagini qualitativamente peggiori ma che occupano ovviamente meno spazio su disco:

FileOutputStream out = new FileOutputStream(filename);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bi);
param.setQuality(1.0f, false);
encoder.setJPEGEncodeParam(param);


Implementazione
Riportiamo di seguito, il codice che esegue la cattura delle immagine da webcam mediante GUI:


import javax.swing.*;
import javax.swing.border.*;
import java.io.*;
import javax.media.*;
import javax.media.datasink.*;
import javax.media.format.*;
import javax.media.protocol.*;
import javax.media.util.*;
import javax.media.control.*;
import java.util.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import com.sun.image.codec.jpeg.*;
import com.sun.media.protocol.vfw.VFWCapture;

public class WebcamCapture extends JFrame implements WindowListener,
ComponentListener {
protected final static int MIN_WIDTH = 320;

protected final static int MIN_HEIGHT = 240;

protected static int shotCounter = 1;

protected JLabel statusBar = null;

protected JPanel visualContainer = null;

protected Component visualComponent = null;

protected JToolBar toolbar = null;

protected MyToolBarAction formatButton = null;

protected MyToolBarAction captureButton = null;

protected Player player = null;

protected CaptureDeviceInfo webCamDeviceInfo = null;

protected MediaLocator ml = null;

protected Dimension imageSize = null;

protected FormatControl formatControl = null;

protected VideoFormat currentFormat = null;

protected Format[] videoFormats = null;

protected MyVideoFormat[] myFormatList = null;

protected boolean initialised = false;

/*
* --------------------------------------------------------------
* Costruttore *
* --------------------------------------------------------------
*/
public WebcamCapture(String frameTitle) {
super(frameTitle);
try {
UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
} catch (Exception cnfe) {
System.out.println(cnfe.getMessage());
}
// dimensione della finestra
setSize(320, 260);
addWindowListener(this);
addComponentListener(this);
getContentPane().setLayout(new BorderLayout());
visualContainer = new JPanel();
visualContainer.setLayout(new BorderLayout());
getContentPane().add(visualContainer, BorderLayout.CENTER);
statusBar = new JLabel("");
statusBar.setBorder(new EtchedBorder());
getContentPane().add(statusBar, BorderLayout.SOUTH);
}

/*
* ----------------------------------------------------------------
* @restituisce true se viene trovata una webcam *
* ----------------------------------------------------------------
*/
public boolean initialise() throws Exception {
return (initialise(autoDetect()));
}

/*
* -------------------------------------------------------------------
* @params _deviceInfo, informazioni sulla webcam rilevata * @restituisce
* true se la webcam viene correttamente rilevata*
* -------------------------------------------------------------------
*/
public boolean initialise(CaptureDeviceInfo _deviceInfo) throws Exception {
statusBar.setText("Avvio in corso...");
webCamDeviceInfo = _deviceInfo;
if (webCamDeviceInfo != null) {
statusBar.setText("Connessione alla webcam : "
+ webCamDeviceInfo.getName());
try {
setUpToolBar();
getContentPane().add(toolbar, BorderLayout.NORTH);
ml = webCamDeviceInfo.getLocator();
if (ml != null) {
player = Manager.createRealizedPlayer(ml);
if (player != null) {
player.start();
formatControl = (FormatControl) player
.getControl("javax.media.control.FormatControl");
videoFormats = webCamDeviceInfo.getFormats();
visualComponent = player.getVisualComponent();
if (visualComponent != null) {
visualContainer.add(visualComponent,
BorderLayout.CENTER);
myFormatList = new MyVideoFormat[videoFormats.length];
for (int i = 0; i < videoFormats.length; i++) {
myFormatList[i] = new MyVideoFormat(
(VideoFormat) videoFormats[i]);
}
Format currFormat = formatControl.getFormat();
if (currFormat instanceof VideoFormat) {
currentFormat = (VideoFormat) currFormat;
imageSize = currentFormat.getSize();
visualContainer.setPreferredSize(imageSize);
setSize(imageSize.width, imageSize.height
+ statusBar.getHeight()
+ toolbar.getHeight());
} else {
System.err
.println("Errore nella rilevazione del formato video.");
}
invalidate();
pack();
return (true);
} else {
System.err
.println("Errore nella creazione della componente visiva.");
return (false);
}
} else {
System.err
.println("Errore nella creazione del player.");
statusBar.setText("Errore nella creazione del player.");
return (false);
}
} else {
System.err.println("Nessun MediaLocator per: "
+ webCamDeviceInfo.getName());
statusBar.setText("Nessun MediaLocator per: "
+ webCamDeviceInfo.getName());
return (false);
}
} catch (IOException ioEx) {
statusBar.setText("Connessione a: "
+ webCamDeviceInfo.getName());
return (false);
} catch (NoPlayerException npex) {
statusBar.setText("Errore nella creazione del player.");
return (false);
} catch (CannotRealizeException nre) {
statusBar.setText("Errore nella realizzazione del player.");
return (false);
}
} else {
return (false);
}
}

public void setFormat(VideoFormat selectedFormat) {
if (formatControl != null) {
player.stop();
imageSize = selectedFormat.getSize();
formatControl.setFormat(selectedFormat);
player.start();
statusBar.setText("Formato: " + selectedFormat);
currentFormat = selectedFormat;
visualContainer.setPreferredSize(currentFormat.getSize());
setSize(imageSize.width, imageSize.height + statusBar.getHeight()
+ toolbar.getHeight());
} else {
System.out
.println("Visual component non è un'istanza di FormatControl");
statusBar.setText("Visual component non può cambiare formato");
}
}

public VideoFormat getFormat() {
return (currentFormat);
}

protected void setUpToolBar() {
toolbar = new JToolBar();
formatButton = new MyToolBarAction("Risoluzione", null);
captureButton = new MyToolBarAction("Cattura immagine", null);
toolbar.add(formatButton);
toolbar.add(captureButton);
getContentPane().add(toolbar, BorderLayout.NORTH);
}

protected void toolbarHandler(MyToolBarAction actionBtn) {
if (actionBtn == formatButton) {
Object selected = JOptionPane.showInputDialog(this,
"Selezionare il formato video", "Selezionare il formato",
JOptionPane.INFORMATION_MESSAGE, null, myFormatList,
currentFormat);
if (selected != null) {
setFormat(((MyVideoFormat) selected).format);
}
} else if (actionBtn == captureButton) {
Image photo = grabFrameImage();
if (photo != null) {
MySnapshot snapshot = new MySnapshot(photo, new Dimension(
imageSize));
} else {
System.err.println("Errore : Impossibile grabbare il frame");
}
}
}

/*-------------------------------------------------------------------
* Cerca una webcam installata per Windows (vfw)*
* @restituisce le informazioni sul device trovato
*-------------------------------------------------------------------*/

public CaptureDeviceInfo autoDetect() {
Vector list = CaptureDeviceManager.getDeviceList(null);
CaptureDeviceInfo devInfo = null;
if (list != null) {
String name;
for (int i = 0; i < list.size(); i++) {
devInfo = (CaptureDeviceInfo) list.elementAt(i);
name = devInfo.getName();
if (name.startsWith("vfw:")) {
break;
}
}
if (devInfo != null && devInfo.getName().startsWith("vfw:")) {
return (devInfo);
} else {
for (int i = 0; i < 10; i++) {
try {
name = VFWCapture.capGetDriverDescriptionName(i);
if (name != null && name.length() > 1) {
devInfo = com.sun.media.protocol.vfw.VFWSourceStream
.autoDetect(i);
if (devInfo != null) {
return (devInfo);
}
}
} catch (Exception ioEx) {
statusBar
.setText("Errore nella ricerca della webcam : "
+ ioEx.getMessage());
}
}
return (null);
}
} else {
return (null);
}
}

public void deviceInfo() {
if (webCamDeviceInfo != null) {
Format[] formats = webCamDeviceInfo.getFormats();
if ((formats != null) && (formats.length > 0)) {
}
for (int i = 0; i < formats.length; i++) {
Format aFormat = formats[i];
if (aFormat instanceof VideoFormat) {
Dimension dim = ((VideoFormat) aFormat).getSize();
System.out.println("Video Format " + i + ": "
+ formats[i].getEncoding() + ", " + dim.width
+ " x " + dim.height);
}
}
} else {
System.out.println("Errore : Nessuna webcam trovata!");
}
}

/*
* -------------------------------------------------------------------
* Grabba un frame dalla webcam @restituisce il frame in un buffer
* -------------------------------------------------------------------
*/
public Buffer grabFrameBuffer() {
if (player != null) {
FrameGrabbingControl fgc = (FrameGrabbingControl) player
.getControl("javax.media.control.FrameGrabbingControl");
if (fgc != null) {
return (fgc.grabFrame());
} else {
System.err
.println("Errore : FrameGrabbingControl non disponibile");
return (null);
}
} else {
System.err.println("Errore nel Player");
return (null);
}
}

/*
* -------------------------------------------------------------------
* Converte il buffer frame in un'immagine
* -------------------------------------------------------------------
*/
public Image grabFrameImage() {
Buffer buffer = grabFrameBuffer();
if (buffer != null) {
BufferToImage btoi = new BufferToImage((VideoFormat) buffer
.getFormat());
if (btoi != null) {
Image image = btoi.createImage(buffer);
if (image != null) {
return (image);
} else {
System.err
.println("Errore di conversione Buffer - BufferToImage");
return (null);
}
} else {
System.err.println("Errore nella creazione di BufferToImage");
return (null);
}
} else {
System.out.println("Errore: buffer vuoto");
return (null);
}
}

public void playerClose() {
if (player != null) {
player.close();
player.deallocate();
player = null;
}
}

public void windowClosing(WindowEvent e) {
playerClose();
System.exit(1);
}

public void componentResized(ComponentEvent e) {
Dimension dim = getSize();
boolean mustResize = false;
if (dim.width < MIN_WIDTH) {
dim.width = MIN_WIDTH;
mustResize = true;
}
if (dim.height < MIN_HEIGHT) {
dim.height = MIN_HEIGHT;
mustResize = true;
}
if (mustResize)
setSize(dim);
}

public void windowActivated(WindowEvent e) {
}

public void windowClosed(WindowEvent e) {
}

public void windowDeactivated(WindowEvent e) {
}

public void windowDeiconified(WindowEvent e) {
}

public void windowIconified(WindowEvent e) {
}

public void windowOpened(WindowEvent e) {
}

public void componentHidden(ComponentEvent e) {
}

public void componentMoved(ComponentEvent e) {
}

public void componentShown(ComponentEvent e) {
}

protected void finalize() throws Throwable {
playerClose();
super.finalize();
}

class MyToolBarAction extends AbstractAction {

public MyToolBarAction(String name, String imagefile) {
super(name);
}

public void actionPerformed(ActionEvent event) {
toolbarHandler(this);
}

};

class MyVideoFormat {

public VideoFormat format;

public MyVideoFormat(VideoFormat _format) {
format = _format;
}

public String toString() {
Dimension dim = format.getSize();
return (format.getEncoding() + " [ " + dim.width + " x "
+ dim.height + " ]");
}

};

class MySnapshot extends JFrame {
protected Image photo = null;

protected int shotNumber;

public MySnapshot(Image grabbedFrame, Dimension imageSize) {
super();
shotNumber = shotCounter++;
setTitle("Immagine" + shotNumber);
photo = grabbedFrame;
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
int imageHeight = photo.getWidth(this);
int imageWidth = photo.getHeight(this);
setSize(imageSize.width, imageSize.height);
final FileDialog saveDialog = new FileDialog(this,
"Salva immagine", FileDialog.SAVE);
final JFrame thisCopy = this;
saveDialog.setFile("Immagine" + shotNumber);

addWindowListener(new WindowAdapter() {

public void windowClosing(WindowEvent e) {
saveDialog.show();
String filename = saveDialog.getFile();
if (filename != null) {
if (saveJPEG(filename)) {
JOptionPane.showMessageDialog(thisCopy,
"Salvata immagine " + filename);
setVisible(false);
dispose();
} else {
JOptionPane.showMessageDialog(thisCopy,
"Errore nel salvataggio di " + filename);
}
} else {
setVisible(false);
dispose();
}
}
});

setVisible(true);
}

public void paint(Graphics g) {
g.drawImage(photo, 0, 0, getWidth(), getHeight(), this);
}

/*
* -------------------------------------------------------------------
* Salva un'immagine come JPEG * @params immagine da salvare * @params
* nome del file dove salvare l'immagine *
* -------------------------------------------------------------------
*/
public boolean saveJPEG(String filename) {
boolean saved = false;
BufferedImage bi = new BufferedImage(photo.getWidth(null), photo
.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = bi.createGraphics();
g2.drawImage(photo, null, null);
FileOutputStream out = null;
try {
out = new FileOutputStream(filename);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bi);
param.setQuality(1.0f, false);
encoder.setJPEGEncodeParam(param);
encoder.encode(bi);
out.close();
saved = true;
} catch (Exception ex) {
System.out.println("Errore salvataggio JPEG: "
+ ex.getMessage());
}
return (saved);
}
}

public static void main(String[] args) {
try {
WebcamCapture myWebCam = new WebcamCapture("WebCam Capture");
myWebCam.setVisible(true);
if (!myWebCam.initialise()) {
System.out.println("WebCam non trovata / inizializzata");
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

 

Conclusioni
Abbiamo realizzato un'applicazione che molto più semplicemente può essere eseguita utilizzando il software che viene fornito all'atto dell'acquisto della vostra webcam. Ma se avete bisogno di controllare la vostra webcam anche se non siete in casa, per farle ad esempio salvare un'immagine ogni ora, quest'applicazione vi sarà molto utile, soprattutto su sistemi operativi diversi da Windows, per i quali probabilmente non ci saranno software in dotazione con la webcam.

 

Bibliografia
[1] "JMF API Specification", java.sun.com/products/java-media/jmf/index.jsp


Vincenzo Viola è n
ato a Formia (LT) il 03/11/1977, laureato in Ingegneria delle Telecomunicazioni presso l'Università Federico II di Napoli con una tesi sulla segmentazione automatica di video digitali in scene, con sviluppo software implementato su piattaforma Java - Oracle. Lavora come progettista software per una Mobile Company che offre la sua consulenza ai gestori di telefonia e alle major del settore ICT.

 


MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it