Outils pour utilisateurs

Outils du site


diy:projets:chromakey

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
diy:projets:chromakey [2018/05/24 12:01] sduranddiy:projets:chromakey [2018/07/02 09:35] (Version actuelle) – [Sources du projet] sdurand
Ligne 13: Ligne 13:
 Il est important de noter que vous aurez besoin d'une installation d'OpenCV sur votre raspberry (j'ai utilisé tout au long du développement la version 3.3.0) et d'un compilateur C++ (de préférence à jour). Il est important de noter que vous aurez besoin d'une installation d'OpenCV sur votre raspberry (j'ai utilisé tout au long du développement la version 3.3.0) et d'un compilateur C++ (de préférence à jour).
 De plus, si vous ne travaillez pas directement sur le raspberry (mais par exemple en ssh), je met à votre disposition un programme permettant de visualiser  De plus, si vous ne travaillez pas directement sur le raspberry (mais par exemple en ssh), je met à votre disposition un programme permettant de visualiser 
-les images traitées aussi simplement que possible (voir [[https://github.com/SDurand7/ChromaKey/tree/master/RemoteDisplay|mon github]]+les images traitées aussi simplement que possible, 
 ou, pour plus d'explications, la rubrique "Vous travaillez à distance" de la partie [[https://wiki.ensfea.fr/doku.php?id=diy:projets:chromakey#le_reste_du_code|"Le reste du code"]]). ou, pour plus d'explications, la rubrique "Vous travaillez à distance" de la partie [[https://wiki.ensfea.fr/doku.php?id=diy:projets:chromakey#le_reste_du_code|"Le reste du code"]]).
 Ce programme nécessite une installation d'**OpenCV** sur votre pc, et **netcat** sur votre pc et le raspberry. Ce programme nécessite une installation d'**OpenCV** sur votre pc, et **netcat** sur votre pc et le raspberry.
Ligne 77: Ligne 77:
 ===== Développement du programme ===== ===== Développement du programme =====
  
-==== La compilation ==== 
- 
-Pour ne pas vous embêter avec la compilation de votre code, je vous recommande fortement d'utiliser le makefile mis à  
-votre disposition [[https://github.com/SDurand7/ChromaKey/tree/master/ChromaKey|ici]], notamment parce qu'il inclut toutes  
-les **options de compilation** nécessaire pour compiler un programme utilisant OpenCV. Ce makefile requiert que votre fichier soit nommé chroma_key.cpp, mais vous êtes évidemment libre de  
-modifier ça à votre convenance. Vous devrez également veiller à ce que ce soit bien votre compilateur C++ qui soit renseigné dans le makefile (variable CC). L'exécutable généré sera nommé **chrk**. 
  
 ==== La fonction de traitement ==== ==== La fonction de traitement ====
Ligne 88: Ligne 82:
 La fonction de traitement (qui est en quelque sorte le coeur de notre code) se contente de **traduire scrupuleusement  La fonction de traitement (qui est en quelque sorte le coeur de notre code) se contente de **traduire scrupuleusement 
 l'algorithme général** (avec quelques subtilitées utiles pour les performances). Pour définir les paramètres votre fonction, sachez que dans OpenCV **une image est de type Mat**. l'algorithme général** (avec quelques subtilitées utiles pour les performances). Pour définir les paramètres votre fonction, sachez que dans OpenCV **une image est de type Mat**.
-(une version complète du code est disponible sur [[https://github.com/SDurand7/ChromaKey/tree/master/ChromaKey|mon github]]) (fichier //chroma_key.cpp//). 
 <code C++> <code C++>
 // frame: notre image à traiter // frame: notre image à traiter
Ligne 126: Ligne 119:
  
 Vous pouvez également voir que j'utilise dans ma condition une fonction nommée //**inBounds**//. Cette fonction prend en paramètre la valeur d'une composante d'un pixel,  Vous pouvez également voir que j'utilise dans ma condition une fonction nommée //**inBounds**//. Cette fonction prend en paramètre la valeur d'une composante d'un pixel, 
-la valeur de notre couleur "à remplacer" et une valeur de tolérance (//l'offset//). Cette fonction est également disponible dans le fichier chroma_key.cpp sur  +la valeur de notre couleur "à remplacer" et une valeur de tolérance (//l'offset//). 
-[[https://github.com/SDurand7/ChromaKey/tree/master/ChromaKey|mon github]] au cas où vous ne souhaiteriez pas l'écrire vous même.+
  
  
Ligne 189: Ligne 181:
  
 <code C++> <code C++>
-   chromakey(frame, B, G, R, offset, background);+chromakey(frame, B, G, R, offset, background);
 </code> </code>
  
Ligne 216: Ligne 208:
  
 Vous devriez obtenir un **main** structuré de cette manière: Vous devriez obtenir un **main** structuré de cette manière:
-   Tant que(votre condition d'arrêt): +<code> 
-   |   Récupérer l'image; +Tant que(votre condition d'arrêt): 
-   |   Traiter l'image; +|   Récupérer l'image; 
-   |    +|   Traiter l'image; 
-   |   cv::imshow(l'image); +|    
-   |   cv::waitKey(un petit temps d'attente); +|   cv::imshow(l'image); 
-        +|   cv::waitKey(un petit temps d'attente); 
-   cv::destroyAllWindows();+     
 +cv::destroyAllWindows(); 
 +</code>
        
 **Il est important de noter que votre raspberry doit être capable d'ouvrir des fenêtres graphiques.** **Il est important de noter que votre raspberry doit être capable d'ouvrir des fenêtres graphiques.**
Ligne 249: Ligne 243:
 images traitées en jpeg pour les transmettre sur le réseau et les afficher en exécutant un autre programme sur le poste  images traitées en jpeg pour les transmettre sur le réseau et les afficher en exécutant un autre programme sur le poste 
 sur lequel vous travaillez. Cette affichage nécessitera comme annoncé au début de ce tutoriel une installation d'OpenCV et un  sur lequel vous travaillez. Cette affichage nécessitera comme annoncé au début de ce tutoriel une installation d'OpenCV et un 
-compilateur C++ sur votre machine (et n'oubliez pas netcat). Le code du programme à exécuter est disponible [[https://github.com/SDurand7/ChromaKey/tree/master/RemoteDisplay|ici]],  +compilateur C++ sur votre machine (et n'oubliez pas netcat). 
-il est accompagné du makefile permettant de le compiler.+
  
 Si vous êtes curieux de savoir ce qui se passe vraiment ici, je vous invite à consulter la documentation d'OpenCV. Si vous êtes curieux de savoir ce qui se passe vraiment ici, je vous invite à consulter la documentation d'OpenCV.
Ligne 256: Ligne 249:
 Une fois vos deux programmes prêts à être exécuté, il ne vous reste plus qu'à ouvrir un terminal sur votre pc et un terminal sur  Une fois vos deux programmes prêts à être exécuté, il ne vous reste plus qu'à ouvrir un terminal sur votre pc et un terminal sur 
 votre raspberry et à exécuter **sur votre poste**: votre raspberry et à exécuter **sur votre poste**:
-<code C+++<code shell
-netcat -l -p un_numéro_de_port | bin/dbgrm+netcat -l -p un_numéro_de_port | dbgrm
 </code> </code>
 Et **sur votre raspberry**: Et **sur votre raspberry**:
-<code C+++<code shell
-bin/chrk paramètres_de_la commande | netcat ip_de_votre_poste le_même_numéro_de_port+chrk paramètres_de_la commande | netcat ip_de_votre_poste le_même_numéro_de_port
 </code> </code>
 +
 +**Le programme exécuter sur votre machine (ci-dessus) se contentera simplement de lire les données de chaque image sur stdin, de les décoder et de les afficher** (cf rubrique "Sources").
  
 **Vous avez terminé !** Cependant, vous êtes probablement déçu de la fluidité de la vidéo si vous utilisez la 2ème méthode. **Vous avez terminé !** Cependant, vous êtes probablement déçu de la fluidité de la vidéo si vous utilisez la 2ème méthode.
Ligne 313: Ligne 308:
  
  
-===== Vidéo de démonstration et sources du projet =====+===== Sources du projet =====
  
-==== Vidéo de démonstration ====+==== Sources du projet ====
  
-Comme vous pourrez le voir dans cette vidéo, on n'obtient pas un framerate suffisant pour un usage  +=== "Émetteur" === 
-en temps réelVous observerez également également que dans des conditions moyennes, une partie de l'image qu'on  +  
-ne souhaiterai pas voir disparaître disparaît (ici, mon bras).+<code C++> 
 +#include <iostream> 
 +#include <chrono> 
 +#include <opencv2/opencv.hpp> 
 +#include <omp.h>
  
-{{youtube>jZObECC9kJc?large}} 
  
-==== Sources du projet ====+#define WIDTH 1280 
 +#define HEIGHT 720 
 +#define FRAME_SIZE WIDTH*HEIGHT*3 
 +#define NUMBER_OF_FRAMES 150
  
-Comme je l'ai répété à plusieurs reprises tout au long de ce tutoriel, les sources de tout le projet sont  +#define ALL_GOOD 0 
-disponibles à [[https://github.com/SDurand7/ChromaKey|cette adresse]]. +#define INVALID_ARGUMENTS -1 
 +#define INVALID_IMAGE -2
  
-Cela inclus à la fois la partie capture/traitement de la vidéo et la partie affichage (étant donné que j'ai utilisé la seconde 
-méthode d'affichage), respectivement dans les répertoires  
-[[https://github.com/SDurand7/ChromaKey/tree/master/ChromaKey|ChromaKey]] et [[https://github.com/SDurand7/ChromaKey/tree/master/RemoteDisplay|RemoteDisplay]].  
  
 +// For a bit of comfort
 +using namespace cv;
 +
 +// Simple function to test if a channel of a pixel (B, G or R) is within the interval [value - offset; value + offset]
 +inline bool inBounds(int value, int bound, int offset) {
 + return abs(value - bound) <= offset;
 +}
 +
 +
 +// Most important part of the code:
 +// Replaces the pixels of the frame close enough (depending of the offset) to the color B, G, R
 +inline void chromakey(Mat& frame, int B, int G, int R, int offset, const Mat& background) {
 + // Using openMP was definitely worth on a Raspberry Pi 3 model B
 + #pragma omp parallel for
 + // Iterating on the rows of our frame
 + for(int i = 0; i < frame.rows; i++) {
 + // Using a pointer to the rows is the fastest way to access a matrix
 + Vec3b *Fi = frame.ptr<Vec3b>(i);
 + const Vec3b *Bi = background.ptr<Vec3b>(i);
 +
 + // Iterating on the elements of each row
 + for(int j = 0; j < frame.cols; j++) {
 + // If the pixel (i, j) of our frame is close enough to B, G and R, we replace it with the color of the background (i, j) pixel
 + if(inBounds(Fi[j][0], B, offset) && inBounds(Fi[j][1], G, offset) && inBounds(Fi[j][2], R, offset)) {
 + for(int k = 0; k < 3; k++) {
 + Fi[j][k] = Bi[j][k];
 + }
 + }
 + }
 + }
 +}
 +
 +
 +int main(int argc, char** argv) {
 + // Just a simple parameters check
 + if(argc !=  6) {
 + std::cerr << "Usage: ./chrk R G B offset /path/to/background_image" << std::endl;
 +
 + return INVALID_ARGUMENTS;
 + }
 +
 + // This Mat contains our background
 + Mat background;
 + background = imread(argv[5]);
 +
 + // Testing that the images size is the same as defined
 + // We could also just reshape the image, it would be a bit less restrictive
 + if(!background.data || background.cols != WIDTH || background.rows != HEIGHT) {
 + std::cerr << "ERROR: Invalid background image" << std::endl;
 +
 + return INVALID_IMAGE;
 + }
 +
 + int B, G, R, offset;
 +
 + // Getting the parameters from the command line
 + std::istringstream rr(argv[1]);
 + std::istringstream rg(argv[2]);
 + std::istringstream rb(argv[3]);
 + std::istringstream rt(argv[4]);
 +
 + rb >> B;
 + rg >> G;
 + rr >> R;
 + rt >> offset;
 +
 + // This Mat will contain the images
 + Mat frame;
 +
 + // Initializing the capture, should be equivalent to: 
 + // VideoCapture cam(0);
 + VideoCapture cam(0);
 +
 + // Setting the width and height of the capture as defined
 + // If not set, default resolution is 640*480
 + cam.set(3, WIDTH);
 + cam.set(4, HEIGHT); 
 +
 + // Adding the OpenCV JPEG parameters to a vector, this is used to encode a Mat with non-default settings
 + std::vector<int> jpg_parameters;
 + jpg_parameters.push_back(CV_IMWRITE_JPEG_QUALITY);
 + jpg_parameters.push_back(75);
 +
 + std::vector<uchar> buffer; 
 +
 + for(int frame_count = 0; frame_count < NUMBER_OF_FRAMES; frame_count++) {
 + const auto start = std::chrono::high_resolution_clock::now();
 +
 + do {
 + cam >> frame;
 + } while(frame.empty());
 +
 + chromakey(frame, B, G, R, offset, background);
 +
 + // Encoding the frame
 + imencode(".jpg", frame, buffer, jpg_parameters);
 + const unsigned int size = buffer.size();
 +
 + // Writing the encoded image to stdout
 + // unsigned int and uchar size are the same on ARM and 64 bits regular computer
 + fwrite(&size, sizeof(unsigned int), 1, stdout);
 + fwrite(buffer.data(), sizeof(uchar), size, stdout);
 +
 + // We use stderr to print an FPS counter and not because we are already writing images on stdout
 + const std::chrono::duration<float> elapsed = std::chrono::high_resolution_clock::now() - start;
 +
 + std::cerr << "\rFPS: " << 1/elapsed.count();
 + }
 + std::cerr << std::endl;
 +
 + return ALL_GOOD;
 +
 +</code>
 +
 +=== "Récepteur" ===
 +
 +<code C++>
 +#include <iostream>
 +#include <chrono>
 +#include <opencv2/opencv.hpp>
 +
 +// WIDTH, HEIGHT and NUMBER_OF_FRAMES should always be set to the same value as in the transmitter
 +#define WIDTH 1280
 +#define HEIGHT 720
 +// Maximum size of the received frame
 +// In theory, if jpeg doesn't compress the image at all, it could be larger than that because of the format overhead
 +// I'm not sure it can ever happen, so, as this program should not be used for critical stuff (it really should not), I'm just gonna assume it won't happen
 +#define FRAME_SIZE WIDTH*HEIGHT*3 
 +#define NUMBER_OF_FRAMES 150
 +
 +#define ALL_GOOD 0
 +
 +
 +
 +int main(int argc, char **argv) {
 + // Actual size of the received frame
 + unsigned int size; 
 + uchar buffer[FRAME_SIZE];
 + cv::Mat frame;
 +
 + for(int frame_count = 0; frame_count < NUMBER_OF_FRAMES; frame_count++) {
 + // Reading the encoded image from stdin
 + // unsigned int and uchar size are the same on ARM and 64 bits regular computer
 + fread(&size, sizeof(unsigned int), 1, stdin);
 + fread(buffer, sizeof(uchar), size, stdin);
 +
 + std::vector<uchar> data(buffer, buffer+size);
 +
 + // Decoding the received frame
 + frame = cv::imdecode(cv::Mat(data), cv::IMREAD_UNCHANGED);
 +
 + // Displaying the received frame
 + cv::imshow("frame", frame);
 +
 + // This wait is mandatory for the image to actually display
 + cv::waitKey(1);
 + }
 +
 + return ALL_GOOD;
 +}
 +</code>
  
 ---- ----
-//Auteur: Sylvain Durand//+//Auteur: S. Durand//
diy/projets/chromakey.1527163274.txt.gz · Dernière modification : 2018/05/24 12:01 de sdurand