Laden...

Freistellen eines Bitmap klappt bei einigen Bildern nicht

Erstellt von johannes0405 vor 2 Jahren Letzter Beitrag vor 2 Jahren 1.383 Views
J
johannes0405 Themenstarter:in
9 Beiträge seit 2021
vor 2 Jahren
Freistellen eines Bitmap klappt bei einigen Bildern nicht

Guten Abend zusammen,
ich hänge jetzt schon seit einigen Tagen an der Freistellung eines Bitmaps fest.
Ich möchte anhand eines Rectangles ein Gesicht ausschneiden.
Dies mache ich mit Bitmap.Clone().
Bei einigen Bildern kappt es auch, bei einigen jedoch nicht. Der Ausschnitt ist viel zu klein.
Mir ist aufgefallen, dass es immer bei Bildern klappt, bei denen keine Informationen zur Auflösung gespeichert sind.

Gibt es einen Weg, die Auflösung zu berücksichtigen? Bitmap.SetResolution bringt nichts.
Hier mein Code:


public Image cropImage(Image img, Rectangle cropArea)
        {                           
            Bitmap bmpImage = new Bitmap(img);
            bmpImage.SetResolution(800, 800);                        
            return bmpImage.Clone(cropArea, bmpImage.PixelFormat);
        }

Viele Grüße,

T
2.219 Beiträge seit 2008
vor 2 Jahren

Auf Stack Overflow gibt es einen Lösungsansatz, der vermutlich helfen kann.
how-to-resize-an-image-c-sharp

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

J
johannes0405 Themenstarter:in
9 Beiträge seit 2021
vor 2 Jahren

@T-Virus: Danke für deine Antwort, hat leider keine Abhilfe geschaffen.

T
2.219 Beiträge seit 2008
vor 2 Jahren

Sollte eigentlich nicht sein, da ich eine ähnliche Methode auch verwendet habe und diese resized meine Image Instanzen korrekt.
Wie sieht dein Code aus?
Bzw. wie verwendest du das Ergebnis?

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

J
johannes0405 Themenstarter:in
9 Beiträge seit 2021
vor 2 Jahren

Mein Code sieht nun wie folgt aus:


 public Image cropImage(Image img, Rectangle cropArea)
        {           
            Bitmap bmpImage = new Bitmap(cropArea.Width, cropArea.Height);
            bmpImage.SetResolution(800, 800);

            using(var g = Graphics.FromImage(bmpImage))
            {
                g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;

                using (var wrapMode = new ImageAttributes())
                {
                    wrapMode.SetWrapMode(System.Drawing.Drawing2D.WrapMode.TileFlipXY);
                    g.DrawImage(img, cropArea, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, wrapMode);
                }
            }
                        
#if DEBUG
            bmpImage.Save("C:\\Users\\User\\Pictures\\Scans\\test.jpg", ImageFormat.Jpeg);
#endif
            return bmpImage;
        }

Derzeit speichere ich das Bild nur erstmal in einem Verzeichnis ab.
Die Funktion liefert nun bei mir in keinem Fall ein brauchbares Ergebnis.

Folgendes wäre ggf. noch gut zu wissen:
Die Rectangles zum freistellen stammen bei mir wahlweise aus der Azure Gesichtserkennung oder werden freihand auf ein Canvas gezeichnet, welches das Image im Hintergrund hat.

16.806 Beiträge seit 2008
vor 2 Jahren

Wenn Du mit der Azure API arbeitest, dann stimmt vielleicht die Logik nicht..?
Die Face API gibt Dir ja die erkannten Gesichter in ner Liste an, und jedes erkannte Gesicht liefert ein FaceRectangle.
Dort sind die Rectangles aber nicht als Koordinaten hinterlegt, sondern als Positionen (Height, Left, Right, Width).
Man erkennts nun am Code nicht, aber hast Du das beachtet, dass Du das beim Croppen erst umrechnen musst?

Wir haben damit eigentlich kein Problem gehabt die Gesichter raus zu schneiden oder ähnliche Effekte wie Du.
Wir arbeiten aber direkt mit GitHub - SixLabors/ImageSharp: A modern, cross-platform, 2D Graphics library for .NET und mit der Quelle als Bitmaps, ohne Image. Evtl. macht das Image noch irgendwas damit, was bei Dir blockt.


using (MemoryStream ms = new MemoryStream())
using (Image image = Image.Load(ms, out IImageFormat format))
{
    var clone = image.Clone(
          i => i.Resize(width, height).Crop(new Rectangle(x, y, cropWidth, cropHeight))
    );

    clone.Save(outStream, format);
}

Ich mag da bei Grafikoperationen nicht ganz bewandert zu sein, aber müsste man nicht erst das Bild laden und dann croppen?
Du hast erzeugst erst die Zielfläche und schreibst dann das Image rein....?

C
2.121 Beiträge seit 2010
vor 2 Jahren

Vorweg ich hab zu Gesichtserkennung überhaupt keinen Einblick.

Mich wundert dass hier die Auflösung des Bilds eine Rolle spielen soll. Sollten das nicht alles absolute Pixelangaben sein? Da sollte es völlig irrelevant sein welche Auflösung man dem Bild andichtet, die es später vielleicht beim Ausdruck oder sonstiger Darstellung haben soll.

J
johannes0405 Themenstarter:in
9 Beiträge seit 2021
vor 2 Jahren

@Abt:
Ich habe es jetzt auch mal mit Imagesharp versucht.
Ein Problem habe ich jetzt nur:
Wenn ich das Bild speichere, wird mir angezeigt, dass Dateiformat nicht unterstützt wird.

J
johannes0405 Themenstarter:in
9 Beiträge seit 2021
vor 2 Jahren

Edit:
Mit der ImageSharp habe ich es nun ans Laufen bekommen.
Jedoch habe ich hier die gleichen Probleme wie zuvor.
Einige Bilder lassen sich korrekt croppen und einige croppen an falscher Stelle.
Die, bei denen es klappt haben eine Auflösung von 96.

16.806 Beiträge seit 2008
vor 2 Jahren

Wenn Du die falschen Begriffe verwendest, dann wird die Lösungssuche für Dich und für uns schwer.
Ich hätte immer noch null Ahnung gehabt, was Du mit Auflösung meinst, wenn Du nicht die 96 als Zahl genannt hättest. Das ist nicht die Auflösung, sondern die Maßeinheit der Auflösung, auch Pixeldichte genannt.

Die DPI kannst Du mit SetResolution an der Bitmap setzen.

J
johannes0405 Themenstarter:in
9 Beiträge seit 2021
vor 2 Jahren

Entschuldige, bin auf dem Gebiet auch relativ neu.
Das hatte ich bereits versucht. Macht keinen Unterschied.
Vllt noch ein paar mehr Details

Hier der aktuelle Quellcode:


using (var inStream = new MemoryStream(bildByte))
                using (var outStream = new MemoryStream())                    
                using (var image1 = SixLabors.ImageSharp.Image.Load(inStream, out IImageFormat format))
                {                         
                    
                    image1.Mutate(
                        i => i.Resize(image1.Width, image1.Height)
                              .Crop(new SixLabors.ImageSharp.Rectangle(x, y, width, height)));
                   
                    image1.Save("C:\\Users\\User\\Pictures\\Scans\\test.jpeg");            
                }

Ich habe auch nochmal die Bilder angehangen.

T
2.219 Beiträge seit 2008
vor 2 Jahren

Zwei Anmerkungen.

  1. Der outStream ist ungenutzt, kannst du weglassen.
  2. Dein Resize bekommt die Größe/Breite vom Bild selbst, entsprechend ändert sich auch an dem nichts.
    Vermutlich musst du hier deine eigene Höhe/Brite hingeben, so wie bei Crop schon geschehen?

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

J
johannes0405 Themenstarter:in
9 Beiträge seit 2021
vor 2 Jahren

@T-Virus:
Wenn ich das mache, bekomme ich folgenden Fehler:
"Crop rectangle should be smaller than the source bounds."

16.806 Beiträge seit 2008
vor 2 Jahren

Kurze Frage: kopierst Du nur planlos Code und probierst wild nach try-and-error rum, oder willst Du auch verstehen, was Du da machst? 😉

Deine Fehlermeldung sagt ja deutlich, dass das Quellbild kleiner als das Rectangle ist. Das kann ja nicht sein.
Dein gefundenes Rectangle kann niemals größer sein als das Bild. Les die Bitmap / ImageSharp Doku, verstehe das System. Debugge Dein Code und schau nach, wo der Fehler ist.
Gerade wenn man neu in dem Thema ist muss man halt viel lesen. Das ist der Job eines Entwicklers.

J
johannes0405 Themenstarter:in
9 Beiträge seit 2021
vor 2 Jahren

Nene, ich probiere und lese mich da schon seit mehr als einer Woche durch.
Ich verstehe mehr oder weniger das System, ich verstehe nur nicht, warum es bei dem einen Bild klappt und beim anderen nicht.

Und dass das Quellbild nicht größer als das Rectangle sein kann ist klar, wenn man die Größe auf die des Rectangles setzt. Hatte nur das Feedback zu deinem T-Virus weitergeben wollen.
Weil ich schon vieles probiert habe, klammere ich mich mittlerweile an jeden Strohhalm.

C
2.121 Beiträge seit 2010
vor 2 Jahren

Sind die Werte für die neue Bildgröße schlüssig? Vergleiche sie mit dem Bild was als Resultat herauskommt. Dann siehst du ob deine Crop Routine nicht passt, oder ob die neue Bildgröße schon nicht stimmt.
Wenn die Methoden zur Gesichtserkennung schon unsinnige Werte zurückgeben, kann natürlich kein sinniges Bild mehr herauskommen.

Und wie gesagt, dass das immer nur bei der Auflösung von 96 funktioniert ist wirklich komisch. Die Auflösung hat meiner Meinung hier überhaupt nichts zu suchen.

J
johannes0405 Themenstarter:in
9 Beiträge seit 2021
vor 2 Jahren

So gestern Abend im Bett ist mir die entscheidende Lösung eingefallen.
Die Standard Windows Auflösung ist ja 96 DPI. Deswegen funktioniert es auch mit Bildern die eine Auflösung von 96 DPI besitzen.
Die Bilder mit einer abweichenden Auflösung muss ich also auf die DPI runterrechnen.
D. h. x, y, width und height des Rectangles für den Bildausschnitt müssen angepasst werden auf die Auflösung des Bildes.
Mache ich jetzt wie folgt:
Beispiel anhand der Width:


var widthneu = (int)((rectangle.Width / 96.0) * image1.Metadata.HorizontalResolution);