【问题标题】:How to determine the distance between upper lip and lower lip by using webcam in Processing?如何在处理中使用网络摄像头确定上唇和下唇之间的距离?
【发布时间】:2016-05-08 22:58:54
【问题描述】:

我应该从哪里开始?我可以看到大量使用 Python、Java 脚本的人脸识别和分析,但 Processing 呢?

我想通过网络摄像头使用上下唇之间的最高点和最低点之间的 2 个点来确定距离,以便在进一步的项目中使用它。

任何帮助将不胜感激

【问题讨论】:

  • 你能分享你的工作吗?
  • 一个瞬间的想法是你需要测量绝对距离,所以你还必须以某种方式计算图像的深度(人与相机的距离)。在您的情况下,获得正确的深度将是最具挑战性的部分,而不是寻找嘴唇。
  • 通过处理,您也可以完成这项工作,但在核心,您将主要使用库(主要是 OpenCV)。所以无论你使用python、java还是C++都没有关系。如果您对处理感到满意,那么请务必继续。 PS:-如果有帮助,请点赞。
  • 我的项目是尝试通过面部机器人复制用户嘴巴最简单的动作,你有什么库推荐吗?我可以看到一些换脸应用程序可以给我一些想法,例如 matthewearl.github.io/2015/07/28/switching-eds-with-python 实际上,确定确切的距离值并不重要,因为我需要它来确定张嘴和闭嘴之间的状态。

标签: opencv image-processing processing video-processing face-detection


【解决方案1】:

如果您想单独在处理中执行此操作,您可以使用Greg Borenstein's OpenCV for Processing library

  1. 您可以从Face Detection example 开始
  2. 检测到人脸后,您可以使用OpenCV.CASCADE_MOUTH 在人脸矩形内检测嘴巴。
  3. 一旦检测到嘴巴,也许您就可以使用嘴巴边界框高度。有关更多详细信息,您可以使用 OpenCV 来设置该矩形的阈值。希望张开的嘴巴能很好地与皮肤的其余部分分开。 Finding contours 应该为您提供可以使用的点列表。

对于更准确的信息,您可以使用 Jason Saragih 的 CLM FaceTracker, which is available as an OpenFrameworks addon。 OpenFrameworks 与 Processing 有相似之处。如果您在处理中确实需要这种精度,您可以在后台运行FaceOSC,并使用oscP5 读取处理中的嘴部坐标

更新

对于第一个选项,使用 HAAR 级联分类器,结果发现有几个问题:

  1. OpenCV 处理库可以加载一个级联,第二个实例将覆盖第一个。
  2. OpenCV.CASCADE_MOUTH 似乎更适合闭嘴,但不太适合张嘴

要跳过第一个问题,您可以直接使用OpenCV Java API,绕过 OpenCV 处理进行多级联检测。

有几个参数可以帮助检测,例如事先知道嘴巴的边界框作为提示传递给分类器。 我使用笔记本电脑上的网络摄像头进行了基本测试,并在不同距离测量了面部和嘴巴的边界框。这是一个例子:

import gab.opencv.*;
import org.opencv.core.*;
import org.opencv.objdetect.*;

import processing.video.*;

Capture video;
OpenCV opencv;

CascadeClassifier faceDetector,mouthDetector;
MatOfRect faceDetections,mouthDetections;

//cascade detections parameters - explanations from Mastering OpenCV with Practical Computer Vision Projects
int flags = Objdetect.CASCADE_FIND_BIGGEST_OBJECT;
// Smallest object size.
Size minFeatureSizeFace = new Size(50,60);
Size maxFeatureSizeFace = new Size(125,150);
Size minFeatureSizeMouth = new Size(30,10);
Size maxFeatureSizeMouth = new Size(120,60);

// How detailed should the search be. Must be larger than 1.0.
float searchScaleFactor = 1.1f;
// How much the detections should be filtered out. This should depend on how bad false detections are to your system.
// minNeighbors=2 means lots of good+bad detections, and minNeighbors=6 means only good detections are given but some are missed.
int minNeighbors = 4;
//laptop webcam face rectangle
//far, small scale, ~50,60px
//typing distance, ~83,91px
//really close, ~125,150
//laptop webcam mouth rectangle
//far, small scale, ~30,10
//typing distance, ~50,25px
//really close, ~120,60

int mouthHeightHistory = 30;
int[] mouthHeights = new int[mouthHeightHistory]; 

void setup() {
  opencv = new OpenCV(this,320,240);
  size(opencv.width, opencv.height);
  noFill();
  frameRate(30);

  video = new Capture(this,width,height);
  video.start();

  faceDetector = new CascadeClassifier(dataPath("haarcascade_frontalface_alt2.xml"));
  mouthDetector = new CascadeClassifier(dataPath("haarcascade_mcs_mouth.xml"));

}

void draw() {
  //feed cam image to OpenCV, it turns it to grayscale
  opencv.loadImage(video);
  opencv.equalizeHistogram();
  image(opencv.getOutput(), 0, 0 );

  //detect face using raw Java OpenCV API
  Mat equalizedImg = opencv.getGray();
  faceDetections = new MatOfRect();
  faceDetector.detectMultiScale(equalizedImg, faceDetections, searchScaleFactor, minNeighbors, flags, minFeatureSizeFace, maxFeatureSizeFace);
  Rect[] faceDetectionResults = faceDetections.toArray();
  int faces = faceDetectionResults.length;
  text("detected faces: "+faces,5,15);
  if(faces >= 1){
    Rect face = faceDetectionResults[0];
    stroke(0,192,0);
    rect(face.x,face.y,face.width,face.height);
    //detect mouth - only within face rectangle, not the whole frame
    Rect faceLower = face.clone();
    faceLower.height = (int) (face.height * 0.65);
    faceLower.y = face.y + faceLower.height; 
    Mat faceROI = equalizedImg.submat(faceLower);
    //debug view of ROI
    PImage faceImg = createImage(faceLower.width,faceLower.height,RGB);
    opencv.toPImage(faceROI,faceImg);
    image(faceImg,width-faceImg.width,0);

    mouthDetections = new MatOfRect();
    mouthDetector.detectMultiScale(faceROI, mouthDetections, searchScaleFactor, minNeighbors, flags, minFeatureSizeMouth, maxFeatureSizeMouth);
    Rect[] mouthDetectionResults = mouthDetections.toArray();
    int mouths = mouthDetectionResults.length;
    text("detected mouths: "+mouths,5,25);
    if(mouths >= 1){
      Rect mouth = mouthDetectionResults[0];
      stroke(192,0,0);
      rect(faceLower.x + mouth.x,faceLower.y + mouth.y,mouth.width,mouth.height);
      text("mouth height:"+mouth.height+"~px",5,35);
      updateAndPlotMouthHistory(mouth.height);
    }
  }
}
void updateAndPlotMouthHistory(int newHeight){
  //shift older values by 1
  for(int i = mouthHeightHistory-1; i > 0; i--){
    mouthHeights[i] = mouthHeights[i-1];
  } 
  //add new value at the front
  mouthHeights[0] = newHeight;
  //plot
  float graphWidth = 100.0;
  float elementWidth = graphWidth / mouthHeightHistory;  
  for(int i = 0; i < mouthHeightHistory; i++){
    rect(elementWidth * i,45,elementWidth,mouthHeights[i]);
  }
}
void captureEvent(Capture c) {
  c.read();
}

一个非常重要需要注意:我已将级联 xml 文件从 OpenCV 处理库文件夹 (~/Documents/Processing/libraries/opencv_processing/library/cascade-files) 复制到草图的数据文件夹。我的草图是 OpenCVMouthOpen,所以文件夹结构是这样的:

OpenCVMouthOpen
├── OpenCVMouthOpen.pde
└── data
    ├── haarcascade_frontalface_alt.xml
    ├── haarcascade_frontalface_alt2.xml
    ├── haarcascade_frontalface_alt_tree.xml
    ├── haarcascade_frontalface_default.xml
    ├── haarcascade_mcs_mouth.xml
    └── lbpcascade_frontalface.xml

如果您不复制级联文件并按原样使用代码,则不会出现任何错误,但检测根本不起作用。如果你想检查,你可以做

println(faceDetector.empty())

setup()函数结束时,如果你得到false,则级联已加载,如果你得到true,则级联尚未加载。

您可能需要使用minFeatureSizemaxFeatureSize 值来设置面部和嘴巴。第二个问题,级联不能很好地检测张开的嘴是棘手的。可能有一个已经训练过的张开嘴的级联,但你需要找到它。否则,使用这种方法,您可能需要自己训练一个,这可能有点乏味。

不过,请注意,当检测到嘴巴时,左侧会绘制一个颠倒的图。在我的测试中,我注意到高度不是非常准确,但图表中有明显的变化。您可能无法获得稳定的嘴巴高度,但通过将当前高度值与之前的平均高度值进行比较,您应该会看到一些峰值(值从正值变为负值,反之亦然),这让您了解嘴巴张开/闭合的变化.

虽然在整个图像中搜索一张嘴而不是一张脸可能会有点慢且不太准确,但这是一个更简单的设置。如果您可以在项目中以较低的准确性和更多的误报逃脱,这可能会更简单:

import gab.opencv.*;
import java.awt.Rectangle;
import org.opencv.objdetect.Objdetect;
import processing.video.*;

Capture video;
OpenCV opencv;
Rectangle[] faces,mouths;

//cascade detections parameters - explanations from Mastering OpenCV with Practical Computer Vision Projects
int flags = Objdetect.CASCADE_FIND_BIGGEST_OBJECT;
// Smallest object size.
int minFeatureSize = 20;
int maxFeatureSize = 150;
// How detailed should the search be. Must be larger than 1.0.
float searchScaleFactor = 1.1f;
// How much the detections should be filtered out. This should depend on how bad false detections are to your system.
// minNeighbors=2 means lots of good+bad detections, and minNeighbors=6 means only good detections are given but some are missed.
int minNeighbors = 6;

void setup() {
  size(320, 240);
  noFill();
  stroke(0, 192, 0);
  strokeWeight(3);

  video = new Capture(this,width,height);
  video.start();

  opencv  = new OpenCV(this,320,240);
  opencv.loadCascade(OpenCV.CASCADE_MOUTH);
}

void draw() {
  //feed cam image to OpenCV, it turns it to grayscale
  opencv.loadImage(video);
  opencv.equalizeHistogram();
  image(opencv.getOutput(), 0, 0 );

  Rectangle[] mouths = opencv.detect(searchScaleFactor,minNeighbors,flags,minFeatureSize, maxFeatureSize);
  for (int i = 0; i < mouths.length; i++) {
    text(mouths[i].x + "," + mouths[i].y + "," + mouths[i].width + "," + mouths[i].height,mouths[i].x, mouths[i].y);
    rect(mouths[i].x, mouths[i].y, mouths[i].width, mouths[i].height);
  }
}
void captureEvent(Capture c) {
  c.read();
}

我也提到了分段/阈值。这是一个粗略的例子,使用检测到的面部的下半部分只是一个基本阈值,然后是一些基本的形态过滤器(腐蚀/扩张)来稍微清理阈值图像:

import gab.opencv.*;
import org.opencv.core.*;
import org.opencv.objdetect.*;
import org.opencv.imgproc.Imgproc;
import java.awt.Rectangle;
import java.util.*;

import processing.video.*;

Capture video;
OpenCV opencv;

CascadeClassifier faceDetector,mouthDetector;
MatOfRect faceDetections,mouthDetections;

//cascade detections parameters - explanations from Mastering OpenCV with Practical Computer Vision Projects
int flags = Objdetect.CASCADE_FIND_BIGGEST_OBJECT;
// Smallest object size.
Size minFeatureSizeFace = new Size(50,60);
Size maxFeatureSizeFace = new Size(125,150);

// How detailed should the search be. Must be larger than 1.0.
float searchScaleFactor = 1.1f;
// How much the detections should be filtered out. This should depend on how bad false detections are to your system.
// minNeighbors=2 means lots of good+bad detections, and minNeighbors=6 means only good detections are given but some are missed.
int minNeighbors = 4;
//laptop webcam face rectangle
//far, small scale, ~50,60px
//typing distance, ~83,91px
//really close, ~125,150

float threshold = 160;
int erodeAmt = 1;
int dilateAmt = 5;

void setup() {
  opencv = new OpenCV(this,320,240);
  size(opencv.width, opencv.height);
  noFill();

  video = new Capture(this,width,height);
  video.start();

  faceDetector = new CascadeClassifier(dataPath("haarcascade_frontalface_alt2.xml"));
  mouthDetector = new CascadeClassifier(dataPath("haarcascade_mcs_mouth.xml"));

}

void draw() {
  //feed cam image to OpenCV, it turns it to grayscale
  opencv.loadImage(video);

  opencv.equalizeHistogram();
  image(opencv.getOutput(), 0, 0 );

  //detect face using raw Java OpenCV API
  Mat equalizedImg = opencv.getGray();
  faceDetections = new MatOfRect();
  faceDetector.detectMultiScale(equalizedImg, faceDetections, searchScaleFactor, minNeighbors, flags, minFeatureSizeFace, maxFeatureSizeFace);
  Rect[] faceDetectionResults = faceDetections.toArray();
  int faces = faceDetectionResults.length;
  text("detected faces: "+faces,5,15);
  if(faces > 0){
    Rect face = faceDetectionResults[0];
    stroke(0,192,0);
    rect(face.x,face.y,face.width,face.height);
    //detect mouth - only within face rectangle, not the whole frame
    Rect faceLower = face.clone();
    faceLower.height = (int) (face.height * 0.55);
    faceLower.y = face.y + faceLower.height; 
    //submat grabs a portion of the image (submatrix) = our region of interest (ROI)
    Mat faceROI = equalizedImg.submat(faceLower);
    Mat faceROIThresh = faceROI.clone();
    //threshold
    Imgproc.threshold(faceROI, faceROIThresh, threshold, width, Imgproc.THRESH_BINARY_INV);
    Imgproc.erode(faceROIThresh, faceROIThresh, new Mat(), new Point(-1,-1), erodeAmt);
    Imgproc.dilate(faceROIThresh, faceROIThresh, new Mat(), new Point(-1,-1), dilateAmt);
    //find contours
    Mat faceContours = faceROIThresh.clone();
    List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
    Imgproc.findContours(faceContours, contours, new Mat(), Imgproc.RETR_EXTERNAL , Imgproc.CHAIN_APPROX_SIMPLE);
    //draw contours
    for(int i = 0 ; i < contours.size(); i++){
      MatOfPoint contour = contours.get(i);
      Point[] points = contour.toArray();
      stroke(map(i,0,contours.size()-1,32,255),0,0);
      beginShape();
      for(Point p : points){
        vertex((float)p.x,(float)p.y);
      }
      endShape();
    }

    //debug view of ROI
    PImage faceImg = createImage(faceLower.width,faceLower.height,RGB);
    opencv.toPImage(faceROIThresh,faceImg);
    image(faceImg,width-faceImg.width,0);
  }
  text("Drag mouseX to control threshold: " + threshold+
      "\nHold 'e' and drag mouseX to control erodeAmt: " + erodeAmt+
      "\nHold 'd' and drag mouseX to control dilateAmt: " + dilateAmt,5,210);
}
void mouseDragged(){
  if(keyPressed){
    if(key == 'e') erodeAmt = (int)map(mouseX,0,width,1,6);
    if(key == 'd') dilateAmt = (int)map(mouseX,0,width,1,10);
  }else{
    threshold = mouseX;
  }
}
void captureEvent(Capture c) {
  c.read();
}

这可以通过使用 YCrCb 色彩空间更好地分割皮肤来改善一点,但总体而言,您注意到有很多变量需要正确处理,这并不能使其成为一个非常灵活的设置。

使用FaceOSC 并通过 oscP5 在处理中读取您需要的值,您将获得更好的结果。以下是FaceOSCReceiver 处理示例的略微简化版本,主要关注嘴巴:

import oscP5.*;
OscP5 oscP5;

// num faces found
int found;

// pose
float poseScale;
PVector posePosition = new PVector();


// gesture
float mouthHeight;
float mouthWidth;

void setup() {
  size(640, 480);
  frameRate(30);

  oscP5 = new OscP5(this, 8338);
  oscP5.plug(this, "found", "/found");
  oscP5.plug(this, "poseScale", "/pose/scale");
  oscP5.plug(this, "posePosition", "/pose/position");
  oscP5.plug(this, "mouthWidthReceived", "/gesture/mouth/width");
  oscP5.plug(this, "mouthHeightReceived", "/gesture/mouth/height");
}

void draw() {  
  background(255);
  stroke(0);

  if(found > 0) {
    translate(posePosition.x, posePosition.y);
    scale(poseScale);
    noFill();
    ellipse(0, 20, mouthWidth* 3, mouthHeight * 3);
  }
}

// OSC CALLBACK FUNCTIONS

public void found(int i) {
  println("found: " + i);
  found = i;
}

public void poseScale(float s) {
  println("scale: " + s);
  poseScale = s;
}

public void posePosition(float x, float y) {
  println("pose position\tX: " + x + " Y: " + y );
  posePosition.set(x, y, 0);
}

public void mouthWidthReceived(float w) {
  println("mouth Width: " + w);
  mouthWidth = w;
}

public void mouthHeightReceived(float h) {
  println("mouth height: " + h);
  mouthHeight = h;
}


// all other OSC messages end up here
void oscEvent(OscMessage m) {
  if(m.isPlugged() == false) {
    println("UNPLUGGED: " + m);
  }
}

在 OSX 上,您可以简单地下载 compiled FaceOSC app。 在其他操作系统上你可能需要setup OpenFrameworks,下载ofxFaceTracker并自己编译FaceOSC。

【讨论】:

  • @JameS 很高兴它有帮助,我已经通过示例扩展了我的答案。长话短说,如果可以的话,使用 FaceOSC :)
  • 哇,这简直是无语了。我今天会试试谢谢你
  • 对于第一个代码,因为由于处理 v.3,我无法将变量用于 size(),所以我将 size() 更改为 surface.setSize() 以尝试此代码。但有时我收到此错误 OpenCV Error: Assertion failed (0
  • 感谢将size()改为surface.setSize(),我还是主要使用Processing 2。就报错而言。 submat() 在幕后断言 ROI 不为空(0 宽度、高度、x、y)。可能有一个奇怪的帧,其中faceLower Rect 的宽度、高度 > 0,所以最好跳过这种情况。一种方法是检查if (faceLower.area() &gt; 0),如果是,请继续,否则跳过。同样,您可以将脚本中 submat() 的代码放入 try...catch 块
【解决方案2】:

很难回答一般的“我该怎么做”类型的问题。 Stack Overflow 专为特定的“我尝试过 X,预期 Y,但得到 Z”类型的问题而设计。但我会尝试笼统地回答:

你需要把你的问题分解成更小的部分。

第 1 步:您能否在您的草图中显示网络摄像头供稿?暂时不要担心计算机视觉的东西。只需连接相机即可。做一些研究并尝试一下。

第 2 步:您能检测出该视频中的面部特征吗?您可以自己尝试,也可以使用the Processing libraries page视频和视觉部分中列出的众多库之一。

第 3 步:阅读有关这些库的文档。试试看。您可能必须使用每个库制作一堆小示例草图,直到找到您喜欢的。 我们无法为您做到这一点,因为哪一个适合您取决于。如果您对某些特定的事情感到困惑,我们可以尝试帮助您,但我们无法真正帮助您挑选图书馆。

第 4 步:完成一系列示例程序并挑选出一个库后,开始朝着您的目标努力。您可以使用库检测面部特征吗?让那部分正常工作。一旦你开始工作,你能检测到像张开或闭上嘴这样的变化吗?

一次迈出一小步。如果您遇到困难,请发布MCVE 以及特定的技术问题,我们将从那里开始。祝你好运。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-15
    • 2020-07-22
    • 1970-01-01
    • 1970-01-01
    • 2018-11-05
    • 1970-01-01
    • 2020-05-14
    相关资源
    最近更新 更多