Skip to content
Snippets Groups Projects
ext.cpp 6.38 KiB
/**
 * @author Andre Anjos <andre.anjos@idiap.ch>
 * @date Thu 20 Sep 2012 14:46:35 CEST
 *
 * @brief Boost.Python extension to flandmark
 */

#include <boost/python.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/shared_array.hpp>

#include <bob/config.h>
#ifdef BOB_API_VERSION
#  include <bob/python/gil.h>
#  include <bob/python/ndarray.h>
#else
#  include <bob/core/python/gil.h>
#  include <bob/core/python/ndarray.h>
#endif

#include <cv.h>

#include "flandmark_detector.h"

using namespace boost::python;

static void delete_cascade(CvHaarClassifierCascade* o) {
  cvReleaseHaarClassifierCascade(&o);
}

static void delete_flandmark(FLANDMARK_Model* o) {
  flandmark_free(o);
}

static void delete_image(IplImage* i) {
  i->imageData = 0; ///< never delete blitz::Array data
  cvReleaseImage(&i);
}

static void delete_storage(CvMemStorage* s) {
  cvClearMemStorage(s);
  cvReleaseMemStorage(&s);
}

/**
 * A simple wrapper to operate the flandmark library quickly in iterative
 * environments like Python.
 */
class Localizer {

  public: //api

    /**
     * Constructor, has to load a boosted cascaded from OpenCV and the
     * flandmark model.
     */
    Localizer(const std::string& opencv_cascade,
        const std::string& flandmark_model) :
      m_cascade((CvHaarClassifierCascade*)cvLoad(opencv_cascade.c_str(), 0, 0, 0), std::ptr_fun(delete_cascade)),
      m_flandmark(flandmark_init(flandmark_model.c_str()), std::ptr_fun(delete_flandmark)),
      m_storage(cvCreateMemStorage(0), std::ptr_fun(delete_storage))
    {
      if( !m_cascade ) {
        PYTHON_ERROR(RuntimeError, "Couldnt load Face detector '%s'", opencv_cascade.c_str());
      }

      if ( !m_flandmark ) {
        PYTHON_ERROR(RuntimeError, "Structure model wasn't created. Corrupted file '%s'", flandmark_model.c_str());
      }
      m_landmarks.reset(new double[2*m_flandmark->data.options.M]);
    }

    /**
     * Detect and locates the landmarks from an input image
     */
    tuple call1(bob::python::const_ndarray input) {
      //checks type
      const bob::core::array::typeinfo& type = input.type();
      if ((type.dtype != bob::core::array::t_uint8) || (type.nd != 2)) {
        PYTHON_ERROR(TypeError, "Input data must be a 2D numpy.array with dtype=uint8 (i.e. a gray-scaled image), but you passed %s", type.str().c_str());
      }

      //converts to IplImage
      boost::shared_ptr<IplImage> ipl_image(cvCreateImageHeader(cvSize(type.shape[1], type.shape[0]), IPL_DEPTH_8U, 1), std::ptr_fun(delete_image));
      ipl_image->imageData = (char*)input.bz<uint8_t,2>().data();

      // Flags for OpenCV face detection
      CvSize minFeatureSize = cvSize(40, 40);
      int flags =  CV_HAAR_DO_CANNY_PRUNING;
      float search_scale_factor = 1.1f;

      // Detect all the faces in the greyscale image.
      CvSeq* rects = cvHaarDetectObjects(ipl_image.get(), m_cascade.get(),
            m_storage.get(), search_scale_factor, 2, flags, minFeatureSize);
      int nFaces = rects->total;

      list retval;
      for (int iface = 0; iface < (rects ? nFaces : 0); ++iface) {

        CvRect* r = (CvRect*)cvGetSeqElem(rects, iface);

        dict det;
        det["bbox"] = make_tuple(r->x, r->y, r->width, r->height);
        int bbox[4] = {r->x, r->y, r->x + r->width, r->y + r->height};

    		int flandmark_result;
        {
          bob::python::no_gil unlock;
          flandmark_result = flandmark_detect(ipl_image.get(), bbox, m_flandmark.get(),
              m_landmarks.get());
        }

        list lmlist; ///< landmark list

    		// do not copy the results when the landmark detector indicates an error.
    		// otherwise stale results (from a previous invocation) are returned
    		if (flandmark_result == NO_ERR) {
          // The first point represents the center of the bounding box used by
          // the flandmark library.
          for (int i = 0; i < (2*m_flandmark->data.options.M); i += 2) {
            lmlist.append(make_tuple(m_landmarks[i+1], m_landmarks[i]));
          }
        }
        det["landmark"] = tuple(lmlist);

        retval.append(det);
      }

      return tuple(retval);
    }


    /**
     * Detect and locates the landmarks from an input image
     */
    object call2(bob::python::const_ndarray input, const int b_y, const int b_x, const int b_height, const int b_width)
    {
      //checks type
      const bob::core::array::typeinfo& type = input.type();
      if ((type.dtype != bob::core::array::t_uint8) || (type.nd != 2)) {
        PYTHON_ERROR(TypeError, "Input data must be a 2D numpy.array with dtype=uint8 (i.e. a gray-scaled image), but you passed %s", type.str().c_str());
      }

      //converts to IplImage
      boost::shared_ptr<IplImage> ipl_image(cvCreateImageHeader(cvSize(type.shape[1], type.shape[0]), IPL_DEPTH_8U, 1), std::ptr_fun(delete_image));
      ipl_image->imageData = (char*)input.bz<uint8_t,2>().data();

      int bbox[4] = {b_x, b_y, b_x + b_width, b_y + b_height};
      int flandmark_result;
      {
        bob::python::no_gil unlock;
        flandmark_result = flandmark_detect(ipl_image.get(), bbox, m_flandmark.get(),
            m_landmarks.get());
      }

      list lmlist; ///< landmark list

      if (flandmark_result == NO_ERR) {
        for (int i = 0; i < (2*m_flandmark->data.options.M); i += 2) {
          lmlist.append(make_tuple(m_landmarks[i+1], m_landmarks[i]));
        }
      }

      return object(lmlist);
    }


  private: //representation

    boost::shared_ptr<CvHaarClassifierCascade> m_cascade;
    boost::shared_ptr<FLANDMARK_Model> m_flandmark;
    boost::shared_ptr<CvMemStorage> m_storage;
    boost::shared_array<double> m_landmarks;

};

BOOST_PYTHON_MODULE(_flandmark) {
  bob::python::setup_python("bindings to flandmark - a library for the localization of facial landmarks");

  class_<Localizer>("Localizer", "A key-point localization for faces using flandmark", init<const std::string&, const std::string&>((arg("detector"), arg("localizer")), "Initializes with both an OpenCV face detector model and an flandmark model"))
    .def("__call__", &Localizer::call1, (arg("image")), "Locates (possibly multiple) key-points on the given input image. Returns a list of located faces (by OpenCV's model), each attached to a list of key-points.")
    .def("__call__", &Localizer::call2, (arg("image"), arg("b_y"), arg("b_x"), arg("b_height"), arg("b_width")), "Locates (possibly multiple) key-points on the given input image, given a bounding box and returns them as a list.")
    ;
}