B/S 系统集成高拍仪

本贴最后更新于 2109 天前,其中的信息可能已经时过境迁

概述

高拍仪厂家一般都提供 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 组件,具体使用可以参考其他资料。

总结

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

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3190 引用 • 8214 回帖 • 1 关注
  • 高拍仪
    1 引用 • 1 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • someone

    解释的很详细,解决了我头疼好长时间的问题,谢谢楼主。