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:
-
Ricercare una webcam installata nel sistema.
-
Creare un Player associato al dispositivo.
-
Per il flusso video prodotto dalla webcam:
- Confrontare il formato con i formati supportati.
- Se c'è riscontro impostare tale formato.
- Grabbare
un frame componente il flusso video.
-
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 è nato
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.
|