目录

阿崔实验室

记录精彩的程序人生

X

B/S系统集成高拍仪

概述

高拍仪厂家一般都提供B/S系统集成高拍仪的方法,厂商提供的二次开发绝大多数都是基于ActiveX控件,通过JS脚本来控制,功能上受限,只能使用ActiveX控件暴露的接口且无法自定义界面,另外ActiveX控件的使用限制了系统只能使用IE浏览器或基于IE内核的浏览器。本文采用一种基于Java的解决方案,通过Java Web Start技术可以兼容各种浏览器,也可以在非Window系统下使用,缺点是通常会比厂商提供的原生程序需要慢一些,但可以接受。

数据采集

高拍仪本质上可以看做是USB摄像头,由于Java不能直接访问硬件,在Java的世界,访问摄像头必须借助本地库才能实现,这足以让一大部分人止步,所幸的是,有高手为我们开发了这样一个库github: webcam-capture官网: webcam-capture

The goal of this project is to allow integrated or USB-connected webcams to be accessed directly
 from Java. Using provided libraries users are able to read camera images and detect motion. 
Main project consist of several sub projects - the root one, which contains required classes, 
build-in webcam driver compatible with Windows, Linux and Mac OS…

这个库以驱动的形式集成了多种访问摄像头的方式,适用于各种场景,使用简单,示例丰富,强烈推荐有需要的同学使用。目前支持以下驱动:

下面是一段功能很丰富的代码示例,来自WebcamViewerExample.java,大家体会一下👍 :

import java.awt.BorderLayout;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.lang.Thread.UncaughtExceptionHandler;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamDiscoveryEvent;
import com.github.sarxos.webcam.WebcamDiscoveryListener;
import com.github.sarxos.webcam.WebcamEvent;
import com.github.sarxos.webcam.WebcamListener;
import com.github.sarxos.webcam.WebcamPanel;
import com.github.sarxos.webcam.WebcamPicker;
import com.github.sarxos.webcam.WebcamResolution;


/**
 * Proof of concept of how to handle webcam video stream from Java
 * 
 * @author Bartosz Firyn (SarXos)
 */
public class WebcamViewerExample extends JFrame implements Runnable, WebcamListener, WindowListener, UncaughtExceptionHandler, ItemListener, WebcamDiscoveryListener {

	private static final long serialVersionUID = 1L;

	private Webcam webcam = null;
	private WebcamPanel panel = null;
	private WebcamPicker picker = null;

	@Override
	public void run() {

		Webcam.addDiscoveryListener(this);

		setTitle("Java Webcam Capture POC");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLayout(new BorderLayout());

		addWindowListener(this);

		picker = new WebcamPicker();
		picker.addItemListener(this);

		webcam = picker.getSelectedWebcam();

		if (webcam == null) {
			System.out.println("No webcams found...");
			System.exit(1);
		}

		webcam.setViewSize(WebcamResolution.VGA.getSize());
		webcam.addWebcamListener(WebcamViewerExample.this);

		panel = new WebcamPanel(webcam, false);
		panel.setFPSDisplayed(true);

		add(picker, BorderLayout.NORTH);
		add(panel, BorderLayout.CENTER);

		pack();
		setVisible(true);

		Thread t = new Thread() {

			@Override
			public void run() {
				panel.start();
			}
		};
		t.setName("example-starter");
		t.setDaemon(true);
		t.setUncaughtExceptionHandler(this);
		t.start();
	}

	public static void main(String[] args) {
		SwingUtilities.invokeLater(new WebcamViewerExample());
	}

	@Override
	public void webcamOpen(WebcamEvent we) {
		System.out.println("webcam open");
	}

	@Override
	public void webcamClosed(WebcamEvent we) {
		System.out.println("webcam closed");
	}

	@Override
	public void webcamDisposed(WebcamEvent we) {
		System.out.println("webcam disposed");
	}

	@Override
	public void webcamImageObtained(WebcamEvent we) {
		// do nothing
	}

	@Override
	public void windowActivated(WindowEvent e) {
	}

	@Override
	public void windowClosed(WindowEvent e) {
		webcam.close();
	}

	@Override
	public void windowClosing(WindowEvent e) {
	}

	@Override
	public void windowOpened(WindowEvent e) {
	}

	@Override
	public void windowDeactivated(WindowEvent e) {
	}

	@Override
	public void windowDeiconified(WindowEvent e) {
		System.out.println("webcam viewer resumed");
		panel.resume();
	}

	@Override
	public void windowIconified(WindowEvent e) {
		System.out.println("webcam viewer paused");
		panel.pause();
	}

	@Override
	public void uncaughtException(Thread t, Throwable e) {
		System.err.println(String.format("Exception in thread %s", t.getName()));
		e.printStackTrace();
	}

	@Override
	public void itemStateChanged(ItemEvent e) {
		if (e.getItem() != webcam) {
			if (webcam != null) {

				panel.stop();

				remove(panel);

				webcam.removeWebcamListener(this);
				webcam.close();

				webcam = (Webcam) e.getItem();
				webcam.setViewSize(WebcamResolution.VGA.getSize());
				webcam.addWebcamListener(this);

				System.out.println("selected " + webcam.getName());

				panel = new WebcamPanel(webcam, false);
				panel.setFPSDisplayed(true);

				add(panel, BorderLayout.CENTER);
				pack();

				Thread t = new Thread() {

					@Override
					public void run() {
						panel.start();
					}
				};
				t.setName("example-stoper");
				t.setDaemon(true);
				t.setUncaughtExceptionHandler(this);
				t.start();
			}
		}
	}

	@Override
	public void webcamFound(WebcamDiscoveryEvent event) {
		if (picker != null) {
			picker.addItem(event.getWebcam());
		}
	}

	@Override
	public void webcamGone(WebcamDiscoveryEvent event) {
		if (picker != null) {
			picker.removeItem(event.getWebcam());
		}
	}
}

上述代码是基于Swing开发的,数据获取的工作完全可以在此基础上完成。

获得BufferedImage对象并处理

通过Webcam.getImage()捕获摄像头当前图像,返回值是一个BufferedImage对象,可以对该对象进一步处理,例如通过HttpClient作为附件上传到服务器。
在获得BufferedImage对象之前,可以通过WebcamImageTransformer接口对图像进行预处理,参考How to rotate image from camera with WebcamImageTransformer

选择驱动

前面说了,github: webcam-capture库支持10种驱动,这10种驱动对应不同的访问摄像头的方式,每一种方式都有自己适用的场景,比如V4L4j Driver只能用在Linux操作系统上。这里推荐使用webcam-capture-driver-openimaj驱动。优点是可用于Windows操作系统,速度快(相比较webcam-capture-driver-javacv驱动因为加载了过多的本地库,速度慢且在某些机器上无法使用)。使用时调用Webcam.setDriver(new OpenImajDriver());即可。有一点需要注意的是,webcam-capture-driver-openimaj驱动只支持有限的分辨率,在OpenImajDevice.java文件中可以看到以下代码:

	/**
	 * Artificial view sizes. I'm really not sure if will fit into other webcams
	 * but hope that OpenIMAJ can handle this.
	 */
	private final static Dimension[] DIMENSIONS = new Dimension[] {
		new Dimension(176, 144),
		new Dimension(320, 240),
		new Dimension(352, 288),
		new Dimension(640, 400),
		new Dimension(640, 480),
		new Dimension(1280, 720),
	};

最高支持1280*720分辨率,在有些情况下,这样的分辨率是不够的,我手里有台两台高拍仪,一台是良田的,一台是方正的,在这个分辨率下对A4幅面的纸张进行拍照,边缘总是有大约2厘米无法照到。解决方式是改源码,将要高拍仪支持的要设置的分辨率加入到上面的数组中,或者暴露共有方法可以从外部指定DIMENSIONS数组。分辨率直接影响到拍照的效果,分辨率低的话可能无法识别照片中的文字或数字。如果通过webcam.setViewSize(new Dimension(1280, 1280));指定的分辨率不在上述列表中,则运行时会抛出异常:

version=1.1.2
os.name=Windows 8.1
os.version=6.3
os.arch=amd64
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
176.0,144.0
320.0,240.0
352.0,288.0
640.0,400.0
640.0,480.0
1280.0,720.0
2105.0,1487.0
Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Incorrect dimension [1280x1280] possible ones are [176x144] [320x240] [352x288] [640x400] [640x480] [1280x720] [2105x1487] 
	at com.github.sarxos.webcam.Webcam.setViewSize(Webcam.java:622)
	at com.cast514.tools.gaopaiyi.WebcamFrame.run(WebcamFrame.java:248)
	at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:312)
	at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:745)
	at java.awt.EventQueue.access$300(EventQueue.java:103)
	at java.awt.EventQueue$3.run(EventQueue.java:706)
	at java.awt.EventQueue$3.run(EventQueue.java:704)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:715)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242)
	at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:150)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:146)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:138)
	at java.awt.EventDispatchThread.run(EventDispatchThread.java:91)

实际情况是,通过webcam.setViewSize指定的分辨率必须在上述列表中,但实际的分辨率是高拍仪支持的最接近指定值的分辨率。

B/S系统集成

Java Web Start

下面是搬过来的Java Web Start简介:

Java Web Start是帮助客户机端应用程序开发的一个新技术,该技术的独特之处在于将你关心客
户机是如何启动(从Web浏览器或是桌面)中解放出来。并且,该技术提供了一个使Web服务器
能独立发布和更新客户机代码的集合部署方案。
Java Web Start是一个软件技术,它包含了applet的可移植性、Servlet和Java Server Pages(JSP)
的可维护性以及象XML和HTML这样的标记语言的简易性。它是基于Java的应用程
序,允许从标准的Web服务器启动、部署和更新功能完成的Java 2客户机应用程序。
Java Web Start自身是一个Java应用程序,所以该软件是平台独立的,并且支持Java2平台的任
何客户机系统都支持该软件。当客户机应用程序启动时,Java Web Start自动执行更新,在从原
来的高速缓存装入应用程序的同时,从Web下载罪行的版本。Java Web Start还提供了一个Java
应用程序管理器(Java Application Manager)实用程序,即提供了多种选项,如清除下载的应
用程序的高速缓存、指定多种JRE的使用,设置HTTP代理、还允许最终用户组织他们的Java应用
程序。

关于Java Web Start的使用可以参考其他资料,我们的代码虽然是用Java语言写的,但B/S系统并不限于Java,PHP或Asp.net都可以集成。

数据传输

数据传输使用HTTP协议,在Java中,当然使用久经考验的HttpClient组件,具体使用可以参考其他资料。

总结

上面只是简单介绍了一下集成高拍仪的原理,实际应用的时候可能会遇到各种各样的问题,可以在下面的评论中交流。❤️