方案设计
基于opencv图像识别的粒子交互
主题
编写一个“绘画系统”,提供一系列绘画材料(例如画笔/颜料/滤镜)给用户操作,以创作出动态/交互的绘画作品。这个绘画系统是对“绘画”的概念的扩展,但仍然体现出与传统绘画系统的相似性。
材料:电脑的代码,键盘,摄像头,显示器
作画者:人挥动手臂;
交互方式:摄像头识别;
作品:具有时效性,本来的想法就是做一个交互装置,后来想到,粒子系统和沙子会不会有某些相似性呢,也许这个系统可以模拟数字沙画;
图像中的粒子是一个个的小草莓
白色圆是识别的中心目标点
可以看见识别效果还是很一般的,不过这只是一个测试版,主要功能就是这个样子了
主要就是两个功能的组成:识别和粒子
识别一开始是希望用kinect去做的,后来学院的没借上,就只能自己搞摄像头的手部识别了
手部识别:
int ofApp::handloc()
{
int count = 0;
VideoCapture capture;
capture.open(0);
Mat fram, prefram, result, fg;
int framNum = 0;
while (capture.isOpened())
{
capture >> fram;
fram.convertTo(fram, CV_32FC3);
normalize(fram, fram, 1, 0, CV_MINMAX);
imshow("src", fram);
if (framNum == 0)
{
intial(fram);
}
else if (framNum<30)
{
++count;
accbackgound(fram, prefram);
}
else if (framNum == 30)
backgound(count);
else
{
foregound(fram, prefram);
skin(fram);
}
fram.copyTo(prefram);
framNum++;
char key = (char)waitKey(2);
switch (key)
{
case 27:
return 0;
break;
}
}
}
void ofApp::intial(Mat src)
{
src.copyTo(bg);
}
void ofApp::accbackgound(Mat src, Mat pre)
{
Mat temp;
accumulate(src, bg);
absdiff(src, pre, temp);
if (Th.data == NULL)
{
temp.copyTo(Th);
}
else
accumulate(temp, Th);
}
void ofApp::backgound(int count)
{
bg = bg / count;
Th = Th / count;
normalize(bg, bg, 1, 0, CV_MINMAX);
/*imshow("backgound", bg);*/
Mat t[3];
Mat b[3];
split(Th, t);
split(bg, b);
bglow0 = b[0] - t[0] * low;
bglow1 = b[1] - t[1] * low;
bglow2 = b[2] - t[2] * low;
bghigh0 = b[0] + t[0] * high;
bghigh1 = b[1] + t[1] * high;
bghigh2 = b[2] + t[2] * high;
cout << "Start Traclking" << endl;
}
void ofApp::foregound(Mat src, Mat pre)
{
Mat temp0, temp1, temp2;
Mat framNow[3];
Mat frampre[3];
framNow[0].setTo(Scalar(0, 0, 0));
framNow[1].setTo(Scalar(0, 0, 0));
framNow[2].setTo(Scalar(0, 0, 0));
temp0.setTo(Scalar(0, 0, 0));
temp1.setTo(Scalar(0, 0, 0));
temp2.setTo(Scalar(0, 0, 0));
split(src, framNow);
inRange(framNow[0], bglow0, bghigh0, temp0);
inRange(framNow[1], bglow1, bghigh1, temp1);
inRange(framNow[2], bglow2, bghigh2, temp2);
bitwise_or(temp0, temp1, temp0);
bitwise_or(temp0, temp2, temp0);
bitwise_not(temp0, temp0);
imshow("Show", temp0);
temp0.copyTo(mask0);
}
void ofApp::skin(Mat src)
{
src.convertTo(src, CV_8UC3, 255);
Mat yuv, dst;
cvtColor(src, yuv, CV_BGR2YCrCb);
Mat dstTemp1(src.rows, src.cols, CV_8UC1);
Mat dstTemp2(src.rows, src.cols, CV_8UC1);
// 对YUV空间进行量化,得到2值图像,亮的部分为手的形状
inRange(yuv, Scalar(0, 133, 0), Scalar(256, 173, 256), dstTemp1);
inRange(yuv, Scalar(0, 0, 77), Scalar(256, 256, 127), dstTemp2);
bitwise_and(dstTemp1, dstTemp2, mask);
dst.setTo(Scalar::all(0));
bitwise_and(mask, mask0, mask);
src.copyTo(dst, mask);
vector< vector
vector< vector
vector< Vec4i > hierarchy; // 轮廓的结构信息
vector< Point > hull; // 凸包络的点集
contours.clear();
hierarchy.clear();
filterContours.clear();
// 得到手的轮廓
findContours(mask, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
// 去除伪轮廓
for (size_t i = 0; i < contours.size(); i++)
{
if (fabs(contourArea(Mat(contours[i]))) > 1000 && fabs(arcLength(Mat(contours[i]), true))<2000) //判断手进入区域的阈值
{
filterContours.push_back(contours[i]);
}
}
cv::Rect rect;
float radius;
if (!filterContours.empty()) {
minEnclosingCircle(filterContours[0], handcenter, radius);
//cout << handcenter.x << " " << handcenter.y << endl;
}
// 画轮廓
drawContours(src, filterContours, -1, Scalar(0, 0, 255), 2); //8, hierarchy);
imshow("traclking", src);
}
通过图像的二值化和手部颜色的识别可以大致识别皮肤的位置,然后通过对识别区域大小的过滤可以大致识别出手部的位子
思路是这样,识别出来的效果还是一般,很容易把脸给识别出来,同时对人物的远近没有自适应性,所以只能在摄像头前半米的位子识别
总之这样就获得了大致手部位子
粒子系统:
用openframework带的例子
#include "demoParticle.h"
#include "ofApp.h"
//------------------------------------------------------------------
demoParticle::demoParticle(){
attractPoints = NULL;
testphoto.load("C:\\Users\\Yon\\Desktop\\testphoto.png");
}
//------------------------------------------------------------------
void demoParticle::setMode(particleMode newMode){
mode = newMode;
}
//------------------------------------------------------------------
void demoParticle::setAttractPoints( vector
attractPoints = attract;
}
//------------------------------------------------------------------
void demoParticle::reset(){
//为每个粒子设置不同的属性
uniqueVal = ofRandom(-10000, 10000);
pos.x = ofRandomWidth();
pos.y = ofRandomHeight();
vel.x = ofRandom(-3.9, 3.9);
vel.y = ofRandom(-3.9, 3.9);
frc = ofPoint(0,0,0);
scale = ofRandom(0.5, 1.0);
if( mode == PARTICLE_MODE_NOISE ){
drag = ofRandom(0.97, 0.99);
vel.y = fabs(vel.y) * 3.0; //下降
}else{
drag = ofRandom(0.95, 0.998);
}
}
//------------------------------------------------------------------
void demoParticle::update(){
//不同的粒子模式
if( mode == PARTICLE_MODE_ATTRACT ){
ofPoint attractPt(handPoint.x, handPoint.y);
frc = attractPt-pos; // 跟随鼠标
frc.normalize(); //通过标准化,我们忽略了粒子离吸引点有多近
vel *= drag; //apply drag
vel += frc * 0.6; //apply force
}
else if( mode == PARTICLE_MODE_REPEL ){
ofPoint attractPt(handPoint.x, handPoint.y);
frc = attractPt-pos;
//远离鼠标
float dist = frc.length();
frc.normalize();
vel *= drag;
if( dist < 150 ){
vel += -frc * 0.6; //frc取负值即可排斥点
}else{
frc.x = ofSignedNoise(uniqueVal, pos.y * 0.001, ofGetElapsedTimef()*0.02);
frc.y = ofSignedNoise(uniqueVal, pos.x * 0.001, ofGetElapsedTimef()*0.02);
vel += frc * 0.004;
}
}
else if( mode == PARTICLE_MODE_NOISE ){
//模拟下坠粒子
float fakeWindX = ofSignedNoise(pos.x * 0.003, pos.y * 0.006, ofGetElapsedTimef() * 0.6);
frc.x = fakeWindX * 0.25 + ofSignedNoise(uniqueVal, pos.y * 0.04) * 0.6;
frc.y = ofSignedNoise(uniqueVal, pos.x * 0.006, ofGetElapsedTimef()*0.2) * 0.09 + 0.18;
vel *= drag;
vel += frc * 0.4;
//底部刷新
if( pos.y + vel.y > ofGetHeight() ){
pos.y -= ofGetHeight();
}
}
else if( mode == PARTICLE_MODE_NEAREST_POINTS ){
if( attractPoints ){
//找到最近点
ofPoint closestPt;
int closest = -1;
float closestDist = 9999999;
for(unsigned int i = 0; i < attractPoints->size(); i++){
float lenSq = ( attractPoints->at(i)-pos ).lengthSquared();
if( lenSq < closestDist ){
closestDist = lenSq;
closest = i;
}
}
//附加指向点的力
if( closest != -1 ){
closestPt = attractPoints->at(closest);
float dist = sqrt(closestDist);
//使力与距离成比例。
frc = closestPt - pos;
vel *= drag;
//f控制受力距离
if( dist < 300 && dist > 40 && !ofGetKeyPressed('f') ){
vel += frc * 0.003;
}else{
frc.x = ofSignedNoise(uniqueVal, pos.y * 0.01, ofGetElapsedTimef()*0.2);
frc.y = ofSignedNoise(uniqueVal, pos.x * 0.01, ofGetElapsedTimef()*0.2);
vel += frc * 0.4;
}
}
}
}
//刷新位子
pos += vel;
if( pos.x > ofGetWidth() ){
pos.x = ofGetWidth();
vel.x *= -1.0;
}else if( pos.x < 0 ){
pos.x = 0;
vel.x *= -1.0;
}
if( pos.y > ofGetHeight() ){
pos.y = ofGetHeight();
vel.y *= -1.0;
}
else if( pos.y < 0 ){
pos.y = 0;
vel.y *= -1.0;
}
}
//------------------------------------------------------------------
void demoParticle::draw(){
testphoto.draw(pos.x- scale * 50, pos.y - scale * 50, scale*100, scale*100);
ofSetColor(250, 250, 250);
ofDrawCircle(handPoint.x, handPoint.y, 30);
if( mode == PARTICLE_MODE_ATTRACT ){
ofSetColor(255, 63, 180);
}
else if( mode == PARTICLE_MODE_REPEL ){
ofSetColor(208, 255, 63);
}
else if( mode == PARTICLE_MODE_NOISE ){
ofSetColor(99, 63, 255);
}
else if( mode == PARTICLE_MODE_NEAREST_POINTS ){
ofSetColor(103, 160, 237);
}
ofDrawCircle(pos.x, pos.y, scale * 4.0);
}
void demoParticle::drawflower(int x,int y,int radious)
{
}
课后感想:
感觉中间的一些难点还是在将两个系统搭建在一起的时候,因为两个系统都是持续触发的,所以一起运行的时候总有些错误,不过后来好在都解决了。
手部识别的判定还是很糟,一方面是我这个电脑的摄像头不怎么样,另一方面,关于形态学识别的部分还是不够完好,如果用kinect的话就不用自己写手部追踪的部分了
粒子系统虽然用的范例,但还是有我自己的创新,如导入图片和对手部点的读取判断,受限于电脑性能,处理的粒子不多,而且也很卡,程序运行在x64的平台上就不流畅,不晓得是什么原因。
感觉openframework实现的效果还蛮不错的,就是相关的教程资料很少,只有官网上有,再就是看example里的代码,估计以后还会继续玩openframework,希望能做出更棒的作品