Build a Face Detector on OS X Using OpenCV and C++
Building and using C++ libraries can be a daunting task, even more so for big libraries like OpenCV. This article should get you started with a minimal build of OpenCV and a sample application written in C++.
This application will get images from the webcam, draw rectangles around the faces in the images and show them to you on screen.
Requirements
I've built this on a MacBook Pro running OS X El Capitan Version 10.11.1.
We'll be using the GNU C++ compiler (g++) from the command line. Note that you should still have Xcode installed (I have Xcode 7.1 installed).
Here's what you need to do :
- Get "OpenCV for Linux/Mac" from the OpenCV Downloads Page I got version 3.0.
- Extract the contents of the zip file from step 1 to a folder of your choosing (I chose ~/opencv-3.0.0).
- Get a binary distribution of Cmake from the Cmake Downloads Page I got cmake-3.4.0-Darwin-x86_64.dmg.
- Install Cmake.
Building OpenCV
OpenCV uses CMake files to describe how the project needs to be built. CMake can transform these files into actual project settings (e.g. an Xcode project, Unix makefiles, a Visual Studio project, etc.) depending on the generator you choose.
First open CMake and a small window will pop-up that will let you choose your build options based on the CMakeList.txt files in the opencv source directory. First click on the Browse Source... button and choose the path to the opencv source folder (the folder you extracted the zip file to at step 2). Then click on the Browse Build... button and choose a path to a build folder, I'm going to create a new folder called build in the previously mentioned source folder.
If at any point you are prompted to choose a generator, pick Unix Makefiles. If the paths you chose were correct, after you click the Configure button, you should be looking at something like this :
For a somewhat minimal OpenCV build, make sure you only have the following options enabled :
- BUILD_JASPER
- BUILD_JPEG
- BUILD_OPENEXR
- BUILD_PACKAGE
- BUILD_PNG
- BUILD_TIFF
- BUILD_WITH_DEBUG_INFO
- BUILD_ZLIB
- BUILD_opencv_apps
- BUILD_opencv_calib3d
- BUILD_opencv_core
- BUILD_opencv_features2d
- BUILD_opencv_flann
- BUILD_opencv_hal
- BUILD_opencv_highgui
- BUILD_opencv_imgcodecs
- BUILD_opencv_imgproc
- BUILD_opencv_ml
- BUILD_opencv_objdetect
- BUILD_opencv_photo
- BUILD_opencv_python2
- BUILD_opencv_shape
- BUILD_opencv_stitching
- BUILD_opencv_superres
- BUILD_opencv_ts
- BUILD_opencv_video
- BUILD_opencv_videoio
- BUILD_opencv_videostab
- ENABLE_SSE
- ENABLE_SSE2
- WITH_1394
- WITH_JASPER
- WITH_JPEG
- WITH_LIBV4L
- WITH_OPENEXR
- WITH_PNG
- WITH_TIFF
- WITH_V4L
- WITH_WEBP
You should disable the options that are not in the list, especially the BUILD_SHARED_LIBS one. Don't touch the options that are text fields unless you know what you're doing.
Most of these options you don't need for this particular exercise, but it will save you time by not having to rebuild OpenCV should you decide to try something else.
Once you have selected the settings above, click Generate. Now you can navigate to the build folder, I'll do so with cd ~/opencv-3.0.0/build/ and run make to build OpenCV.
Installing OpenCV
If everything goes well, after the build finishes, run make install to add the OpenCV includes to the /usr/local/include folder and the libraries to the /usr/local/lib and /usr/local/share/OpenCV/3rdparty/lib folders.
After that's done, you should be able to build your own C++ applications that link against OpenCV.
The Face Detector Application
Now let's try to build our first application with OpenCV
Here's the code and comments that explain how to do just that :
#include <iostream> //Include OpenCV #include <opencv2/opencv.hpp> int main(void ) { //Capture stream from webcam. cv::VideoCapture capture(0); //Check if we can get the webcam stream. if(!capture.isOpened()) { std::cout << "Could not open camera" << std::endl; return -1; } //OpenCV saves detection rules as something called a CascadeClassifier which // can be used to detect objects in images. cv::CascadeClassifier faceCascade; //We'll load the lbpcascade_frontalface.xml containing the rules to detect faces. //The file should be right next to the binary. if(!faceCascade.load("lbpcascade_frontalface.xml")) { std::cout << "Failed to load cascade classifier" << std::endl; return -1; } while (true) { //This variable will hold the image from the camera. cv::Mat cameraFrame; //Read an image from the camera. capture.read(cameraFrame); //This vector will hold the rectangle coordinates to a detection inside the image. std::vector<cv::Rect> faces; //This function detects the faces in the image and // places the rectangles of the faces in the vector. //See the detectMultiScale() documentation for more details // about the rest of the parameters. faceCascade.detectMultiScale( cameraFrame, faces, 1.09, 3, 0 | CV_HAAR_SCALE_IMAGE, cv::Size(30, 30)); //Here we draw the rectangles onto the image with a red border of thikness 2. for( size_t i = 0; i < faces.size(); i++ ) cv::rectangle(cameraFrame, faces[i], cv::Scalar(0, 0, 255), 2); //Here we show the drawn image in a named window called "output". cv::imshow("output", cameraFrame); //Waits 50 miliseconds for key press, returns -1 if no key is pressed during that time if (cv::waitKey(50) >= 0) break; } return 0; }
I saved this as main.cpp. To build it I used the following command :
g++ -o main main.cpp -I/usr/local/include \ -L/usr/local/lib -lopencv_core -lopencv_imgproc -lopencv_objdetect \ -lopencv_imgcodecs -lopencv_highgui -lopencv_hal -lopencv_videoio \ -L/usr/local/share/OpenCV/3rdparty/lib -llibpng -llibjpeg -llibwebp \ -llibtiff -lzlib -lIlmImf -llibjasper -framework AVFoundation -framework QuartzCore \ -framework CoreMedia -framework Cocoa -framework QTKit
Hopefully, no errors should occur.
Conclusions
Before running the application, you have to copy the lbpcascade_frontalface.xml next to the main file. You can find this file in the OpenCV source folder under /data/lbpcascades/. You can also find some other cascades to detect eyes, cat faces, etc.
Now just run the ./main and enjoy!
Symfony2 Facebook and Google Login: The Easy Way
Since pretty much any potential user of an app has a Facebook or a Google account, it's crucial to have a login or registration service using these social networks. Luckily, HWIOAuthBundle provides the necessary tools to accomplish this. So here's an easy and straightforward way to integrate HWIOAuth in our app alongside FOSUserBundle:
1. Configure HWIOauthBundle
Assuming that you've already installed and configured FOSUserBundle, we now need to add HWIOAuthBundle in our application. As mentioned in their setup page, we simply have to:
composer require hwi/oauth-bundle
Then we should enable the bundle in the kernel:
// app/AppKernel.php public function registerBundles() { $bundles = array( // ... new HWI\Bundle\OAuthBundle\HWIOAuthBundle(), ); }
and then we can import the social routes:
# app/config/routing.yml hwi_oauth_redirect: resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml" prefix: /connect hwi_oauth_login: resource: "@HWIOAuthBundle/Resources/config/routing/login.xml" prefix: /login
Once we have these setup, we can start configuring the security details in security.yml
:
firewalls: main: pattern: ^/ oauth: failure_path: your_path login_path: your_path check_path: /connect_check provider: fos_userbundle resource_owners: facebook: "/login/check-facebook" google: "/login/check-google" oauth_user_provider: service: app.provider.oauth
And then we can finally add Facebook and Google into HWIOAuth's configuration in config.yml
:
hwi_oauth: # name of the firewall in which this bundle is active, this setting MUST be set firewall_name: main connect: account_connector: app.provider.oauth resource_owners: facebook: type: facebook client_id: %facebook_client_id% client_secret: %facebook_client_secret% scope: "email, public_profile" infos_url: "https://graph.facebook.com/me?fields=id,name,email,picture.type(large)" paths: email: email options: display: page google: type: google client_id: %google_client_id% client_secret: %google_client_secret% scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile" paths: email: email
2. Build our own user provider by extending HWIOAuth's FOSUBUserProvider
As you've probably noticed, we used app.provider.oauth
in both security.yml
and config.yml
. So we need to define that service in services.yml
:
app.provider.oauth: class: AppBundle\Security\Core\User\OAuthUserProvider arguments: [@fos_user.user_manager,{facebook: facebookID, google: googleID}]
And then build it in our AppBundle:
<?php namespace AppBundle\Security\Core\User; use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface; use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\User\UserChecker; use Symfony\Component\Security\Core\User\UserInterface; /** * Class OAuthUserProvider * @package AppBundle\Security\Core\User */ class OAuthUserProvider extends BaseClass { /** * {@inheritdoc} */ public function loadUserByOAuthUserResponse(UserResponseInterface $response) { $socialID = $response->getUsername(); $user = $this->userManager->findUserBy(array($this->getProperty($response)=>$socialID)); $email = $response->getEmail(); //check if the user already has the corresponding social account if (null === $user) { //check if the user has a normal account $user = $this->userManager->findUserByEmail($email); if (null === $user || !$user instanceof UserInterface) { //if the user does not have a normal account, set it up: $user = $this->userManager->createUser(); $user->setEmail($email); $user->setPlainPassword(md5(uniqid())); $user->setEnabled(true); } //then set its corresponding social id $service = $response->getResourceOwner()->getName(); switch ($service) { case 'google': $user->setGoogleID($socialID); break; case 'facebook': $user->setFacebookID($socialID); break; } $this->userManager->updateUser($user); } else { //and then login the user $checker = new UserChecker(); $checker->checkPreAuth($user); } return $user; } }
Related: What Nobody Tells You When You're a Junior Developer
3. Override the User Model
In our user provider defined above we modified two attributes that do not exist in FOSUser's User model: $facebookId
and $googleId
. So we need to override the User model and add them.
namespace AppBundle\Entity; use FOS\UserBundle\Model\User as BaseUser; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(repositoryClass="AppBundle\Entity\UserRepository") * @ORM\Table(name="fos_user") */ class User extends BaseUser { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @var string * * @ORM\Column(name="facebook_id", type="string", nullable=true) */ private $facebookID; /** * @var string * * @ORM\Column(name="google_id", type="string", nullable=true) */ private $googleID; // ... }
Don't forget to update your database and then you can move forward to the next step.
4. Add Facebook and Google to our login and register pages
Now we're all set to update our Twig files with HWIOauth's paths. Since we're using FOSUser, we've overridden their register.html.twig
and login.html.twig
files and then added the social links:
{# ... #} <a href="{{ path('hwi_oauth_service_redirect', {'service': 'facebook' }) }}"> <span>Facebook</span> </a> <a href="{{ path('hwi_oauth_service_redirect', {'service': 'google' }) }}"> <span>Google</span> </a> {# ... #}
And there you have it: a fully functional Facebook and Google login and signup system. Now we can go to Facebook Developers and Google Developers Console, create our apps and add our client_id
and client_secret
in config.yml
. Have fun!
If you have a different take on this process and can make it even easier, please let us know in the comment section below.
Getting Started with Building APIs in Symfony2
Hello all you Interwebs friends! While we're passing through the shallow mists of time, REST is becoming more and more of a universal standard when building web applications. That said, here's a very brief tutorial on how to get started with building APIs in Symfony2.
Spoiler alert: the bits of code written below use FosUserBundle + Doctrine.
1. Generate a New Bundle
It's nice to keep your code neat and tidy, so ideally, you should create a separate bundle that will only store your API code, we can generically name ApiBundle.
$ php app/console generate:bundle --bundle-name=ApiBundle --format=annotation
2. Versioning
This isn't by any means mandatory, but if you believe that your API endpoints will suffer major changes along the way, that cannot be predicted from the get go, it would be nice to version your code. This means that you would, initially, have a prefix in your endpoints, like: `/v1`/endpoint.json, and you'd increase that value each time a new version comes along. I'll describe how to actually create the first version (`v1`) of your API a little further down the line.
3. Install a Few Useful Bundles
- FosRestBundle - this bundle will make our REST implementation a lot easier.
$ composer require friendsofsymfony/rest-bundle
and then include FOSRestBundle in your `AppKernel.php` file:
$bundles = array( // ... new FOS\RestBundle\FOSRestBundle(), );
- JmsSerializerBundle - this will take care of the representation of our resources, converting objects into JSON.
composer require jms/serializer-bundle
and then include JMSSerializerBundle in your `AppKernel.php`:
$bundles = array( // ... new JMS\SerializerBundle\JMSSerializerBundle(), // ... );
4. Configurations
Configure the Response object to return JSON, as well as set a default format for our API calls. This can be achieved by adding the following code in `app/config/config.yml`:
fos_rest: format_listener: rules: - { path: ^/api/, priorities: [ html, json, xml ], fallback_format: ~, prefer_extension: true } routing_loader: default_format: json param_fetcher_listener: true view: view_response_listener: 'force' formats: xml: true json: true templating_formats: html: true
5. Routing
I prefer using annotations as far as routes go, so all we need to do in this scenario would be to modify our API Controllers registration in `/app/config/routing.yml`. This registration should have already been created when you ran the generate bundle command. Now we'll only need to add our version to that registration. As far as the actual routes of each endpoint go, we'll be manually defining them later on, in each action's annotation.
api: resource: "@ApiBundle/Controller/V1/" type: annotation prefix: /api/v1
At this point we're all set to start writing our first bit of code. First off, in our Controller namespace, we would want to create a new directory, called `V1`. This will hold all of our v1 API Controllers. Whenever we want to create a new version, we'll start from scratch by creating a `V2` namespace and so on.
After that's done let's create an action that will GET a user (assuming we've previously created a User entity and populated it with users). This would look something like:
<?php namespace ApiBundle\Controller\V1; use FOS\RestBundle\Controller\FOSRestController; use FOS\RestBundle\Controller\Annotations as Rest; class UserController extends FOSRestController { /** * @return array * @Rest\Get("/users/{id}") * @Rest\View */ public function getUserAction($id) { $em = $this->getDoctrine()->getManager(); $user = $em->getRepository('AppBundle:User')->find($id); return array('user' => $user); } }
If we want to GET a list of all users, that's pretty straightforward as well:
/** * GET Route annotation. * @return array * @Rest\Get("/users/get.{_format}") * @Rest\View */ public function getUsersAction() { $em = $this->getDoctrine()->getManager(); $users = $em->getRepository('AppBundle:User')->findAll(); return array('users' => $users); }
With that done, when running a GET request on `https://yourapp.com/api/v1/users/1.json`, you should get a `json` response with that specific user object.
What about a POST request? Glad you asked! There are actually quite a few options to do that. One would be to get the request data yourself, validate it and create the new resource yourself. Another (simpler) option would be to use Symfony Forms which would handle all this for us.
The scenario here would be for us to add a new user resource into our database.
If you're also using FosUserBundle to manage your users, you can just use a similar RegistrationFormType:
<?php namespace ApiBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; class RegistrationFormType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('email', 'email') ->add('username') ->add('plainPassword', 'password') ; } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( 'data_class' => 'AppBundle\Entity\User', 'csrf_protection' => false )); } public function getName() { return 'my_awesome_form'; } }
Next, we'll want to actually create our addUserAction():
/** * POST Route annotation. * @Rest\Post("/users/new.{_format}") * @Rest\View * @return array */ public function addUserAction(Request $request) { $userManager = $this->container->get('fos_user.user_manager'); $user = $userManager->createUser(); $form = $this->createForm(new \ApiBundle\Form\Type\RegistrationFormType(), $user); $form->handleRequest($request); if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($user); $em->flush(); return array('user' => $user); } return View::create($form, 400); }
To make a request, you'll need to send the data as raw JSON, to our `https://yourapp.com/api/v1/users/new.json` POST endpoint:
{ "my_awesome_form":{ "email":"andrei@ibw.com", "username":"sdsa", "plainPassword":"asd" } }
And that's all there is to it.
We haven't covered the cases where you'd want Delete or Update a user yet. Updating resources through the REST standards can be done using either PUT or PATCH. The difference between them is that PUT will completely replace your resource, while PATCH will only, well, patch it... meaning that it will partially update your resource with the input it got from the API request.
Let's get to it then. We'll use the same form as before, and we'll try to only change (we'll use the PATCH verb for that) the email address, username and password of our previously created user:
/** * PATCH Route annotation. * @Rest\Patch("/users/edit/{id}.{_format}") * @Rest\View * @return array */ public function editAction(Request $request, $id) { $userManager = $this->container->get('fos_user.user_manager'); $user = $userManager->findUserBy(array('id'=>$id)); $form = $this->createForm(new \ApiBundle\Form\Type\RegistrationFormType(), $user, array('method' => 'PATCH')); $form->handleRequest($request); if ($user) { if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($user); $em->flush(); return array('user' => $user); } else { return View::create($form, 400); } } else { throw $this->createNotFoundException('User not found!'); } }
The request body is the same as the one shown for the POST method, above. There are a few small differences in our edit action though. First off - we're telling our form to use the PATCH method. Second - we are handling the case where the user ID provided isn't found.
The Delete method is the easiest one yet. All we need to do is to find the user and remove it from our database. If no user is found, we'll throw a "user not found" error:
/** * DELETE Route annotation. * @Rest\Delete("/users/delete/{id}.{_format}") * @Rest\View(statusCode=204) * @return array */ public function deleteAction($id) { $em = $this->getDoctrine()->getManager(); $user = $em->getRepository('AppBundle:User')->find($id); $em->remove($user); $em->flush(); }
Related: Routing in Symfony2
Conclusions
Aaand we're done. We now have a fully working CRUD for our User Entity. Thanks for reading and please do share your opinions/suggestions in the comment section below.
What Nobody Tells You When You're a Junior Developer
Hooray! You've landed your dream job and you can now proudly call yourself a Junior Developer. The future looks nothing but bright and your awesome life story is underway. But now the hard part actually begins. Here's how you can stay on top of things with some useful tips I wish I knew when I was in your shoes as a junior developer.