Главная > Hard > Получение картинки с помощью Java на Raspberry Pi

Получение картинки с помощью Java на Raspberry Pi

Ранее я пытался получить картинку с помощью Arduino Uno, но больше одной картинки за три секунды передать на компьютер не получилось. При попытке передать через Bluetooth ситуация оказалась намного хуже — около 15 секунд. Потом была попытка воспользоваться Arduino Due, которая имеет более мощный микроконтроллер.  Но и тут меня ждала неприятность — более 4-х секунд, что было связанно с проблема со скоростью UART. Поэтому я решил переключиться на другую платформу — Raspberry PI.

Получение картинки с помощью Java на Raspberry Pi

В моём распоряжении оказалась Rapberry PI Model B, 700Mhz, 512Mb RAM и Веб-камера Logitech C270.  Более года она лежала без применения и вот настал её час. Установка Linux и настройка платы описываться не будут. Предполагается, что вы сами это сможете сделать.

Задача по получению картинки с помощью Java на Raspberry PI осложняется малой мощностью платформы. Попытка решить задачу с помощью OpenCV, как это делается на обычном компьютере, заканчивается долгой компиляцией и зависанием. Поэтому нужно искать альтернативные пути.

Можно начать с самого простого варианта: вызвать программку и потом прочитать картинку из файла.

ShellUtil.execute(new String[] { "streamer", "-f", "jpeg", "-o", "./cam1.jpeg" });
File myImage = new File("./cam1.jpeg");
Image image = ImageIO.read(myImage);

Но и тут ждёт проблема. Сколько циклов записи может выдержать карта памяти? Скорее всего через неделю она просто испортится. Поэтому нужно искать другой вариант. Мой взор пал на интерфейс V4L2 и Java Native Interface.

Для начала нужно подготовить класс, который будет отвечать за JNI. Он будет получать путь к камере, желаемое разрешение и будет отдавать BufferedImage.

import java.awt.image.BufferedImage;

public class VideoDevice {
	private int width = 640;
	private int height = 480;
	private String deviceName = "/dev/video0";

	private boolean firstInit = true;
	
	public VideoDevice(int width, int height) {
		this.width = width;
		this.height = height;
	}
	
	public VideoDevice(String deviceName, int width, int height) {
		this(width, height);
		this.deviceName = deviceName;
	}
	
	public BufferedImage read() {
		setDebug(false);
		if (firstInit) {
			deviceInit(deviceName, width, height);
			firstInit = false;
		}

		byte[]data = readImage();
		BufferedImage out = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);  
		out.getRaster().setDataElements(0, 0, width, height, data); 

		return out;
	}
	
	protected void finalize() {
		if (!firstInit) {
			deviceUninit();
		}
	}

	private native void setDebug(boolean debug);
	//Read with init
	private native int deviceInit(String deviceName, int width, int height);
	private native byte[] readImage();
	private native int deviceUninit();
	//read image, init inside
	private native byte[] readImage2(String deviceName, int width, int height);
}

Теперь нужно его скомпилировать и создать заголовок для программы на C.

#!/bin/bash
#Это начало bash-скрипта, который используется для компиляции.

output='./output'
src='./src'

rm -rf $output
mkdir $output

echo '--> Compiling java'
javac -d $output $src/*.java

echo '--> Creating C++ header'
cd $output
javah VideoDevice

В результате у нас получается вот такой заголовочный файл для JNI.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class VideoDevice */

#ifndef _Included_VideoDevice
#define _Included_VideoDevice
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     VideoDevice
 * Method:    setDebug
 * Signature: (Z)V
 */
JNIEXPORT void JNICALL Java_VideoDevice_setDebug
  (JNIEnv *, jobject, jboolean);

/*
 * Class:     VideoDevice
 * Method:    deviceInit
 * Signature: (Ljava/lang/String;II)I
 */
JNIEXPORT jint JNICALL Java_VideoDevice_deviceInit
  (JNIEnv *, jobject, jstring, jint, jint);

/*
 * Class:     VideoDevice
 * Method:    readImage
 * Signature: ()[B
 */
JNIEXPORT jbyteArray JNICALL Java_VideoDevice_readImage
  (JNIEnv *, jobject);

/*
 * Class:     VideoDevice
 * Method:    deviceUninit
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_VideoDevice_deviceUninit
  (JNIEnv *, jobject);

/*
 * Class:     VideoDevice
 * Method:    readImage2
 * Signature: (Ljava/lang/String;II)[B
 */
JNIEXPORT jbyteArray JNICALL Java_VideoDevice_readImage2
  (JNIEnv *, jobject, jstring, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

Теперь его нужно включить в программу на C и реализовать методы. Код можно посмотреть по ссылке. В реализации использовался V4L2 MMAP.

echo '--> Compiling C++ library'
cd ../
cp $src/camera.c $output/
cd $output/
gcc -c -Wall -I/usr/lib/jvm/java-7-openjdk-armhf/include -I/usr/lib/jvm/java-7-opensdk-armhf/include/linux camera.c

gcc -shared -Wl,-soname,libcamera.so.1 -o libcamera.so.1.0 camera.o

mv libcamera.so.1.0 libcamera.so

Теперь у нас есть библиотека, которую можно протестировать.

import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.File;

public class VideoTest {
  static {
   System.loadLibrary("camera");
  }

  public static void main(String[]args) throws Exception {
    System.out.println("Start Testing");

    VideoDevice videoDevice = new VideoDevice(640, 480);

	long startTime = System.currentTimeMillis();
	for (int i = 0; i < 20; i++) {
		BufferedImage out = videoDevice.read();
		ImageIO.write(out, "jpg", new File("test" + i + ".jpg"));   
		System.out.println(i + " with free memory (bytes): " + Runtime.getRuntime().freeMemory());
	}
	long endTime = System.currentTimeMillis();
		
    System.out.println("Done: " + (endTime - startTime));
  }
}

Тест показывает, что таким образом можно получить около одного кадра в секунду. На Netbook получается 3 кадра в секунду. Если уменьшить разрешение до 320 на 240, то уже получается 2 кадра в секунду.

Пример очень простой и поддерживает только одну камеру, но он прекрасно подходит под Raspberry ввиду своей компактности.

Весь пример можно скачать по ссылке.

Categories: Hard Tags: , , ,
  1. Пока что нет комментариев.
  1. Пока что нет уведомлений.