Opencv c++ Bild mit Maske kopieren



  • Hallo allerseits,

    ich hoffe ich bin hier richtig, falls nicht bitte verschieben.
    Ich habe angefangen mit opencv und c++ in die Welt der Bildbearbeitung einzusteigen, jetzt steh ich vor der ersten Hürde und weiß nicht wie ich sie meistern soll. Vielleicht könnt ihr mir einen Tipp geben oder in irgendeiner Art und Weise helfen.

    Folgendes Problem habe ich. Ich lese ein Bild ein und verändere dieses (Weichzeichner) anschließend möchte ich bestimmte Bereiche die ich in einer Maske(8bit Graustufenbild) maskiert habe aus dem Weichgezeichnetem-Bild kopieren und in neues Bild einfügen. Mit welcher funktion kann man dies am besten bewerkstelligen?

    Gruß && ein schönes Wochenende.



  • Wenn deine Maske nur "ja" oder "nein" pro Pixel sagen soll, kannst du

    void Mat::copyTo(OutputArray m, InputArray mask) const
    

    benutzen.

    Falls deine Maske aber einen pro-Pixel-Blend-Faktor darstellt, der sagt, wieviel an der Stelle jeweils von Quellbild_A und somit nicht von Quellbild_B genommen werden soll, benoetigst du eine Funktion, die jeden Zielpixel wie folgt berechnet:
    Zielpixel = Blendfaktor * Pixel_Quelle_A + (1-Blendfaktor) * Pixel_Quelle_B
    Deine Maske musst du dafür natürlich in irgendeinem Schritt aus [0, 255] nach [0.0, 1.0] konvertieren. Das kann aber auch direkt in der Pixelfunktion geschehen.
    Damit du dir keinen eigenen Loop schreiben musst, der über die Pixel iteriert, kannst du das (vorausgesetzt du hast die Bilder vorher in ein zusammenpassendes Format umgewandelt) auch über vorhandene Funktionen machen.

    #include <opencv2/opencv.hpp>
    #include <iostream>
    using namespace cv;
    int main()
    {
    	Mat source_a(imread("a.png", CV_LOAD_IMAGE_COLOR));
    	Mat source_b(imread("b.png", CV_LOAD_IMAGE_COLOR));
    	Mat alpha_mask(imread("mask.png", CV_LOAD_IMAGE_COLOR));
    	source_a.convertTo(source_a, CV_32FC3, 1.0/255.0);
    	source_b.convertTo(source_b, CV_32FC3, 1.0/255.0);
    	alpha_mask.convertTo(alpha_mask, CV_32FC3, 1.0/255.0);
    	Mat dest = source_a.mul(alpha_mask) + source_b.mul(Scalar(1.0, 1.0, 1.0) - alpha_mask);
    	dest.convertTo(dest, CV_8UC3, 255.0);
    	imwrite("dest.png", dest);
    }
    

    Die Beispielbilder fuer den Code gibt es hier: http://daiw.de/share/index.php?dir=Forum%2F2013-04-26_cpp%2F



  • Vielen Dank, das war es was ich gesucht habe. Danke 👍



  • Wunderbar. Verrätst du mir noch, obs das erste oder das zweite war? 😉



  • Na klar, es war das zweite.

    Kannst du mir vielleicht noch einen keinen Tipp geben?
    Ich versuche ein Transparentes PNG(selbe Größe wie der Hintergrund) über den Hintergrund zu legen. Ich nehme an ich muss das Bild auch wieder Pixel für Pixel auslesen und dann über den Hintergrundbildpixel 😉 legen aber wie mache ich das mit der Transparenz?

    Gruß



  • Das ist doch genau das gleiche und auch von meinem Beispiel abgedeckt. 😉
    http://daiw.de/share/Forum/2013-04-26_cpp/mask.png ist in dem Fall dann halt der Alpha-Kanal vom Bild, dass du drüberlegen willst.
    Falls du die PNG direkt mit Alpha-Kanal als 4-Kanal-Bild im Speicher hast, kannst du dir den vierten Kanal mit cv::split ( http://opencv.willowgarage.com/documentation/cpp/core_operations_on_arrays.html#split ) rausholen.
    Aber Laden mit cv::imread ( http://opencv.willowgarage.com/documentation/cpp/highgui_reading_and_writing_images_and_video.html?highlight=imread#imread ) verwirft den Alpha-Kanal.

    <0 the loaded image will be loaded as-is (note that in the current implementation the alpha channel, if any, is stripped from the output image, e.g. 4-channel RGBA image will be loaded as RGB if flags >= 0 ).



  • Moin Dobi,

    Kann ich meine Maske nicht via.

    Mat alpha_mask2(imread("maske2.png", CV_LOAD_IMAGE_UNCHANGED));

    laden? Dabei sollte sie ja unangetastet bleiben und auch noch den Alpha-Kanal behalten oder liege ich da falsch. Nur mit der Split-funktion stehe ich noch auf dem Kriegsfuß. 😡



  • Für CV_LOAD_IMAGE_UNCHANGED steht doch bestimmt irgendwo ein #define im header, was das auf -1 setzt. Und dann bist du wieder hier:

    <0 the loaded image will be loaded as-is (note that in the current implementation the alpha channel, if any, is stripped from the output image, e.g. 4-channel RGBA image will be loaded as RGB if flags >= 0 ).

    OpenCV kann keine Alpha-Kanäle. Auch nicht einlesen.

    Falls maske2.png ein rgba-Bild ist, mach halt mal

    Mat alpha_mask2(imread("maske2.png", CV_LOAD_IMAGE_UNCHANGED));
    std::cout << alpha_mask2.channels() << "\n";
    

    Das wird dann 3 sein, und nicht 4. Der Alpha-Kanal, der in deiner maske2.png drin ist, ist dann weg. (Wenn da allerdings doch 4 stehen sollte, kannst du das Ding raussplitten und wie im Beispiel oben verwernden.)
    Du kannst höchstens schon mit irgendnem anderen Programm/lib den alpha-Kanal als 1-Kanal-Bild irgendwo ablegen wie ich oben in meinem Beispiel.

    OpenCV ist halt mehr auf computer vision (Bilder zerlegen) und nicht auf solche Malerein (Bilder zusammenbauen) ausgelegt. Für sowas ist lib imagemagick oder CImg eventuell besser geeignet.



  • Ich ziehe alles zurück und behaupte das Gegenteil. 😉
    (Entweder ist die Doku veraltet oder ich lese sie falsch.)

    OpenCV (zumindeste meine Version 4.2, die ich auf dem Rechner hier gerade installiert habe) kann mit CV_LOAD_IMAGE_UNCHANGED *doch* den Alpha-Kanal einer PNG mit laden. Man bekommt dann tatsächlich eine cv::Mat mit 4 channels, und dann kannst du genau das machen, was ich meinte. Hier der code:

    #include <opencv2/opencv.hpp>
    #include <iostream>
    using namespace cv;
    using namespace std;
    int main()
    {
        Mat bg(imread("bg.png", CV_LOAD_IMAGE_COLOR)); // BGR
        Mat img(imread("Png-logo.png", CV_LOAD_IMAGE_UNCHANGED)); // BGRA
        cout << "img.channels(): " << img.channels() << "\n"; // 4, yeah!
    
        // Bild in die 4 Kanaele splitten.
        Mat b, g, r, a;
        vector<Mat> img_channels;
        img_channels.push_back(b);
        img_channels.push_back(g);
        img_channels.push_back(r);
        img_channels.push_back(a);
        split(img, img_channels);
    
    	// Ohne Alpha-Kanal wieder zusammenkleben.
        Mat alpha_mask(img_channels[3]);
        img_channels.pop_back();
        merge(img_channels, img);
    
        // Alpha Kanal auf 3 Kanaele ausbreiten.
        vector<Mat> alpha_channels;
        alpha_channels.push_back(alpha_mask);
        alpha_channels.push_back(alpha_mask);
        alpha_channels.push_back(alpha_mask);
        merge(alpha_channels, alpha_mask);
    
     	// float wie ueblich
        bg.convertTo(bg, CV_32FC3, 1.0/255.0);
        img.convertTo(img, CV_32FC3, 1.0/255.0);
        alpha_mask.convertTo(alpha_mask, CV_32FC3, 1.0/255.0);
    
        Mat dest = img.mul(alpha_mask) + bg.mul(Scalar(1.0, 1.0, 1.0) - alpha_mask);
    
        // zurueck aus der Floatigkeit
        dest.convertTo(dest, CV_8UC3, 255.0);
    
        imwrite("dest.png", dest);
    }
    

    Die Bilder dazu liegen hier: http://daiw.de/share/index.php?dir=Forum%2F2013-04-28_cpp%2F

    Statt die Kanaele (ab Zeile 20) wieder zusammenzumergen koennte man das Bild auch einfach nochmal mit CV_LOAD_IMAGE_COLOR laden. Dann haette man es auch ohne Alpha-Kanal. Aber du willst ja lernen wie split und merge funktioniert. Deshalb habe ich das so gemacht. 😉



  • Danke Dobi, das hat mir geholfen 🙂

    Ich habe ein kleines Problem bei der Umwandlung, ich versuche folgende Berechnung und erhalte diese Fehlermeldung

    Mat C= ((B > A) ? A:B);

    Fehler: »cv::operator>(const cv::Mat&, const cv::Mat&)(((const cv::Mat)(& A)))« konnte nicht von »cv::MatExpr« nach »bool« umgewandelt werden

    😞



  • Da hast du aber Glück, dass ich deinen Edit zufällig noch gesehen hab.

    Was willst du denn da machen?
    C soll A sein wenn B größer ist als A und ansonsten B?
    also sowas wie C=min(A,B)?
    Was soll der operator> denn da von A und B vergleichen? Die Anzahl Zeilen oder Spalten? Die Summe aller Elemente?



  • Hallo Dobi,

    ich habe eine Internet-Seite gefunden auf der Blend-Modies von Photoshop erklärt sind und wollte manche übernehmen.

    #define ChannelBlend_Normal(A,B)     ((uint8)(A))
    #define ChannelBlend_Lighten(A,B)    ((uint8)((B > A) ? B:A))
    #define ChannelBlend_Darken(A,B)     ((uint8)((B > A) ? A:B))
    #define ChannelBlend_Multiply(A,B)   ((uint8)((A * B) / 255))
    #define ChannelBlend_Average(A,B)    ((uint8)((A + B) / 2))
    #define ChannelBlend_Add(A,B)        ((uint8)(min(255, (A + B))))
    #define ChannelBlend_Subtract(A,B)   ((uint8)((A + B < 255) ? 0:(A + B - 255)))
    #define ChannelBlend_Difference(A,B) ((uint8)(abs(A - B)))
    #define ChannelBlend_Negation(A,B)   ((uint8)(255 - abs(255 - A - B)))
    #define ChannelBlend_Screen(A,B)     ((uint8)(255 - (((255 - A) * (255 - B)) >> 8)))
    #define ChannelBlend_Exclusion(A,B)  ((uint8)(A + B - 2 * A * B / 255))
    #define ChannelBlend_Overlay(A,B)    ((uint8)((B < 128) ? (2 * A * B / 255):(255 - 2 * (255 - A) * (255 - B) / 255)))
    #define ChannelBlend_SoftLight(A,B)  ((uint8)((B < 128)?(2*((A>>1)+64))*((float)B/255):(255-(2*(255-((A>>1)+64))*(float)(255-B)/255))))
    #define ChannelBlend_HardLight(A,B)  (ChannelBlend_Overlay(B,A))
    #define ChannelBlend_ColorDodge(A,B) ((uint8)((B == 255) ? B:min(255, ((A << 8 ) / (255 - B)))))
    #define ChannelBlend_ColorBurn(A,B)  ((uint8)((B == 0) ? B:max(0, (255 - ((255 - A) << 8 ) / B))))
    #define ChannelBlend_LinearDodge(A,B)(ChannelBlend_Add(A,B))
    #define ChannelBlend_LinearBurn(A,B) (ChannelBlend_Subtract(A,B))
    #define ChannelBlend_LinearLight(A,B)((uint8)(B < 128)?ChannelBlend_LinearBurn(A,(2 * B)):ChannelBlend_LinearDodge(A,(2 * (B - 128))))
    #define ChannelBlend_VividLight(A,B) ((uint8)(B < 128)?ChannelBlend_ColorBurn(A,(2 * B)):ChannelBlend_ColorDodge(A,(2 * (B - 128))))
    #define ChannelBlend_PinLight(A,B)   ((uint8)(B < 128)?ChannelBlend_Darken(A,(2 * B)):ChannelBlend_Lighten(A,(2 * (B - 128))))
    #define ChannelBlend_HardMix(A,B)    ((uint8)((ChannelBlend_VividLight(A,B) < 128) ? 0:255))
    #define ChannelBlend_Reflect(A,B)    ((uint8)((B == 255) ? B:min(255, (A * A / (255 - B)))))
    #define ChannelBlend_Glow(A,B)       (ChannelBlend_Reflect(B,A))
    #define ChannelBlend_Phoenix(A,B)    ((uint8)(min(A,B) - max(A,B) + 255))
    #define ChannelBlend_Alpha(A,B,O)    ((uint8)(O * A + (1 - O) * B))
    #define ChannelBlend_AlphaF(A,B,F,O) (ChannelBlend_Alpha(F(A,B),A,O))
    

    Nur Funktionieren die mit True or False Operator "?" nicht 😞



  • Da geht es um Skalare (Pixelwerte). Was sind in deinem code

    Mat C= ((B > A) ? A:B);
    

    A und B denn für Typen? cv::Mat?



  • Hallo,

    genau A und B sind Mat Typen.

    Mat A(imread("Bild1.jpg", CV_LOAD_IMAGE_COLOR));
    Mat B(imread("Bild2.jpg", CV_LOAD_IMAGE_COLOR));

    Gruß



  • Gut, also machst du da jetzt das:

    Mat A(imread("Bild1.jpg", CV_LOAD_IMAGE_COLOR));
    Mat B(imread("Bild2.jpg", CV_LOAD_IMAGE_COLOR));
    Mat C= ((B > A) ? A:B);
    

    Und das ist das gleiche wie das:

    Mat A(imread("Bild1.jpg", CV_LOAD_IMAGE_COLOR));
    Mat B(imread("Bild2.jpg", CV_LOAD_IMAGE_COLOR));
    Mat C = std::min(A, B);
    

    Und das ist das gleiche wie das:

    Mat A(imread("Bild1.jpg", CV_LOAD_IMAGE_COLOR));
    Mat B(imread("Bild2.jpg", CV_LOAD_IMAGE_COLOR));
    Mat C;
    if (B > A)
    {
        C = A;
    }
    else
    {
        C = B;
    }
    

    Du willst das aber pixelweise haben, jedoch verrätst du deinem Compiler das nicht. 😉



  • Hier ist mein Code

    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include <stdio.h>
    #include <iostream>
    
    using namespace std;
    using namespace cv;
    char window_name[] = "Mein Fenster";
    
     int main( int argc, char** argv )
     {
    
    Mat A(imread("Bild1.jpg", CV_LOAD_IMAGE_COLOR));
    Mat B(imread("Bild2.jpg", CV_LOAD_IMAGE_COLOR));
    
    Mat C;
    if (B > A)
    {
        C = A;
    }
    else
    {
        C = B;
    }
    
    imshow( window_name, C );
    waitKey(0);
    
    return 0;
    }
    

    Wenn ich versuche es zu kompilieren bekomme ich immer folgende Fehlermeldung :

    mul.cpp: In Funktion »int main(int, char**)«:
    mul.cpp:29:10: Fehler: »cv::operator>(const cv::Mat&, const cv::Mat&)(((const cv::Mat)(& A)))« konnte nicht von »cv::MatExpr« nach »bool« umgewandelt werden



  • Ja, schon klar. Was soll der Compiler beispielsweise auch mit dieser Zeile anfangen?

    if (B > A)
    

    Wann ist eine Matrix größer als eine andere?

    Was du willst, sind pixelweise Vergleiche. Die machst du aber gar nicht.



  • Na gut, will ich mal nicht so sein. 😉

    #include <opencv2/opencv.hpp>
    using namespace cv;
    int main()
    {
    	Mat img_a(imread("1.png"));
    	Mat img_b(imread("2.png"));
    
    	// Beide Bilder sollten gleich gross sein.
    	assert(img_a.size() == img_b.size());
    
    	// Beide Bilder sollten vom gleichen Typ sein.
    	assert(img_a.type() == img_b.type());
    
    	// darken blend
    	Mat img_c(cv::min(img_a, img_b));
    
    	// Speichern
    	imwrite("out.png", img_c);
    }
    

    Für dein Channel-Blend-Darken bekommt es also noch so hin. Jenachdem, was du vor hast, musst du aber eh früher oder später lernen, wie man auf die einzelnen Pixel zugreift. Hier der gleiche Algorithmus nur per Hand:

    #include <opencv2/opencv.hpp>
    using namespace cv;
    int main()
    {
    	Mat img_a(imread("1.png", CV_LOAD_IMAGE_GRAYSCALE));
    	Mat img_b(imread("2.png", CV_LOAD_IMAGE_GRAYSCALE));
    
    	// Beide Bilder sollten gleich gross sein.
    	assert(img_a.size() == img_b.size());
    
    	// Beide Bilder sollten vom gleichen Typ sein.
    	assert(img_a.type() == img_b.type());
    
    	// Zielbild anlegen
    	Mat img_c(img_a.size(), img_a.type());
    
    	// darken blend
    	typedef unsigned char byte;
    	for (int y = 0; y < img_c.rows; ++y)
    	{
    		for (int x = 0; x < img_c.cols; ++x)
    		{
    			byte a = img_a.at<byte>(y, x);
    			byte b = img_b.at<byte>(y, x);
    			img_c.at<byte>(y, x) = b > a ? a : b; // img_c.at<byte>(y, x) = std::min(a, b)
    		}
    	}	
    
    	// Speichern
    	imwrite("out.png", img_c);
    }
    

    Hier die Bilder dazu: http://daiw.de/share/index.php?dir=Forum%2F2013-05-03_cpp%2F

    Was hast du eigentlich generell vor? Ich frage, weil das, was du da tust, hat ja erstmal wenig mit Computer Vision zu tun.



  • Hallo Dobi,

    vielen Dank für deinen Post, schon habe ich wieder was gelernt, ich versuche generell immer noch meine Filter die ich bisher in Imagemagick vorliegen habe mit opencv zu realisieren im Moment laufen die Filter die ich fertig habe mit ca. 0.08 ms bis 0.5ms. Imagemagick im Gegensatz dazu mit 1.5s bis 3s und das obwohl Imagemgick mit mehreren CPUś arbeitet.

    Gruß



  • Das ist interessant. Normalerweise würde man ja erwarten, dass Imagemagick schon ziemlich durchoptimiert ist. Kann es sein, dass du bei deiner Version nur die Filterei an sich mit deiner Stopuhr misst und bei Imagemagick alles, also Programm starten, Bilder laden, Filtern und Ergebnis speichern?



  • Ich mache mit Imagemagick die selben Arbeitsschritte wie mit opencv, sprich Bild laden, Maske laden, Maske an Bildgröße anpassen, Maske mit Bild verrechnen und Bild anschließend speichern.

    Ich messe die Zeit mit dem "time" Befehl

    loonix@ubuntu:~/opencv-2.4.5/test$ time ./mul

    real 0m0.041s
    user 0m0.036s
    sys 0m0.004s

    Ich habe mal deinen zweiten Programmcode kopiert und bei mir eingefügt.
    Wenn ich den Modus der beiden Bilder von GRAYSCALE zu COLOR ändere und das ganz ausführe, wird zwar die Ausgabedatei erstellt aber das Bild ist nur zu einem drittel vorhanden der Rest ist Schwarz. Wo dran kann das liegen?

    Gruß


Anmelden zum Antworten