3
votes

OpenCV - créer un masque de couleur

Amis! Il y a deux images d'entrée. Une image d'arrière-plan, une autre image de masque. J'ai besoin de la partie colorée du masque.

Ce dont j'ai besoin: background , masque , image résultat a>

Mais j'obtiens quelque chose de complètement différent: background , masque , image résultat

Mon code est en C #:

 //Read files
Mat img1 = CvInvoke.Imread(Environment.CurrentDirectory + "\\Test\\All1.jpg");
Mat img = CvInvoke.Imread(Environment.CurrentDirectory + "\\Test\\OriginalMask.jpg");

// Threshold and MedianBlur mask
CvInvoke.Threshold(img, img, 0, 255, Emgu.CV.CvEnum.ThresholdType.BinaryInv);
CvInvoke.MedianBlur(img, img, 13);

// without this conversion, an error appears: (mtype == CV_8U || mtype == CV_8S) && _mask.sameSize(*psrc1)
CvInvoke.CvtColor(img, img, Emgu.CV.CvEnum.ColorConversion.Rgb2Gray);

CvInvoke.BitwiseNot(img1, img1, img);

//Save file
img1.Save(Environment.CurrentDirectory + "\\Test\\Result.jpg");

Premier question: comment puis-je obtenir le résultat indiqué dans l'image?

La deuxième question: pourquoi j'obtiens une erreur si je ne convertis pas le masque: (mtype == CV_8U || mtype == CV_8S) && _mask.sameSize (* psrc1)

La troisième question: comment créer un fond transparent au lieu d'un arrière-plan blanc dans l'image finale?

La solution n'a pas besoin d'être en C #. La solution convient à n'importe quel langage de programmation, car la syntaxe OpenCV est à peu près la même. Merci d'avance.


1 commentaires

Concernant votre première et troisième question, veuillez lire ma réponse ci-dessous. Pour la deuxième question: je pense que img est (également) chargé comme une image à trois canaux (couleur), car il n'y a pas de spécificateur défini. threshold et medianBlur fonctionnent également sur les images couleur, tandis que bitwise_not ne fonctionne que sur les images à un seul canal (niveaux de gris). Par conséquent, le message d'erreur s'il n'y a pas de conversion au préalable.


3 Réponses :


3
votes

J'utiliserai C ++ dans ma réponse, car je suis le plus familier avec celui-ci.

Voici ma suggestion:

// Load background as color image.
cv::Mat background = cv::imread("background.jpg", cv::IMREAD_COLOR);

// Load mask image as grayscale image.
cv::Mat mask = cv::imread("mask.jpg", cv::IMREAD_GRAYSCALE);

// Start time measurement.
auto start = std::chrono::system_clock::now();

// There are some artifacts in the JPG...
cv::threshold(mask, mask, 128, 255, cv::THRESH_BINARY);

// Initialize result image.
cv::Mat result = background.clone().setTo(cv::Scalar(255, 255, 255));

// Copy pixels from background to result image, where pixel in mask is 0.
for (int x = 0; x < background.size().width; x++)
    for (int y = 0; y < background.size().height; y++)
        if (mask.at<uint8_t>(y, x) == 0)
            result.at<cv::Vec3b>(y, x) = background.at<cv::Vec3b>(y, x);

// End time measurement.
auto end = std::chrono::system_clock::now();

// Output duration duration.
std::chrono::duration<double> elapsed_seconds = end - start;
std::cout << elapsed_seconds.count() << "\n";

// Write result.
cv::imwrite("result.png", result);

// Start time measurement.
start = std::chrono::system_clock::now();

// Generate new image with alpha channel.
cv::Mat resultTransparent = cv::Mat(result.size(), CV_8UC4);

// Copy pixels in BGR channels from result to transparent result image.
// Where pixel in mask is not 0, set alpha to 0.
for (int x = 0; x < background.size().width; x++)
{
    for (int y = 0; y < background.size().height; y++)
    {
        resultTransparent.at<cv::Vec4b>(y, x)[0] = result.at<cv::Vec3b>(y, x)[0];
        resultTransparent.at<cv::Vec4b>(y, x)[1] = result.at<cv::Vec3b>(y, x)[1];
        resultTransparent.at<cv::Vec4b>(y, x)[2] = result.at<cv::Vec3b>(y, x)[2];

        if (mask.at<uint8_t>(y, x) != 0)
            resultTransparent.at<cv::Vec4b>(y, x)[3] = 0;
        else
            resultTransparent.at<cv::Vec4b>(y, x)[3] = 255;
    }
}

// End time measurement.
end = std::chrono::system_clock::now();

// Output duration duration.
elapsed_seconds = end - start;
std::cout << elapsed_seconds.count() << "\n";

// Write transparent result.
cv::imwrite("resultTransparent.png", resultTransparent);

Résultats dans ces deux sorties (vous ne verra pas la transparence sur la deuxième image ici sur fond blanc StackOverflow):

 Fond blanc

 Arrière-plan transparent a >


5 commentaires

Merci pour la solution proposée. Dites-moi, combien de temps l'opération entière est-elle effectuée en millisecondes, sans compter le temps de lecture et d'écriture dans le fichier?


J'ai édité ma réponse et ajouté deux mesures de temps à l'aide de chrono, c'est-à-dire que vous devez ajouter #include . En mode de libération, la première opération nécessite environ 12 ms et la seconde environ 39 ms. Mais je doute fortement que ce soient des chiffres significatifs - aucune optimisation utilisée, quel est le système exécutant le code, ....


plutôt interessant. sur mon processeur Intel Core i5-8250U, la première opération prend 0,47 seconde et la deuxième opération prend 1,4 seconde


C'est ce que j'obtiens (à peu près) en mode débogage - vous l'avez vérifié?


je regarde les informations de la console std :: cout << elapsed_seconds.count () << "\ n";



1
votes

Juste pour ajouter un petit détail à la réponse de HanseHirse :

Si vous ajoutez un flou gaussien au masque (comme vous l'avez fait dans votre question avec CvInvoke.MedianBlur (img, img, 13); ) les bords du masque seront plus lisses et l'image de sortie sera plus jolie lorsqu'elle sera placée au-dessus d'une autre image .

Vous pouvez le faire en définissant simplement le quatrième canal de l'image de sortie directement sur le masque flou.

Donc au lieu de

resultTransparent.at<cv::Vec4b>(y, x)[3] = mask.at<uint8_t>(y, x);

vous pouvez essayer

if (mask.at<uint8_t>(y, x) != 0)
            resultTransparent.at<cv::Vec4b>(y, x)[3] = 0;
        else
            resultTransparent.at<cv::Vec4b>(y, x)[3] = 255;


1 commentaires

Vous avez tout à fait raison. J'ai laissé la partie "jolies" à l'auteur de la question. Merci pour votre ajout.



1
votes

Même résultat en Python, s'il peut vous inspirer:

import cv2
import numpy as np
# Read files
img1 = cv2.imread("All1.jpg",cv2.IMREAD_COLOR);
img = cv2.imread("OriginalMask.jpg",cv2.IMREAD_GRAYSCALE)  # loading in grayscale

# Threshold and MedianBlur mask
_, img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)  # corrected to 127 instead of 0
img = cv2.medianBlur(img, 13)

# fill with white
dest= np.full(np.shape(img1),255,np.uint8)

# Assuming dst and src are of same sizes
# only copy values where the mask has color > 0
dest[img>0] = img1[img>0]  # after @T.Kau's suggestion


cv2.imshow('dest',dest)
cv2.waitKey(0)
cv2.destroyAllWindows()


1 commentaires

En python, vous pouvez enregistrer quelques lignes en faisant dest [img> 0] = img1 [img> 0]