2017-08-10 3 views
4

Je charge des images PNG dotées d'un plan de transparence. Lorsque converties en niveaux de gris, les zones transparentes de l'image apparaissent en noir, ce qui semble être l'arrière-plan par défaut. J'ai besoin qu'ils soient blancs à la place. Que puis-je faire ?Définition de la couleur d'arrière-plan d'un fichier PNG avec transparence

[Ce n'est pas la question habituelle sur la façon de préserver la transparence.]

+1

Le plan de transparence est-il uniforme? (c'est-à-dire combien de valeurs le composant alpha peut-il prendre? Deux?) Première idée: [dessiner l'image sur une image de fond blanc] (https://stackoverflow.com/questions/20957433/how-to-draw-a-transparent- image-sur-live-caméra-feed-in-opencv # 20958245) avant de convertir en niveaux de gris. Deuxième idée: convertir en niveaux de gris d'abord, puis itérer sur chaque pixel et transformer en blanc dans la nouvelle image tous les pixels qui ont dans l'image originale leur [composant alpha] (https://stackoverflow.com/questions/18491998/creating-transparent-image -in-opencv) en dessous d'un seuil donné. –

+0

@GabrielDevillers: en fait je ne sais pas. Je lis le PNG et obtient l'image en niveaux de gris avec les pixels noirs. Je ne vois pas le plan alpha. –

+0

@YvesDaoust Comment chargez-vous l'image? Je suppose que les niveaux de gris, en fonction de votre description. Vous devrez le charger avec 'IMREAD_UNCHANGED', de sorte que vous obteniez l'image complète à 4 canaux et que vous effectuiez un post-traitement. –

Répondre

0

Si vous lisez un PNG avec imread sans passer IMREAD_UNCHANGED alors vous aurez une image BGR 3 canaux. S'il y avait un quatrième canal alpha (0 = entièrement transparent, 255 = entièrement visible), alors est recadré comme documentation put it.

Vous obtenez des pixels noirs avec des pixels transparents simplement parce que la partie BGR du pixel donne une couleur noire. (Vec3b(0, 0, 0)).

Si vous n'êtes pas convaincu, essayez d'ouvrir comme BGR (imread wihout paramètre IMREAD_UNCHANGED) et l'affichage (imshow puis waitkey les deux images ci-dessous:

original logo from wikipediaenter image description here

Alors qu'ils se ressemblent sur cette page ou dans Gimp, le premier devrait avoir un fond noir alors que le second devrait avoir un fond rouge

Première solution: utiliser Michael Jep fils de overlayImage function

#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgcodecs.hpp> 
int main(int argc, char** argv) { 
    cv::Mat img_4_channels; 
    img_4_channels = cv::imread(argv[1], cv::IMREAD_UNCHANGED); // gives 8UC4 
    // img_4_channels = cv::imread(argv[1]); // inappropriate: gives 8UC3 

    cv::Mat background = cv::Mat(img_4_channels.size(), CV_8UC3, cv::Vec3b(255, 255, 255)); // white background 
    overlayImage(background, img_4_channels, img_3_channels, cv::Point2i(0, 0)); 

    cv::imshow("3 channels", img_3_channels); 
} 

Deuxième solution: this answer par Rosa Gronchi

Cette solution est plus légère (coordonnée pas de premier plan, pas besoin d'allouer une image d'arrière-plan).

0

La façon la plus efficace (mémoire et CPU) serait de laisser libPNG le faire, en utilisant png_set_background:

Si vous n'avez pas besoin, ou ne peut pas gérer, le canal alpha, vous pouvez appeler png_set_background() pour l'enlever en composant avec une couleur fixe . N'appelez pas png_set_strip_alpha() pour cela - cela laissera fausses valeurs de pixels dans les parties transparentes de cette image.

png_set_background(png_ptr, &background_color, 
    PNG_BACKGROUND_GAMMA_SCREEN, 0, 1); 

Le background_color est une valeur RVB ou en niveaux de gris selon le format des données libpng produira pour vous.

Malheureusement, l'emballage OpenCV autour libpng ne pas utiliser, de sorte que vous auriez à patcher dans un certain soutien rudimentaire vous (entravée par la capacité limitée à passer des options supplémentaires à imread).

Une autre approche possible consisterait simplement à écrire votre propre chargeur d'image simple en utilisant libPNG dans ce but spécifique.

Si vous pouvez vous permettre un peu de gaspillage, chargez-le en tant que BGRA et effectuez un post-traitement. Cependant, je vais aller plus loin que le code mentionné par Gabriel et incorporer la conversion de couleur en elle.

void remove_transparency(cv::Mat const& source 
    , cv::Mat& destination 
    , uint8_t background_color) 
{ 
    CV_Assert(source.type() == CV_8UC4); 

    destination.create(source.rows, source.cols, CV_8UC1); 

    auto it_src(source.begin<cv::Vec4b>()), it_src_end(source.end<cv::Vec4b>()); 
    auto it_dest(destination.begin<uint8_t>()); 

    std::transform(it_src, it_src_end, it_dest 
     , [background_color](cv::Vec4b const& v) -> uchar 
     { 
      // Conversion constants taken from cvtColor docs... 
      float gray(v[0] * 0.114f + v[1] * 0.587f + v[2] * 0.299f); 
      float alpha(v[3]/255.0f); 
      return cv::saturate_cast<uchar>(gray * alpha + background_color * (1 - alpha)); 
     } 
     ); 
} 

Bien sûr, cela est encore un seul thread, donc nous allons levier cv::parallel_for_ pour améliorer un peu plus loin.

class ParallelRemoveTransparency 
    : public cv::ParallelLoopBody 
{ 
public: 
    ParallelRemoveTransparency(cv::Mat const& source 
     , cv::Mat& destination 
     , uint8_t background_color) 
     : source_(source) 
     , destination_(destination) 
     , background_color_(background_color) 
    { 
     CV_Assert(source.size == destination.size); 
    } 

    virtual void operator()(const cv::Range& range) const 
    { 
     cv::Mat4b roi_src(source_.rowRange(range)); 
     cv::Mat1b roi_dest(destination_.rowRange(range)); 

     std::transform(roi_src.begin(), roi_src.end(), roi_dest.begin() 
      , [this](cv::Vec4b const& v) -> uint8_t { 
       float gray(v[0] * 0.114f + v[1] * 0.587f + v[2] * 0.299f); 
       float alpha(v[3]/255.0f); 
       return cv::saturate_cast<uint8_t>(gray * alpha + background_color_ * (1 - alpha)); 
      } 
      ); 
    } 

private: 
    cv::Mat const& source_; 
    cv::Mat& destination_; 
    uint8_t background_color_; 
}; 

void remove_transparency(cv::Mat const& source 
    , cv::Mat& destination 
    , uint8_t background_color) 
{ 
    CV_Assert(source.type() == CV_8UC4); 

    destination.create(source.rows, source.cols, CV_8UC1); 

    ParallelRemoveTransparency parallel_impl(source, destination, background_color); 
    cv::parallel_for_(cv::Range(0, source.rows), parallel_impl); 
} 

Il se trouve que vous avez besoin de ce en Python. Voici un petit brouillon d'une alternative:

import numpy as np 
import cv2 

def remove_transparency(source, background_color): 
    source_img = cv2.cvtColor(source[:,:,:3], cv2.COLOR_BGR2GRAY) 
    source_mask = source[:,:,3] * (1/255.0) 

    background_mask = 1.0 - source_mask 

    bg_part = (background_color * (1/255.0)) * (background_mask) 
    source_part = (source_img * (1/255.0)) * (source_mask) 

    return np.uint8(cv2.addWeighted(bg_part, 255.0, source_part, 255.0, 0.0)) 


img = cv2.imread('smile.png', -1) 
result = remove_transparency(img, 255) 

cv2.imshow('', result) 
cv2.waitKey() 
+0

Merci pour toutes les informations précieuses. Je travaille sous Python, donc l'implémentation est un peu hors de question et c'est pourquoi je cherchais une solution plus OpenCVish. –

+0

Ahh, je vois. Il n'y avait pas de tag de langue dans la question, donc en fonction de votre profil, j'ai supposé que vous vouliez du C++ :) –

+0

@YvesDaoust Ajout d'une solution Python. Je ne suis pas sûr que vous puissiez faire beaucoup mieux facilement. –