互动媒体——基于opencv图像识别的粒子绘画

方案设计

基于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 > contours; // 轮廓

vector< vector > filterContours; // 筛选后的轮廓

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 * attract ){

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,希望能做出更棒的作品

导入的草莓png

你可能感兴趣的:(互动媒体——基于opencv图像识别的粒子绘画)