Umschreiben von Java nach C ++ auf der Android-Plattform

Ich möchte mit Ihnen die Erfahrung des Umschreibens von Java auf C ++ auf der Android-Plattform und die Ergebnisse teilen.

Für Ihr kleines Heimprojekt wurde der Viola-Jones-Gesichtssuchalgorithmus verwendet. Der Java-Quellcode für das Modell wurde mit einer geringfügigen Änderung von code.google.com/p/jviolajones übernommen. Zwei Klassen wurden hinzugefügt: Point und Rectangle. Ich werde klären, warum ich OpenCV für Android nicht verwendet habe - ich muss eine separate Bibliotheksanwendung dafür installieren, was in meinem Fall sehr unpraktisch ist. Die Experimente haben gezeigt, dass es ohne Vorwarnung abgestürzt ist. Ich habe es lange nicht herausgefunden und auch nicht nach anderen Bibliotheken gesucht beschlossen, die einfachste fertige Implementierung zu nehmen.

Die Geschwindigkeit des Algorithmus zeigte katastrophale Ergebnisse, bei einem 400 mal 300 großen Foto auf meinem alten kaputten GT-I9300I - 54 Sekunden, auf avd (virtuelles Gerät) und sogar länger - 250 Sekunden.

Oft bin ich auf meine Diskussion über die Leistung von Code in Java und C ++ gestoßen, irgendwo schien es, als ob Java in einigen Fällen hinterherhinkt und umgekehrt, es gab kleine Codeabschnitte mit einer Schleife. Hier ist der Algorithmus in der Größenordnung von 6 verschachtelten Zyklen etwas komplizierter, wie Sie aus der Quelle ersehen können. Daher wurde die Entscheidung getroffen, unsere eigenen Erfahrungen mit dem Umschreiben in C ++ auszuprobieren. Bei allen Artikeln, die ich las, hatte ich den Eindruck, dass sich die Geschwindigkeit um maximal 20 Prozent erhöhen würde, aber wie sich herausstellte, war es falsch.

Natürlich ergaben sich die folgenden Aufgaben - wie man Eingangs- und Empfangsdaten überträgt und wie man Code umschreibt. Detector hat beschlossen, das Füllen des Modells von XML im Konstruktor auf Java zu belassen. Es füllt sich natürlich nicht schnell, aber da die Arbeit mit XML in C ++ für mich sehr beängstigend klingt, habe ich es so belassen, wie es ist. Mein Beruf ist mit Java verbunden, ich habe nur C \ C ++ am Institut kontaktiert und ein wenig an alten Projekten gearbeitet. Deshalb musste ich ein wenig Dokumentation studieren, Artikel lesen und ein paar Zapfen füllen.

Logik umschreiben. Hier gab es keine besonderen Probleme, eine Methode wurde übernommen - ohne die Klassen kopieren zu wollen, bei denen die Sonnenfinsternis mit einem roten Beil hervorgehoben wurde. Ich habe alle ArrayLists in ein Array konvertiert, zum Glück haben sie die Größe nicht geändert.

Ich werde die Umgebungseinstellung zum Aufrufen von nativem Code nicht beschreiben, es gibt viele Artikel zu diesem Thema.

So übertragen Sie Daten. Bei einfachen Typen - int, float, boolean - ist alles einfach und klar. Mit eindimensional scheint es auch einfach zu sein:

JNIEXPORT jint JNICALL Java_com_example_Computations_intFromJni(JNIEnv* env, jobject thiz, jintArray arr) {
	jsize d = env->GetArrayLength(arr);
	jboolean j;
	int * p = env->GetIntArrayElements(arr, &j);
...
}

Mit zweidimensionalen etwas komplizierter:

JNIEXPORT jint JNICALL Java_com_example_Computations_findFaces(JNIEnv* env, jobject thiz, jobjectArray image) {
	int width = env -> GetArrayLength(image);
	jboolean j2;
	jintArray dim=  (jintArray)env->GetObjectArrayElement(image, 0);
	int height = env -> GetArrayLength(dim);
	int **imageLocal;
	imageLocal = new int*[width];
	for (int i = 0; i < width; i++) {
		jintArray oneDim= (jintArray)env->GetObjectArrayElement(image, i);
		int *element = env->GetIntArrayElements(oneDim, &j2);
		imageLocal[i] = new int[height];
		for(int j=0; j < height; ++j) {
			imageLocal[i][j]= element[j];
		}
	}
...
}

Gehen wir weiter, wie Sie Objekte übergeben, die eine Reihe von Feldern haben, darunter Listentypen. Um ein Objektfeld zu erhalten, wird die folgende Konstruktion angewendet:

jclass clsDetector = env->GetObjectClass(objDetector);
jfieldID sizeFieldId = env->GetFieldID(clsDetector, "size", "Ldetection/Point;");
jobject pointObj = env->GetObjectField(objDetector, sizeFieldId);

Für Blätter benötigen wir zwei Get- und Size-Methoden:

	jfieldID stagesFieldId = env->GetFieldID(clsDetector, "stages", "Ljava/util/List;");
	jobject stagesList = env->GetObjectField(detectorJObj, stagesFieldId);
	jclass listClass = env->FindClass( "java/util/List" );
	jmethodID getMethodIDList = env->GetMethodID( listClass, "get", "(I)Ljava/lang/Object;" );
	jmethodID sizeMethodIDList = env->GetMethodID( listClass, "size", "()I" );
	int listStagesCount = (int)env->CallIntMethod( stagesList, sizeMethodIDList );
	for( int i=0; i < listStagesCount; ++i )
	{
		jobject stage = env->CallObjectMethod( stagesList, getMethodIDList, i);
...

Ich habe gelernt, wie ich Daten bekomme. Wir starten, fällt ein Fehler auf - Lokale Referenztabelle überläuft 512 Einträge. Es stellt sich heraus, dass Sie alle lokalen Jclass- und Jobject-Links bereinigen müssen. Dies geschieht folgendermaßen:

	env->DeleteLocalRef(jcls);
	env->DeleteLocalRef(jobj);

Und auch für Arrays:

	env->ReleaseIntArrayElements(oneDim, element, JNI_ABORT);
	env->DeleteLocalRef(oneDim);

Wir geben das Ergebnis zurück. Um meine Aufgabe zu vereinfachen, habe ich das Ergebnis als Array von Rectangle zurückgegeben:

	jclass cls = env->FindClass("detection/Rectangle");
	jobjectArray jobAr =env->NewObjectArray(faces->currIndex, cls, NULL);
	jmethodID constructor = env->GetMethodID(cls, "", "(IIII)V");
	for (int i = 0; i < faces->currIndex; i++) {
		Rectangle* re = faces->rects[i];
		jobject object = env->NewObject(cls, constructor, re->x, re->y, re->width, re->height);
		env->SetObjectArrayElement(jobAr, i, object);
	}
	return jobAr;

Also, der feierliche Moment - die Suche auf demselben Foto - 14 Sekunden, d. H. 4 mal schneller, auf anderen Fotos ähnliche Ergebnisse. Auf einem virtuellen Android 132 Sekunden vs 300 Sekunden. Aber wie wir wissen, ist es unmöglich, die Ergebnisse eines Experiments zu verwenden, es ist notwendig, mehrere Male zu wiederholen. Ich werde für ein Foto die Verarbeitungszeit in Sekunden angeben.
Virtuelles GerätVirtuelles Gerät mit cppMein Galaxy-HandyMein Galaxy-Handy mit CPP
2381328414
3181375414
4721355414
2641505414
2661385414
2621295314

Und abschließend stelle ich fest. Trotz der Tatsache, dass das Umschreiben eine große Beschleunigung bewirkt hat, sind der Perfektion immer noch viele Grenzen gesetzt. Sie können Multithreading verwenden, das ich in naher Zukunft untersuchen möchte. Und Anpassungen am Algorithmus sind wahrscheinlich der schwierigste Teil.

Update Ich habe die Quellen gepostet . Verwenden Sie wie folgt:
	Detector detector = Detector.create(inputHaas);
	List res = detector.getFaces(background_image, 1.2f, 1.1f, .05f, 2, true, useCpp);

inputHaas ist der Modellstrom, d.h. haarcascade_frontalface_default.xml Datei vom ursprünglichen Algorithmus, useCpp - benutze C ++ oder nicht. In C ++ - Quellen gebe ich keinen Speicher frei, weil schrieb in Eile.

Jetzt auch beliebt: