嘿!您似乎在 United States,您想使用我们的 English 网站吗?
Switch to English site
Skip to main content
shopping_basket 购物车 0
登录

Processing数位互动接口开发软件应用- Processing 影像应用与 openCV 人脸追踪

 

作者

曾吉弘

难度

普通

材料表

1.    Arduino UNO

  1. 伺服机,2颗(用来控制云台的 X、Y方向移动)
  2. 面包板,1片
  3. 线材,1批

 

1040_ad8f76febdc2ffb715793deb73a570da03bb6394.png

本文要介绍如何在 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平台设计

0130_c35a4c92a22839aea2df6864f86add362b053798.jpg

0227_fcd1a513635cbd28c2cde838fa94eb10daad879d.jpg0325_29ab878be8e1161997ce0f9dcae3896c7d6f9597.jpg

 

Processing设定

1.请开启 Processing 官方网站的下载页面,根据您所使用的硬件平台下载对应的版本,如果还没下载请参考上一篇(连结)。

    开启 Processing IDE,点选 Sketch → Import Library → Add Library,开启 Contribution Manager 窗口,分别安装好以下两个函式库:,如下图:

0433_43b6c8bccd6d50934c737e75203b5516dca4d9e9.png0531_6344ede34b760d707ea693aaa537ca1b4a6d814b.png

 

2.完成之后 File → Examples 看到相关范例,都可以直接玩玩看喔

0630_e16a984d473ffa49b6e2920e92646d88464f98ba.png

 

   说真的,Processing的 Pimage 与 video 套件已经能做出相当不错的效果,先来看看两个范例,关键就是在于您想要怎样的效果还有自身创意啰!

马赛克效果

 

     本范例来自 Daniel shiffman 的 Learning Processing 一书范例(http://learningprocessing.com/examples/chp16/example-16-08-BrightnessMirror)。会把画面切成多个方块,每个方块的大小会根据该区域的亮度来决定。亮度愈亮,方块愈大,反之则愈小。先说喔,这个范例没有用到 opencv,只是单纯的马赛克效果而已。先看一下执行效果:

插入影片 01.mp4(RS影音平台連結)

0733_b23b408718453cd4c30b6a4a4f9ff966b2d5835b.png

 

重要指令说明如下:

  • 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 之间的差异,并把画面中移动(差异大于一定程度)的地方显示出来。您后续可以调整差异下限(请看以下说明) 或前后景颜色来产生更多有趣的效果。先看一下执行效果:

0828_ae88125575b9cd678e6b9e9c23c67f145718e696.png

插入影片 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 程序文件的同一个目录下放一张您想要进行边缘侦测的图片,再执行程序就可以看到如下的画面:

0926_88b1071fe275da915f902c1e981b5b0fb84297f6.png

重要指令说明如下:

  • 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分类算法,所以当您左右转动脸或是把脸倾斜一个角度之后,系统就无法辨识你的脸了

1041_ad8f76febdc2ffb715793deb73a570da03bb6394.png1156_6643ea42818b473f008484ffc00848a28661be28.png

 

重要指令说明如下:

  • 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

 

CAVEDU Education is devoted into robotics education and maker movement since 2008, and is intensively active in teaching fundamental knowledge and skills. We had published many books for readers in all ages, topics including Deep Learning, edge computing, App Inventor, IoT and robotics. Please check CAVEDU's website for more information: http://www.cavedu.com, http://www.appinventor.tw
DesignSpark Electrical Logolinkedin