5 Stimmen

IOS: Rechteckiges Bild aus dem Hintergrundbild abrufen

Ich arbeite an einer Implementierung, bei der ich ein rechteckförmiges Bild in einem großen Hintergrundbild habe. Ich versuche, das rechteckförmige Bild aus dem großen Bild zu extrahieren und Textinformationen aus diesem bestimmten Rechteckbild abzurufen. Ich versuche, das Open-CV-Drittanbieterframework zu verwenden, konnte aber das Rechteckbild nicht aus dem großen Hintergrundbild extrahieren. Könnte mir jemand bitte sagen, wie ich das erreichen kann?

Aktualisiert:

Ich habe den Link gefunden, um mit OpenCV quadratische Formen zu finden. Kann ich ihn für die Suche nach Rechtecken modifizieren? Kann mir jemand dabei helfen?

NEUESTES UPDATE:

Ich habe den Code schließlich gefunden, hier ist er unten.

    - (cv::Mat)cvMatWithImage:(UIImage *)image
{
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
    CGFloat cols = image.size.width;
    CGFloat rows = image.size.height;

    cv::Mat cvMat(rows, cols, CV_8UC4); // 8 Bits pro Komponente, 4 Kanäle

    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                 // Zeiger auf Datenspeicher
                                                    cols,                       // Breite des Bitmaps
                                                    rows,                       // Höhe des Bitmaps
                                                    8,                          // Bits pro Komponente
                                                    cvMat.step[0],              // Bytes pro Zeile
                                                    colorSpace,                 // Farbraum
                                                    kCGImageAlphaNoneSkipLast |
                                                    kCGBitmapByteOrderDefault); // Bitmap-Infobits

    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
    CGContextRelease(contextRef);

    return cvMat;
}
-(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat
{
    NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];
    CGColorSpaceRef colorSpace;
    if ( cvMat.elemSize() == 1 ) {
        colorSpace = CGColorSpaceCreateDeviceGray();
    }
    else {
        colorSpace = CGColorSpaceCreateDeviceRGB();
    }

    //CFDataRef data;
    CGDataProviderRef provider = CGDataProviderCreateWithCFData( (CFDataRef) data ); // Es sollte (__bridge CFDataRef)data sein
    CGImageRef imageRef = CGImageCreate( cvMat.cols, cvMat.rows, 8, 8 * cvMat.elemSize(), cvMat.step[0], colorSpace, kCGImageAlphaNone|kCGBitmapByteOrderDefault, provider, NULL, false, kCGRenderingIntentDefault );
    UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease( imageRef );
    CGDataProviderRelease( provider );
    CGColorSpaceRelease( colorSpace );
    return finalImage;
}
-(void)forOpenCV
{
    imageView = [UIImage imageNamed:@"myimage.jpg"];
    if( imageView != nil )
    {
        cv::Mat tempMat = [imageView CVMat];

        cv::Mat greyMat = [self cvMatWithImage:imageView];
        cv::vector > squares;

        cv::Mat img= [self debugSquares: squares: greyMat];

        imageView = [self UIImageFromCVMat: img];

        self.imageView.image = imageView;
    }
}

double angle( cv::Point pt1, cv::Point pt2, cv::Point pt0 ) {
    double dx1 = pt1.x - pt0.x;
    double dy1 = pt1.y - pt0.y;
    double dx2 = pt2.x - pt0.x;
    double dy2 = pt2.y - pt0.y;
    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

- (cv::Mat) debugSquares: (std::vector >) squares : (cv::Mat &)image
{
    NSLog(@"%lu",squares.size());

    // Unschärfe verbessert Kantenerkennung

    //cv::Mat blurred(image);
    cv::Mat blurred = image.clone();
    medianBlur(image, blurred, 9);

    cv::Mat gray0(image.size(), CV_8U), gray;
    cv::vector > contours;

    // Rechtecke in jeder Farbebene des Bildes finden
    for (int c = 0; c < 3; c++)
    {
        int ch[] = {c, 0};
        mixChannels(&image, 1, &gray0, 1, ch, 1);

        // Verschiedene Schwellenwerte ausprobieren
        const int threshold_level = 2;
        for (int l = 0; l < threshold_level; l++)
        {
            // Canny anstelle von Null-Schwellenwert verwenden!
            // Canny hilft bei der Erkennung von Quadraten mit Gradientenschattierung
            if (l == 0)
            {
                Canny(gray0, gray, 10, 20, 3); //

                // Dilatation hilft, potenzielle Löcher zwischen Kantenabschnitten zu entfernen
                dilate(gray, gray, cv::Mat(), cv::Point(-1,-1));
            }
            else
            {
                gray = gray0 >= (l+1) * 255 / threshold_level;
            }

            // Konturen finden und in einer Liste speichern
            findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

            // Konturen überprüfen
            cv::vector approx;
            for (size_t i = 0; i < contours.size(); i++)
            {
                // Kontur annähernd mit Genauigkeit proportional
                // zum Umfang der Kontur
                approxPolyDP(cv::Mat(contours[i]), approx, arcLength(cv::Mat(contours[i]), true)*0.02, true);

                // Hinweis: Der absolute Wert einer Fläche wird verwendet, da
                // die Fläche positiv oder negativ sein kann - entsprechend der
                // Konturorientierung
                if (approx.size() == 4 &&
                    fabs(contourArea(cv::Mat(approx))) > 1000 &&
                    isContourConvex(cv::Mat(approx)))
                {
                    double maxCosine = 0;

                    for (int j = 2; j < 5; j++)
                    {
                        double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                        maxCosine = MAX(maxCosine, cosine);
                    }

                    if (maxCosine < 0.3)
                        squares.push_back(approx);
                }
            }
        }
    }

    NSLog(@"squares.size(): %lu");

    for( size_t i = 0; i < squares.size(); i++ )
    {
        cv::Rect rectangle = boundingRect(cv::Mat(squares[i]));
        NSLog(@"rectangle.x: %d", rectangle.x);
        NSLog(@"rectangle.y: %d", rectangle.y);

        if(i==squares.size()-1)////Hier Rechteck erkennen
        {
            const cv::Point* p = &squares[i][0];

            int n = (int)squares[i].size();

            NSLog(@"%d",n);

            line(image, cv::Point(507,418), cv::Point(507+1776,418+1372), cv::Scalar(255,0,0),2,8);

            polylines(image, &p, &n, 1, true, cv::Scalar(255,255,0), 5, CV_AA);

            int fx1=rectangle.x;
                NSLog("X: %d", fx1);
            int fy1=rectangle.y;
                NSLog("Y: %d", fy1);
            int fx2=rectangle.x+rectangle.width;
                NSLog("Width: %d", fx2);
            int fy2=rectangle.y+rectangle.height;
                NSLog("Height: %d", fy2);

            line(image, cv::Point(fx1,fy1), cv::Point(fx2,fy2), cv::Scalar(0,0,255),2,8);

        }

    }

    return image;
}

Vielen Dank.

6voto

foundry Punkte 31329

Hier ist eine vollständige Antwort unter Verwendung einer kleinen Wrapper-Klasse, um den C++-Code vom Objective-C-Code zu trennen.

Ich musste eine weitere Frage auf Stackoverflow stellen, um mit meinem schlechten C++-Wissen umzugehen - aber ich habe alles herausgefunden, was wir brauchen, um den C++ sauber mit dem Objective-C-Code zu verbinden, unter Verwendung des Beispiels des squares.cpp-Beispielcodes. Ziel ist es, den ursprünglichen C++-Code so sauber wie möglich zu halten und den Großteil der Arbeit mit openCV in reinen C++-Dateien für (un)Portabilität zu belassen.

Ich habe meine ursprüngliche Antwort belassen, da dies über eine Bearbeitung hinausgeht. Das vollständige Demo-Projekt ist auf GitHub

CVViewController.h / CVViewController.m

  • reines Objective-C

  • kommuniziert mit openCV C++-Code über einen WRAPPER... es weiß weder noch kümmert es sich darum, dass C++ diese Methodenaufrufe hinter dem Wrapper verarbeitet.

CVWrapper.h / CVWrapper.mm

  • Objective-C++

macht so wenig wie möglich, wirklich nur zwei Dinge...

  • Aufrufe von UIImage objC++-Kategorien zur Konvertierung von und zu UIImage <> cv::Mat
  • vermittelt zwischen CVViewController's obj-C-Methoden und CVSquares C++ (Klassen-)Funktionsaufrufen

CVSquares.h / CVSquares.cpp

  • reines C++
  • CVSquares.cpp deklariert öffentliche Funktionen innerhalb einer Klassendefinition (in diesem Fall eine statische Funktion).
    Dies ersetzt die Arbeit von main{} in der Originaldatei.
  • Wir versuchen, CVSquares.cpp so nah wie möglich am C++-Original zu halten, um die Portabilität zu gewährleisten.

CVViewController.m

//entferne 'magische Zahlen' aus dem ursprünglichen C++-Quelltext, damit wir sie von Obj-C aus manipulieren können
#define TOLERANCE 0.01
#define THRESHOLD 50
#define LEVELS 9

UIImage* image =
        [CVSquaresWrapper detectedSquaresInImage:self.image
                                       tolerance:TOLERANCE
                                       threshold:THRESHOLD
                                          levels:LEVELS];

CVSquaresWrapper.h

//  CVSquaresWrapper.h

#import 

@interface CVSquaresWrapper : NSObject

+ (UIImage*) detectedSquaresInImage:(UIImage*)image
                          tolerance:(CGFloat)tolerance
                          threshold:(NSInteger)threshold
                             levels:(NSInteger)levels;

@end

CVSquaresWrapper.mm

//  CVSquaresWrapper.mm
//  Wrapper, der mit C++ und Obj-C-Klassen spricht

#import "CVSquaresWrapper.h"
#import "CVSquares.h"
#import "UIImage+OpenCV.h"

@implementation CVSquaresWrapper

+ (UIImage*) detectedSquaresInImage:(UIImage*) image
                          tolerance:(CGFloat)tolerance
                          threshold:(NSInteger)threshold
                             levels:(NSInteger)levels
{
    UIImage* result = nil;

        //Konvertierung von UIImage in das cv::Mat openCV-Bildformat
        //Dies ist eine Kategorie auf UIImage
    cv::Mat matImage = [image CVMat]; 

        //Aufruf der C++-Klassenstatischen Mitgliedsfunktion
        //Wir möchten, dass diese Funktionssignatur genau 
        //das Formular des Aufrufsmethode widerspiegelt 
    matImage = CVSquares::detectedSquaresInImage (matImage, tolerance, threshold, levels);

        //Konvertierung zurück vom cv::Mat OpenCV-Bildformat
        //zum UIImage-Bildformat (Kategorie auf UIImage)
    result = [UIImage imageFromCVMat:matImage]; 

    return result;
}

@end

CVSquares.h

//  CVSquares.h

#ifndef __OpenCVClient__CVSquares__
#define __OpenCVClient__CVSquares__

    //Klassendefinition
    //In diesem Beispiel brauchen wir keine Klasse 
    //da wir keine Instanzvariablen und nur eine statische Funktion haben. 
    //Wir könnten stattdessen die Funktion nur deklarieren, aber diese Form scheint klarer zu sein

class CVSquares
{
public:
    static cv::Mat detectedSquaresInImage (cv::Mat image, float tol, int threshold, int levels);
};

#endif /* defined(__OpenCVClient__CVSquares__) */

CVSquares.cpp

//  CVSquares.cpp

#include "CVSquares.h"

using namespace std;
using namespace cv;

static int thresh = 50, N = 11;
static float tolerance = 0.01;

    //Deklarationen hinzugefügt, damit wir unsere 
    //öffentliche Funktion nach oben in die Datei verschieben können
static void findSquares(  const Mat& image,   vector >& squares );
static void drawSquares( Mat& image, vector >& squares );

    //Diese öffentliche Funktion erfüllt die Rolle von 
    //main{} in der Originaldatei (main{} wird gelöscht)
cv::Mat CVSquares::detectedSquaresInImage (cv::Mat image, float tol, int threshold, int levels)
{
    vector > squares;

    if( image.empty() )
        {
        cout << "Konnte nicht laden " << endl;
        }

    tolerance = tol;
    thresh = threshold;
    N = levels;
    findSquares(image, squares);
    drawSquares(image, squares);

    return image;
}

//Der Rest dieser Datei ist identisch mit der ursprünglichen squares.cpp, außer:
//main{} ist entfernt
//Diese Zeile wird aus drawSquares entfernt: 
//imshow(wndname, image); 
//(Obj-C wird das Zeichnen übernehmen)

UIImage+OpenCV.h

Die UIImage-Kategorie ist eine ObjC++-Datei, die den Code zur Konvertierung zwischen UIImage- und cv::Mat-Bildformaten enthält. Hier verschieben Sie Ihre beiden Methoden -(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat und - (cv::Mat)cvMatWithImage:(UIImage *)image

//UIImage+OpenCV.h

#import 

@interface UIImage (UIImage_OpenCV)

    //cv::Mat zu UIImage
+ (UIImage *)imageFromCVMat:(cv::Mat&)cvMat;

    //UIImage zu cv::Mat
- (cv::Mat)CVMat;

@end        

Die Methodenimplementierungen hier sind unverändert von Ihrem Code (obwohl wir kein UIImage zur Konvertierung übergeben, stattdessen beziehen wir uns auf self)

2voto

foundry Punkte 31329

Hier ist eine teilweise Antwort. Es ist nicht vollständig, weil ich versuche, genau dasselbe zu tun und bei jedem Schritt enorme Schwierigkeiten habe. Mein Wissen über Objective-C ist ziemlich stark, aber sehr schwach in C++

Du solltest diesen Guide zum Umwickeln von C++ lesen hier

Und alles auf Ievgen Khvedchenias Computer Vision Talks Blog, besonders das OpenCV Tutorial. Ievgen hat auch ein erstaunlich umfassendes Projekt auf github gepostet, das zum Tutorial passt.

Trotzdem habe ich immer noch viele Probleme dabei, OpenCV reibungslos zu kompilieren und auszuführen.

Zum Beispiel, Ievgens Tutorial läuft prima als fertiges Projekt, aber wenn ich versuche, es von Grund auf neu zu erstellen, erhalte ich die gleichen OpenCV-Kompilierfehler, die mich die ganze Zeit plagten. Es ist wahrscheinlich mein schlechtes Verständnis von C++ und seiner Integration mit Obj.-C.

In Bezug auf squares.cpp

Was du tun musst

  • Entferne int main(int /*argc*/, char** /*argv*/) aus squares.cpp
  • Entferne imshow(wndname, image); aus drawSquares (Obj.-C wird das Zeichnen übernehmen)
  • Erstelle eine Header-Datei squares.h
  • Erstelle eine oder zwei öffentliche Funktionen in der Header-Datei, die du von Obj.-C aus aufrufen kannst (oder von einem Obj.-C/C++ Wrapper)

Hier ist, was ich bisher habe...

class squares
{
public:
         static cv::Mat& findSquares( const cv::Mat& image, cv::vector >& squares );
         static cv::Mat& drawSquares( cv::Mat& image, const cv::vector >& squares );

};

Du solltest dies auf eine einzige Methode reduzieren können, sagen wir processSquares mit einem Eingang cv::Mat& image und einem Rückgabewert cv::Mat& image. Diese Methode würde squares deklarieren und findSquares und drawSquares innerhalb der .cpp-Datei aufrufen.

Der Wrapper wird ein Eingabe-UIImage nehmen, es in cv::Mat image umwandeln, processSquares mit diesem Eingang aufrufen und ein Ergebnis cv::Mat image erhalten. Dieses Ergebnis wird wieder in ein NSImage umgewandelt und an die aufrufende Objc-Funktion übergeben.

Das ist ein grober Entwurf dessen, was wir tun müssen. Ich werde versuchen, diese Antwort zu erweitern, sobald ich tatsächlich etwas davon geschafft habe!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X