Meeting Rangers de Janvier 2019

La communauté du Grand Est, de Lyon et Paris se sont réunis hier chez Jean-Claude à Montparnasse pour parler replatforming z/OS, Azure et aussi C++ sous Windows !

Il y avait:

  • Cédric G de Strasbourg
  • JBB de Strasbourg
  • une jeune collègue bien sympa à JBB
  • Keelan C de Lyon
  • et moi de Paris

Une belle soirée entre Rangers.

Advertisements

Examens de C++ pour l’ESGI

Pour l’ESGI, je donne des cours de “Modern C++” à des élèves en 4ème année Génie Logiciel. Il y a deux groupes de 32 élèves.

Voici les examens de cet hiver:

EXAM AL2 : https://christophepichaud.home.blog/2019/01/12/examen-c-pour-mes-etudiants-esgi-4eme-annee-al2/

EXAM AL1 : https://christophepichaud.home.blog/2019/01/12/examen-c-pour-mes-etudiants-esgi-4eme-annee-al1/

Détections faciales avec OpenCV

Le monde des services congnitifs est en plein expansion. Azure propose un service comme ça : Cognitives Services. Le problème, c’est que le traitement de l’image est fait par Azure. Il faut envoyer l’image et le traitement est payant.

Il existe des alternatives open-source comme OpenCV 4.0. les bindings sont en C#, Java et Python.

Voici quelques exemples:

opencv_net

opencv_cpp3

Vous vous demandez si le code est complexe ?

En C#:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.CV.UI;
using Emgu.CV.Cuda;

namespace FaceDetection
{
   static class Program
   {
      ///

      /// The main entry point for the application.
      ///

 

      [STAThread]
      static void Main()
      {
         Application.EnableVisualStyles();
         Application.SetCompatibleTextRenderingDefault(false);
         Run();
      }



      static void Run()
      {
         IImage image;

         //Read the files as an 8-bit Bgr image  

         image = new UMat("Attach01.jpg", ImreadModes.Color); //UMat version
         //image = new Mat("lena.jpg", ImreadModes.Color); //CPU version

         long detectionTime;
         List faces = new List();
         List eyes = new List();

         DetectFace.Detect(
           image, @"data\haarcascades\haarcascade_frontalface_default.xml", @"data\haarcascades\haarcascade_eye.xml", 
           faces, eyes,
           out detectionTime);

         foreach (Rectangle face in faces)
            CvInvoke.Rectangle(image, face, new Bgr(Color.Red).MCvScalar, 2);
         foreach (Rectangle eye in eyes)
            CvInvoke.Rectangle(image, eye, new Bgr(Color.Blue).MCvScalar, 2);

         //display the image 
         using (InputArray iaImage = image.GetInputArray())
         ImageViewer.Show(image, String.Format(
            "Completed face and eye detection using {0} in {1} milliseconds", 
            (iaImage.Kind == InputArray.Type.CudaGpuMat && CudaInvoke.HasCuda) ? "CUDA" :
            (iaImage.IsUMat && CvInvoke.UseOpenCL) ? "OpenCL" 
            : "CPU",
            detectionTime));
      }

   }
}



using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using Emgu.CV;
using Emgu.CV.Structure;
#if !(__IOS__ || NETFX_CORE)
using Emgu.CV.Cuda;
#endif

namespace FaceDetection
{
   public static class DetectFace
   {
      public static void Detect(
         IInputArray image, String faceFileName, String eyeFileName,
         List faces, List eyes,
         out long detectionTime)
      {
         Stopwatch watch;

         using (InputArray iaImage = image.GetInputArray())
         {

#if !(__IOS__ || NETFX_CORE)
            if (iaImage.Kind == InputArray.Type.CudaGpuMat && CudaInvoke.HasCuda)
            {
               using (CudaCascadeClassifier face = new CudaCascadeClassifier(faceFileName))
               using (CudaCascadeClassifier eye = new CudaCascadeClassifier(eyeFileName))
               {
                  face.ScaleFactor = 1.1;
                  face.MinNeighbors = 10;
                  face.MinObjectSize = Size.Empty;
                  eye.ScaleFactor = 1.1;
                  eye.MinNeighbors = 10;
                  eye.MinObjectSize = Size.Empty;
                  watch = Stopwatch.StartNew();
                  using (CudaImage<Bgr, Byte> gpuImage = new CudaImage<Bgr, byte>(image))
                  using (CudaImage<Gray, Byte> gpuGray = gpuImage.Convert<Gray, Byte>())
                  using (GpuMat region = new GpuMat())
                  {
                     face.DetectMultiScale(gpuGray, region);
                     Rectangle[] faceRegion = face.Convert(region);
                     faces.AddRange(faceRegion);
                     foreach (Rectangle f in faceRegion)
                     {
                        using (CudaImage<Gray, Byte> faceImg = gpuGray.GetSubRect(f))
                        {
                           //For some reason a clone is required.
                           //Might be a bug of CudaCascadeClassifier in opencv
                           using (CudaImage<Gray, Byte> clone = faceImg.Clone(null))
                           using (GpuMat eyeRegionMat = new GpuMat())
                           {
                              eye.DetectMultiScale(clone, eyeRegionMat);
                              Rectangle[] eyeRegion = eye.Convert(eyeRegionMat);
                              foreach (Rectangle e in eyeRegion)
                              {
                                 Rectangle eyeRect = e;
                                 eyeRect.Offset(f.X, f.Y);
                                 eyes.Add(eyeRect);
                              }
                           }
                        }
                     }
                  }
                  watch.Stop();
               }
            }
            else
#endif
            {
               //Read the HaarCascade objects
               using (CascadeClassifier face = new CascadeClassifier(faceFileName))
               using (CascadeClassifier eye = new CascadeClassifier(eyeFileName))
               {
                  watch = Stopwatch.StartNew();

                  using (UMat ugray = new UMat())
                  {
                     CvInvoke.CvtColor(image, ugray, Emgu.CV.CvEnum.ColorConversion.Bgr2Gray);

                     //normalizes brightness and increases contrast of the image
                     CvInvoke.EqualizeHist(ugray, ugray);

                     //Detect the faces  from the gray scale image and store the locations as rectangle
                     //The first dimensional is the channel
                     //The second dimension is the index of the rectangle in the specific channel                     
                     Rectangle[] facesDetected = face.DetectMultiScale(
                        ugray,
                        1.1,
                        10,
                        new Size(20, 20));

                     faces.AddRange(facesDetected);

                     foreach (Rectangle f in facesDetected)
                     {
                        //Get the region of interest on the faces
                        using (UMat faceRegion = new UMat(ugray, f))
                        {
                           Rectangle[] eyesDetected = eye.DetectMultiScale(
                              faceRegion,
                              1.1,
                              10,
                              new Size(20, 20));

                           foreach (Rectangle e in eyesDetected)
                           {
                              Rectangle eyeRect = e;
                              eyeRect.Offset(f.X, f.Y);
                              eyes.Add(eyeRect);
                           }
                        }
                     }
                  }
                  watch.Stop();
               }
            }
            detectionTime = watch.ElapsedMilliseconds;
         }
      }
   }
}

 

En C++:

#ifndef PCH_H
#define PCH_H

// TODO: add headers that you want to pre-compile here
#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>

using namespace std;
using namespace cv;

#pragma comment(lib, "opencv_world400d.lib")

#endif //PCH_H


// ConsoleApplication1.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include "pch.h"

string cascadeName;
string nestedCascadeName;

void detectAndDraw(Mat& img, CascadeClassifier& cascade,
	CascadeClassifier& nestedCascade,
	double scale, bool tryflip);


int main(int argc, const char** argv)
{
	VideoCapture capture;
	Mat frame, image;
	string inputName;
	bool tryflip;
	CascadeClassifier cascade, nestedCascade;
	double scale;

	cascadeName = "..\\data\\haarcascades\\haarcascade_frontalface_alt.xml";
	nestedCascadeName = "..\\data\\haarcascades\\haarcascade_eye_tree_eyeglasses.xml";
	scale = 1.3;
	inputName = "group3.jpg";
	tryflip = false;

	if (!nestedCascade.load(samples::findFileOrKeep(nestedCascadeName)))
	{
		cerr << "WARNING: Could not load classifier cascade for nested objects" << endl;
		return 1;
	}

	if (!cascade.load(samples::findFile(cascadeName)))
	{
		cerr << "ERROR: Could not load classifier cascade" << endl;
		return 1;
	}
	
	image = imread(samples::findFileOrKeep(inputName), IMREAD_COLOR);
	if (image.empty())
	{
		if (!capture.open(samples::findFileOrKeep(inputName)))
		{
			cout << "Could not read " << inputName << endl;
			return 1;
		}
	}

	cout << "Detecting face(s) in " << inputName << endl;
	if (!image.empty())
	{
		detectAndDraw(image, cascade, nestedCascade, scale, tryflip);
		waitKey(0);
	}

	return 0;
}

void detectAndDraw(Mat& img, CascadeClassifier& cascade,
	CascadeClassifier& nestedCascade,
	double scale, bool tryflip)
{
	double t = 0;
	vector<Rect> faces, faces2;
	const static Scalar colors[] =
	{
		Scalar(255,0,0),
		Scalar(255,128,0),
		Scalar(255,255,0),
		Scalar(0,255,0),
		Scalar(0,128,255),
		Scalar(0,255,255),
		Scalar(0,0,255),
		Scalar(255,0,255)
	};
	Mat gray, smallImg;

	cvtColor(img, gray, COLOR_BGR2GRAY);
	double fx = 1 / scale;
	resize(gray, smallImg, Size(), fx, fx, INTER_LINEAR_EXACT);
	equalizeHist(smallImg, smallImg);

	t = (double)getTickCount();
	cascade.detectMultiScale(smallImg, faces,
		1.1, 2, 0
		//|CASCADE_FIND_BIGGEST_OBJECT
		//|CASCADE_DO_ROUGH_SEARCH
		| CASCADE_SCALE_IMAGE,
		Size(30, 30));
	if (tryflip)
	{
		flip(smallImg, smallImg, 1);
		cascade.detectMultiScale(smallImg, faces2,
			1.1, 2, 0
			//|CASCADE_FIND_BIGGEST_OBJECT
			//|CASCADE_DO_ROUGH_SEARCH
			| CASCADE_SCALE_IMAGE,
			Size(30, 30));
		for (vector<Rect>::const_iterator r = faces2.begin(); r != faces2.end(); ++r)
		{
			faces.push_back(Rect(smallImg.cols - r->x - r->width, r->y, r->width, r->height));
		}
	}
	t = (double)getTickCount() - t;
	printf("detection time = %g ms\n", t * 1000 / getTickFrequency());
	for (size_t i = 0; i < faces.size(); i++)
	{
		Rect r = faces[i];
		Mat smallImgROI;
		vector<Rect> nestedObjects;
		Point center;
		Scalar color = colors[i % 8];
		int radius;

		double aspect_ratio = (double)r.width / r.height;
		if (0.75 < aspect_ratio && aspect_ratio < 1.3)
		{
			center.x = cvRound((r.x + r.width*0.5)*scale);
			center.y = cvRound((r.y + r.height*0.5)*scale);
			radius = cvRound((r.width + r.height)*0.25*scale);
			circle(img, center, radius, color, 3, 8, 0);
		}
		else
			rectangle(img, Point(cvRound(r.x*scale), cvRound(r.y*scale)),
				Point(cvRound((r.x + r.width - 1)*scale), cvRound((r.y + r.height - 1)*scale)),
				color, 3, 8, 0);
		if (nestedCascade.empty())
			continue;
		smallImgROI = smallImg(r);
		nestedCascade.detectMultiScale(smallImgROI, nestedObjects,
			1.1, 2, 0
			//|CASCADE_FIND_BIGGEST_OBJECT
			//|CASCADE_DO_ROUGH_SEARCH
			//|CASCADE_DO_CANNY_PRUNING
			| CASCADE_SCALE_IMAGE,
			Size(30, 30));
		for (size_t j = 0; j < nestedObjects.size(); j++)
		{
			Rect nr = nestedObjects[j];
			center.x = cvRound((r.x + nr.x + nr.width*0.5)*scale);
			center.y = cvRound((r.y + nr.y + nr.height*0.5)*scale);
			radius = cvRound((nr.width + nr.height)*0.25*scale);
			circle(img, center, radius, color, 3, 8, 0);
		}
	}
	imshow("result", img);
}

 

Comme vous pouvez le constater, il est facile de mettre en oeuvre des services techniques performants sans payer.

Contributions pour le magazine Programmez

A partir de 2019, la communauté des .NET Rangers va contribuer de manière régulière au magazine Programmez au travers la rédaction d’articles technique sur les sujets suivants:

  • Azure, Infrastructure as code, DevOps, Kubernetes, Docker
  • Technologies Microsoft
  • C#, NET et SQL Server
  • C++
  • Scripting Python et PowerShell
  • Sécurité, hacking, drivers

Et aussi quelques surprises… A suivre début 2019 !

Fonctionnalités communes dans ASP.NET Core 2.1 WebApi: authentification avec un JWT

Introduction

L’authentification et l’autorisation sont nécessaires pour limiter l’accès aux données personnalisées et / ou sensibles.
Une technique plus sûre que la protection avec des cookies consiste à utiliser des jetons, fournis par un fournisseur de jeton de votre choix.
Dans cet article, nous allons parler des JWT avec OpenID Connect.

OpenID Connect 1.0 est une simple couche d’identité au dessus du protocole OAuth 2.0. Il permet aux clients de vérifier l’identité de l’utilisateur final en fonction de l’authentification effectuée par un serveur d’autorisations, ainsi que d’obtenir des informations de profil de base sur l’utilisateur final de manière interopérable et de type REST. OpenID Connect permet aux clients de tous types, y compris les clients Web, mobiles et JavaScript, de demander et de recevoir des informations sur les sessions authentifiées et les utilisateurs finaux. La suite de spécifications est extensible, permettant aux participants d’utiliser des fonctionnalités optionnelles telles que le cryptage des données d’identité, la découverte des fournisseurs OpenID et la gestion de session, lorsque cela leur semble judicieux.

Pour plus d’informations sur la configuration d’un fournisseur Open Id Connect, vous pouvez consulter cet exemple avec Azure AD ici.

Configurer le Startup.cs

Etape 1: Télécharger le package Microsoft.AspNetCore.Authentication.JwtBearer Nuget

PM> Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version 2.1.2

Etape 2: Paramétrer le schéma d’authentification avec un JWT

public void ConfigureServices(IServiceCollection services)
{
   // Authentication
   services.AddAuthentication(options =>
   {
      options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
      options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
   });
}

Etape 3: Configurer le fournisseur de jeton JWT

public void ConfigureServices(IServiceCollection services)
{
   // Authentication
   services.AddAuthentication(options =>    {
      options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
      options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
   }).AddJwtBearer(options =>
   {
      options.Authority = "provider end point";
      options.Audience = "application id or uri as identifier";
      options.TokenValidationParameters.ValidateLifetime = true;
      options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(5);
   });
}

Audience représente le destinataire prévu du jeton entrant ou de la ressource à laquelle le jeton accorde l’accès. Si la valeur spécifiée dans ce paramètre ne correspond pas au paramètre ‘aud’ dans le jeton, celui-ci sera rejeté car il était destiné à être utilisé pour accéder à une ressource différente. Notez que différents fournisseurs de jetons de sécurité ont des comportements différents concernant ce qui est utilisé comme revendication ‘aud’ (certains utilisent l’URI d’une ressource à laquelle un utilisateur souhaite accéder, d’autres utilisent des noms de portée). Assurez-vous d’utiliser une Audience qui a du sens compte tenu des jetons que vous prévoyez d’accepter.

Autority est l’adresse du serveur d’authentification émettant des jetons. Le middleware d’authentification du support JWT utilisera cet URI pour rechercher et récupérer la clé publique pouvant être utilisée pour valider la signature du jeton. Il confirmera également que le paramètre ‘iss’ du jeton correspond à cet URI.

ValidateLifetime valide l’expiration du jeton.

ClockSkew autorise une certaine dérive d’horloge. Je recommande 5 minutes ou moins.

Etape 4: Paramétrer les policies d’autorisation

services.AddAuthorization(opts =>
{
   opts.AddPolicy("SurveyCreator", p =>
   {
      // Using value text for demo show, else use enum : ClaimTypes.Role
      p.RequireClaim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "SurveyCreator");
   });
});

Cette policy nécessite un Claim de type ‘role’ avec la valeur «SurveyCreator».

Etape 5: Protéger vos actions avec l’attribut Authorize

[Authorize(Policy = "SurveyCreator")]
[HttpPost]
public void Post([FromBody] string value)
{
}

Rappelez-vous que si vous ne fournissez pas un jeton à votre Api ou un jeton erroné ou expiré, la réponse sera Http Unauthorized.

Si votre jeton ne satisfait pas à la policy, vous obtiendrez Http Forbidden.

Conclusion

C’était un exemple d’implémentation de la sécurité dans une WebAPI 🙂

Le code source complet peut être trouvé ici.