Processing数位互动接口开发软件应用- Processing 影像应用与 openCV 人脸追踪
作者 |
曾吉弘 |
难度 |
普通 |
材料表 |
1. Arduino UNO
|
本文要介绍如何在 Processing IDE 中汇入OpenCV视觉函式库,目标是做出一个可以上下左右追踪脸孔的相机云台。
在 Processing 中使用这比建置完整的OpenCV环境做法简单多了,但是功能相对也会比完整的 openCV 环境来得少,就看您个人的需求啰。Processing opencv 相较于最新的 opencv 4.0 当然是旧了很多,但 Processing 的程序代码都很简洁,很容易做出漂亮的接口,要结合Arduino也很简单,绝对值得您试试看喔。
更多信息请直接惨考 Processing openCV 函式库的 github:https://github.com/atduskgreg/opencv-processing
硬件
Arduino 与 Processing 的联机设定请参考上一篇的教学,所需的硬件如材料表:
请将#1伺服机的讯号脚位接到Arduino的#9脚位,用于控制PTZ平台的水平方向移动(Pan)
请将#2伺服机的讯号脚位接到Arduino的#11脚位,用于控制PTZ平台的垂直方向移动(Tilt)。其余分别接电接地完成即可。最后在顶端接上您的 webcam,但注意平台会转动,因此所有线材都要预留一定的长度。您可由 thingiverse 这样的网站找到更多的PTZ平台设计
Processing设定
1.请开启 Processing 官方网站的下载页面,根据您所使用的硬件平台下载对应的版本,如果还没下载请参考上一篇(连结)。
开启 Processing IDE,点选 Sketch → Import Library → Add Library,开启 Contribution Manager 窗口,分别安装好以下两个函式库:,如下图:
2.完成之后 File → Examples 看到相关范例,都可以直接玩玩看喔
说真的,Processing的 Pimage 与 video 套件已经能做出相当不错的效果,先来看看两个范例,关键就是在于您想要怎样的效果还有自身创意啰!
马赛克效果
本范例来自 Daniel shiffman 的 Learning Processing 一书范例(http://learningprocessing.com/examples/chp16/example-16-08-BrightnessMirror)。会把画面切成多个方块,每个方块的大小会根据该区域的亮度来决定。亮度愈亮,方块愈大,反之则愈小。先说喔,这个范例没有用到 opencv,只是单纯的马赛克效果而已。先看一下执行效果:
插入影片 01.mp4(RS影音平台連結)
重要指令说明如下:
- int videoScale = 10; → 这个变量值会决定画面的马赛克程度
- cols = width / videoScale; → 根据 videoScale变量值来切分画面的行数
- rows = height / videoScale; → 根据 videoScale变量值来切分画面的列数
- loadPixels(); → 载入所有像素以便后续处理
- for (int i = 0; i < cols; i++) { for (int j = 0; j < rows; j++) { → 扫过画面所有的行列,就是整个画面
- int loc = (video.width - i - 1) + j * video.width; → 所有栏反向排列,来将影像镜像
- color c = video.pixels[loc]; → 取得指定位置像素之颜色值,每个方块的大小与其中所有像素的亮度成比例。愈亮方块愈大,反之则愈小
- float sz = (brightness(c)/255) * videoScale; → 使用 brightness() 语法取得 c 点之亮度,再乘上 videoScale 变量值来算出最终的方块宽度与高度
完整程序代码如下,为避免占版面已删除批注,请自行开启上述连结来看批注:
// Each pixel from the video source is drawn as
// a rectangle with size based on brightness.
import processing.video.*;
int videoScale = 10;
int cols, rows;
Capture video;
void setup() {
size(640, 480);
cols = width / videoScale;
rows = height / videoScale;
video = new Capture(this, cols, rows);
video.start();
}
void captureEvent(Capture video) {
video.read();
}
void draw() {
background(0);
video.loadPixels();
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
int x = i*videoScale;
int y = j*videoScale;
int loc = (video.width - i - 1) + j * video.width;
color c = video.pixels[loc];
float sz = (brightness(c)/255) * videoScale;
rectMode(CENTER);
fill(255);
noStroke();
rect(x + videoScale/2, y + videoScale/2, sz, sz);
}
}
}
动作侦测
本范例来自 Daniel shiffman 的 Learning Processing 一书范例(http://learningprocessing.com/examples/chp16/example-16-13-MotionPixels),会比较两个 frame 之间的差异,并把画面中移动(差异大于一定程度)的地方显示出来。您后续可以调整差异下限(请看以下说明) 或前后景颜色来产生更多有趣的效果。先看一下执行效果:
插入影片 02.mp4(RS影音平台連結)
重要指令说明如下:
- float threshold = 50; → 像素被判断为移动像素的下限,您可修改这个值来调整程序的敏感度
- copy(video, 0, 0, video.width, video.height, 0, 0, video.width, video.height); 把前一个 frame 存起来侦测动作。在读取新的 frame 之前,一定要先把原本的 frame 存起来才能进行比较
- loadPixels(); prevFrame.loadPixels(); → 加载现在与前一个 frame 的所有像素
- for (int x = 0; x < video.width; x ++ ) { for (int y = 0; y < video.height; y ++ ) { → 同上一个范例,用两个 for loop 来刷过所有像素来进行比较
- int loc = x + y*video.width; → 计算 1D 像素位置
- color current = video.pixels[loc]; → 计算这个像素的颜色,请参考 Processing 的 color() 函式
- color previous = prevFrame.pixels[loc]; → 计算上一张 frame中,同一个位置之像素颜色
- float diff = dist(r1, g1, b1, r2, g2, b2); → 比较这两个点的颜色差,使用 dist() 距离函数来计算两个 frame 中同一点的颜色差,float r1, g1, b1 为 prevFame 的该点像素颜色, float r2, g2, b2 为 currentFame 的该点像素颜色
- if (diff > threshold) { → 如果该像素的颜色差大于所设定的值(50)
- pixels[loc] = color(0); → 代表发生动作,该点设为黑色
- else { pixels[loc] = color(255); → 反之设为白色
完整程序代码如下,为避免占版面已删除批注,请自行开启上述连结来看批注:
import processing.video.*;
Capture video;
PImage prevFrame;
float threshold = 50;
void setup() {
size(320, 240);
video = new Capture(this, width, height, 30);
video.start();
prevFrame = createImage(video.width, video.height, RGB);
}
void captureEvent(Capture video) {
prevFrame.copy(video, 0, 0, video.width, video.height, 0, 0, video.width, video.height);
prevFrame.updatePixels();
video.read();
}
void draw() {
loadPixels();
video.loadPixels();
prevFrame.loadPixels();
for (int x = 0; x < video.width; x ++ ) {
for (int y = 0; y < video.height; y ++ ) {
int loc = x + y*video.width;
color current = video.pixels[loc];
color previous = prevFrame.pixels[loc];
float r1 = red(current);
float g1 = green(current);
float b1 = blue(current);
float r2 = red(previous);
float g2 = green(previous);
float b2 = blue(previous);
float diff = dist(r1, g1, b1, r2, g2, b2);
if (diff > threshold) {
pixels[loc] = color(0);
} else {
pixels[loc] = color(255);
}
}
}
updatePixels();
}
边缘侦测
好了,那来看看 openCV 的范例吧。 在诸多图像处理技术中,边缘侦测是相当重要的算法之一,因为先找到边缘才能进一步把一个物体的轮廓抓出来,并根据这个轮廓在画面中的位置来算出它的移动方向、距离等等。
这边用到的 canny, scharr sobel 分别代表不同的边缘侦测算法 请在 .pde 程序文件的同一个目录下放一张您想要进行边缘侦测的图片,再执行程序就可以看到如下的画面:
重要指令说明如下:
- src = loadImage("toy.jpg"); → 加载图片
- findCannyEdges(20,75); → 使用 Canny 边缘侦测算法来侦测边缘,(20, 75) 代表上下限,详情请参考 http://atduskgreg.github.io/opencv-processing/reference/gab/opencv/OpenCV.html
- canny = opencv.getSnapshot(); → 取得辨识结果快照
- image(src, 0, 0);
- image(canny, src.width, 0);
- image(scharr, 0, src.height);
- image(sobel, src.width, src.height); -> 这四段把不同的边缘侦测算法结果显示在同一个 image 上,请仔细看看如何指定其位置
完整程序代码如下,为避免占版面已删除批注,请自行开启上述连结来看批注
import gab.opencv.*;
OpenCV opencv;
PImage src, canny, scharr, sobel;
void setup() {
src = loadImage("toy.jpg");
size(1080, 720);
opencv = new OpenCV(this, src);
opencv.findCannyEdges(20,75);
canny = opencv.getSnapshot();
opencv.loadImage(src);
opencv.findScharrEdges(OpenCV.HORIZONTAL);
scharr = opencv.getSnapshot();
opencv.loadImage(src);
opencv.findSobelEdges(1,0);
sobel = opencv.getSnapshot();
}
void draw() {
pushMatrix();
scale(0.5);
image(src, 0, 0);
image(canny, src.width, 0);
image(scharr, 0, src.height);
image(sobel, src.width, src.height);
popMatrix();
text("Source", 10, 25);
text("Canny", src.width/2 + 10, 25);
text("Scharr", 10, src.height/2 + 25);
text("Sobel", src.width/2 + 10, src.height/2 + 25);
}
人脸追踪
开启范例程序:LiveCamTest,可以直接追踪脸孔啰,以下程序代码已经加入 Arduino 相关句柄,但您需要根据实际情况来调整伺服机的角度,不然有可能会让它转过头而坏掉喔。
插入影片 03.mp4(RS影音平台連結)
执行画面如下,可以看到画面会把侦测到的脸孔标出来,也会在console中显示侦测到的脸孔张数与坐标。由于在此的脸孔辨识是采用Haar分类算法,所以当您左右转动脸或是把脸倾斜一个角度之后,系统就无法辨识你的脸了
重要指令说明如下:
- loadCascade(OpenCV.CASCADE_FRONTALFACE); → 使用代表在此的脸孔追踪还是用到了Haar分类算法,点击链接可以看到关于本分类法的数理基础。
- loadImage(video); → 载入实时画面
- Rectangle[] faces = opencv.detect(); //侦测是否有脸
- for (int i = 0; i < faces.length; i++) { //根据脸孔数目来跑循环
- println(faces[i].x + "," + faces[i].y); //显示脸的坐标
- rect(faces[i].x, faces[i].y, faces[i].width, faces[i].height); //画长方形把脸框出来
- center_x = faces[i].x + (faces[i].width/2); //起始点x + 0.5*宽
- center_y = faces[i].y + (faces[i].height /2); //起始点y + 0.5*高
- ellipse(center_x, center_y, 10, 10); → 在侦测到的脸部中心画一个圆
- face_x = faces[0].x + (faces[i].width/2); → 取得第一张脸的中心x坐标
- face_y = faces[0].y + (faces[i].height/2); → 取得第一张脸的中心y坐标
- servoWrite(9, int(map(center_x, 100, 270, 0, 180))); → 根据脸孔
- servoWrite(11, int(map(center_y, 80, 250, 0, 180)));
import gab.opencv.*;
import processing.video.*;
import java.awt.*;
import processing.serial.*;
import cc.arduino.*;
int center_x, center_y;
Arduino arduino;
Capture video;
OpenCV opencv;
void setup() {
size(640, 480);
video = new Capture(this, 640/2, 480/2);
opencv = new OpenCV(this, 640/2, 480/2);
opencv.loadCascade(OpenCV.CASCADE_FRONTALFACE);
println(Arduino.list());
arduino = new Arduino(this, Arduino.list()[0], 57600);
//0代表第一个COM装置,请根据实际状况修改
arduino.pinMode(9, Arduino.SERVO); //#1 servo 水平
arduino.pinMode(11, Arduino.SERVO); //#2 servo 垂直
video.start();
}
void draw() {
scale(2);
opencv.loadImage(video);
image(video, 0, 0 );
noFill();
stroke(0, 255, 0);
strokeWeight(2);
Rectangle[] faces = opencv.detect(); //侦测是否有脸
println(faces.length); //侦测到几张脸
for (int i = 0; i < faces.length; i++) { //根据脸孔数目来跑循环
println(faces[i].x + "," + faces[i].y); //显示脸的坐标
rect(faces[i].x, faces[i].y, faces[i].width, faces[i].height); //画长方形把脸框出来
center_x = faces[i].x + (faces[i].width/2); //起始点x + 0.5*宽
center_y = faces[i].y + (faces[i].height /2); //起始点y + 0.5*高
print(center_x);
print(", ");
println(center_y);
ellipse(center_x, center_y, 10, 10);
face_x = faces[0].x + (faces[i].width/2);
face_y = faces[0].y + (faces[i].height/2);
arduino.servoWrite(9, int(map(face_x, 100, 270, 0, 180)));
arduino.servoWrite(11, int(map(face_y, 80, 250, 0, 180)));
}
delay(100);
}
void captureEvent(Capture c) {
c.read();
}
执行
由 Processing console 讯息可以看到以下讯息:
OpenCV for Processing 0.5.4 by Greg Borenstein http://gregborenstein.com
Using Java OpenCV 2.4.5.0
Load cascade from: C:/Users/user/Documents/Processing/libraries/opencv_processing/library/cascade-files/haarcascade_frontalface_alt.xml
Cascade loaded: haarcascade_frontalface_alt.xml
评论