之前的文章有提到,通过Cykit将OpenVIBE与EPOC+连接。完成设备连接后,就可以参考官方案例实现自己的脑-机接口控制程序了。
我的目标是实现四个指令的SSVEP脑-机接口,官方SSVEP案例场景只实现了三个指令,所以要在这个基础上进行一些修改,案例中的脑电处理场景确实很容易被改为四个分类,但是呈现SSVEP刺激的程序只能自己来实现了。
视觉刺激可以通过很多方法实现,我是选用的Unity来做的。无论用什么方法,视觉刺激程序和OV的通讯是关键问题,因为在离线采集的时候OV是作为逻辑控制中心的。
通过VRPN通信实现OPenVIBE与Unity3D之间的指令传递
OV的中列举了实现数据传输的方法,具体内容可以参考下面的链接,对于指令的传输,官方是推荐使用VRPN的方式,案例中也确实是由这种方式实现,但是目前官方VRPN的相关说明文档好像被删掉了,相关代码我会附在后面,代码需要引入OV源码中VRPN的include和lib,可以根据自己所用版本下载相应的OV源码并编译得到。
OpenVIBE与其他程序数据通信方法:http://openvibe.inria.fr/overview-sending-data-stimulations-out-from-openvibe/
OpenVIBE源码编译说明:http://openvibe.inria.fr/build-instructions/
OpenVIBE提供了VRPN Server和VRPN Client两种工具,可以实现Server向Client的单向传输。也就是说需要在Unity程序中分别实现相应的VRPN Client和VRPN Server功能。结构如下图:
官方给的VRPN Server和VRPN Client程序是C++语言写的,所以根据需求将其封装成DLL导入到Unity3D中使用,这种方法网上有很多教程,我在这里随便贴几个作为参考。
unity调用c++ dll方法介绍:https://blog.youkuaiyun.com/yuhan61659/article/details/80335574
C#调用C++数组,结构体DLL:https://www.cnblogs.com/ye-ming/p/7976986.html
C#调用DLL各种传参:https://www.cnblogs.com/ahuo/p/5457420.html
VRPN Server官方案例代码
参考官方代码,根据自己的需求在程序中实现相应的VRPN Server和VRPN Client功能,从而实现通信。
main.cpp
#include "GenericVRPNServer.h"
#include <iostream>
#include <cmath>
#include <windows.h>
#define DEFAULT_PORT 50555
#define M_PI 3.1415926
using namespace std;
int main(){
GenericVRPNServer* vrpnService = GenericVRPNServer::getInstance(DEFAULT_PORT);
const char* buttonDevice = "button_test";
const char* analogDevice = "analog_test";
vrpnService->addAnalog(analogDevice,2);
vrpnService->addButton(buttonDevice, 2);
double time = 0;
double period = 0;
while (true)
{
if (period >= 2 * M_PI)
{
vrpnService->changeButtonState(buttonDevice, 0, 1 - vrpnService->getButtonState(buttonDevice, 0));
period = 0;
}
vrpnService->changeAnalogState(analogDevice, sin(time), cos(time));
time = time + 0.01;
period = period + 0.01;
vrpnService->loop();
Sleep(10);
}
GenericVRPNServer::deleteInstance();
vrpnService = nullptr;
return 0;
}
GenericVRPNServer.cpp
#include "GenericVRPNServer.h"
#include <vrpn_Connection.h>
#include <vrpn_Button.h>
#include <vrpn_Analog.h>
#include <cstdarg>
GenericVRPNServer* GenericVRPNServer::serverInstance = nullptr;
GenericVRPNServer::GenericVRPNServer(int port)
{
_connection = vrpn_create_server_connection(port);
}
GenericVRPNServer::~GenericVRPNServer()
{
deleteInstance();
}
GenericVRPNServer* GenericVRPNServer::getInstance(int port)
{
if (serverInstance == NULL)
{
serverInstance = new GenericVRPNServer(port);
}
return serverInstance;
}
void GenericVRPNServer::deleteInstance(void)
{
for (std::map<std::string, ButtonServer>::iterator it = serverInstance->_buttonServer.begin(); it != serverInstance->_buttonServer.end(); ++it)
{
delete it->second.server;
}
serverInstance->_buttonServer.clear();
for (std::map<std::string, AnalogServer>::iterator it = serverInstance->_analogServer.begin(); it != serverInstance->_analogServer.end(); ++it)
{
delete it->second.server;
}
serverInstance->_analogServer.clear();
delete serverInstance;
serverInstance = nullptr;
}
void GenericVRPNServer::loop()
{
for (std::map<std::string, ButtonServer>::iterator it = _buttonServer.begin(); it != _buttonServer.end(); ++it)
{
it->second.server->mainloop();
}
for (std::map<std::string, AnalogServer>::iterator it = _analogServer.begin(); it != _analogServer.end(); ++it)
{
it->second.server->mainloop();
}
_connection->mainloop();
}
void GenericVRPNServer::addButton(std::string name, int buttonCount)
{
ButtonServer serverObject;
serverObject.server = new vrpn_Button_Server(name.c_str(), _connection, buttonCount);
serverObject.buttonCount = buttonCount;
_buttonServer.insert(std::pair<std::string, ButtonServer>(name, serverObject));
_buttonServer[name].cache.clear();
_buttonServer[name].cache.resize(buttonCount);
}
void GenericVRPNServer::changeButtonState(std::string name, int index, int state)
{
_buttonServer[name].server->set_button(index, state);
_buttonServer[name].cache[index] = state;
}
int GenericVRPNServer::getButtonState(std::string name, int index)
{
return _buttonServer[name].cache[index];
}
void GenericVRPNServer::addAnalog(std::string name, int channelCount)
{
AnalogServer serverObject;
serverObject.server = new vrpn_Analog_Server(name.c_str(), _connection, channelCount);
serverObject.channelCount = channelCount;
_analogServer.insert(std::pair<std::string, AnalogServer>(name, serverObject));
}
void GenericVRPNServer::changeAnalogState(std::string name, ...)
{
double* channels = _analogServer[name].server->channels();
va_list list;
va_start(list, name);
for (int i = 0; i < _analogServer[name].channelCount; i++)
{
channels[i] = va_arg(list, double);
}
va_end(list);
_analogServer[name].server->report();
}
double* GenericVRPNServer::getAnalogChannels(std::string name)
{
return _analogServer[name].server->channels();
}
void GenericVRPNServer::reportAnalogChanges(std::string name)
{
_analogServer[name].server->report();
}
GenericVRPNServer.h
/**
* \file GenericVRPNServer.h
* \author Jozef Legény
*
* Copyright : Inria (2012)
* License : LGPLv2 -> AGPL3
*/
#ifndef __GenericVRPNServer__
#define __GenericVRPNServer__
#include <map>
#include <vector>
#include <string>
class vrpn_Connection;
class vrpn_Button_Server;
class vrpn_Analog_Server;
/**
* \class GenericVRPNServer
* \brief A class providing a very simple generic VRPN server capable of creating Analog and Button controls.
*/
class GenericVRPNServer
{
public:
struct ButtonServer {
vrpn_Button_Server* server;
int buttonCount;
std::vector<int> cache;
};
struct AnalogServer {
vrpn_Analog_Server* server;
int channelCount;
};
/// Public singleton factory
static GenericVRPNServer* getInstance(int port);
static void deleteInstance(void);
/// Public destructor
~GenericVRPNServer();
/// The loop() method has to be called periodically in order for vrpn to work
void loop();
/** Creates a new button object within the VRPN server
* \param name name of the vrpn peripheral
* \param buttonCount number of virtual buttons in the peripeheral
*/
void addButton(std::string name, int buttonCount);
/** Change the button state of a button inside a created VRPN peripheral
* \param name name of the vrpn peripheral containing the button
* \param index index of the button (beginning by 0)
* \param state new state of the button 0 = off, 1 = on
*/
void changeButtonState(std::string name, int index, int state);
/** Get the state of a button
* \param name name of the vrpn peripheral containing the button
* \param index index of the button (beginning by 0)
* \return the state of the button
*/
int getButtonState(std::string name, int index);
/** Creates a new analog object within the VRPN server
* \param name name of the vrpn peripheral
* \param channelCount number of channels in the peripeheral
*/
void addAnalog(std::string name, int channelCount);
/** Change the state of channels of an analog VRPN peripheral
* \param name name of the vrpn peripheral containing the analog control
* \param ellipsis list of the values (double)
*/
void changeAnalogState(std::string name, ...);
/** Gets a pointer to the channel array
* \param name name of the vrpn peripheral containing the analog control
* \return pointer to the array containing the channels
*/
double* getAnalogChannels(std::string name);
/** Marks the selected analog server channels as modified so the values are sent in the next loop
* \param name name of the vrpn peripheral containing the analog control
*/
void reportAnalogChanges(std::string name);
private:
static GenericVRPNServer* serverInstance;
GenericVRPNServer(int port);
vrpn_Connection* _connection;
std::map<std::string, ButtonServer> _buttonServer;
std::map<std::string, AnalogServer> _analogServer;
};
#endif // __GenericVRPNServer__
VRPN Client官方案例代码
#include<iostream>
#include<vrpn_Button.h>
#include<vrpn_Analog.h>
void VRPN_CALLBACK vrpn_button_callback(void* user_data, vrpn_BUTTONCB button)
{
std::cout << "Button ID : " << button.button << " / Button State : " << button.state << std::endl;
//if (button.button == 1)
//{
// std::cout << "Quit requested by button press" << std::endl;
// *(bool*)user_data = false;
//}
}
void VRPN_CALLBACK vrpn_analog_callback(void* user_data, vrpn_ANALOGCB analog)
{
for (int i = 0; i < analog.num_channel; i++)
{
std::cout << "Analog Channel : " << i << " / Analog Value : " << analog.channel[i] << std::endl;
}
}
int main(int argc, char** argv) {
if (argc != 1 && argc != 3) {
std::cout << "Usage:\n\n" << argv[0] << " [buttonDevice] [analogDevice]\n";
return 1;
}
//const char* buttonDevice = "openvibe_vrpn_button@localhost";
const char* buttonDevice = "openvibe_vrpn_button@localhost";
const char* analogDevice = "openvibe_vrpn_analog@localhost";
if (argc == 3) {
buttonDevice = argv[1];
analogDevice = argv[2];
}
std::cout << "Polling these VRPN devices\n Button: " << buttonDevice << "\n Analog: " << analogDevice << "\n";
bool running = true;
//VRPN Btoon Obj
vrpn_Button_Remote* VRPNButton;
VRPNButton = new vrpn_Button_Remote(buttonDevice);
VRPNButton->register_change_handler(&running, vrpn_button_callback);
//VRPN Analog Obj
vrpn_Analog_Remote* VRPNAnalog;
VRPNAnalog = new vrpn_Analog_Remote(analogDevice);
VRPNAnalog->register_change_handler(NULL, vrpn_analog_callback);
while (running)
{
VRPNButton->mainloop();
}
VRPNAnalog->unregister_change_handler(NULL, vrpn_analog_callback);
VRPNButton->unregister_change_handler(&running, vrpn_button_callback);
delete VRPNButton;
delete VRPNAnalog;
return 0;
}