Opencv, native libs, maven und UnsatisfiedLinkError

Ich beziehe mich mal auf fogenden Thread Byte-Welt - Projekte / Projects - Byte-Welt - Die Welt des Programmierens

und folgenden Text von @Marco13

After dozens of reports of „UnsatisfiedLinkErrors“, this issue finally seemed to be resolved by packing the natives into the JAR and managing them completely transparently for the user.

Was habe ich:
Opencv 3.0.0 mit Java-Bindings auf einem Linux System sind gebaut. Dabei kamen ein Jar-File und ein libopencv_java300.so heraus.

Daraus habe ich dann zwei Jars gebaut und ins lokale maven-repository installiert.
Bin nach dieser Anleitung vorgegangen Introduction to OpenCV Development with Clojure — OpenCV 2.4.11.0 documentation
Diese bezieht sich zwar auf clojure und leiningen, aber installiert Javaprojekte genauso zuverlässig in ein maven-repo. Das hat also soweit geklappt.

Wenn ich die Jars nun in einem Projekt einbinde, dann müsste ich ja eigentlich alles zur Verfügung haben, was ich brauche.

Das einzige Problem, dass ich nun noch habe ist, das Laden der libopencv_java300.so

Packe ich die Datei libopencv_300.so irgendwo hin und setze den java.library.path auf das beinhaltende Verzeichnis dann funktioniert das ganze und ich kann die lib mit System.loadLibrary("opencv_java300"); laden und das ganze verwenden.

Was ich allerdings gerne hätte, ist dass ich ein einziges jar habe und dieses per Doppelklick ausführen kann.

Und jetzt habe ich das Problem, dass ich diese lib nicht referenzieren kann, wenn sie in einer jar steckt.

Mit clojure hingegen, kann ich die maven-dependencies referenzieren und dann in einer REPL das ganze mit (clojure.lang.RT/loadLibrary org.opencv.core.Core/NATIVE_LIBRARY_NAME) laden und verwenden. Aber da ist eh noch etwas mehr „Magie“ im Spiel. Der Name verweist auf das Verzeichnis target/native/linux/x86_64. Scheint also kein ganz unmögliches unterfangen zu sein.

Und die Frage die sich mir stellt ist, wie du @Marco13 das ganz transparent für den User mit der JOCL löst. Bzw. was sich hier als Best-Practice erweist.

Eine Idee die mir vorschwebt ist die so-Datei beim Start nach temp zu kopieren und von dort zu laden.

[QUOTE=MrEuler]
Opencv 3.0.0 mit Java-Bindings auf einem Linux System sind gebaut. Dabei kamen ein Jar-File und ein libopencv_java300.so heraus.

Das einzige Problem, dass ich nun noch habe ist, das Laden der libopencv_java300.so

Packe ich die Datei libopencv_300.so irgendwo hin und setze den java.library.path auf das beinhaltende Verzeichnis dann funktioniert das ganze und ich kann die lib mit System.loadLibrary("opencv_java300"); laden und das ganze verwenden.

Was ich allerdings gerne hätte, ist dass ich ein einziges jar habe und dieses per Doppelklick ausführen kann.

Und jetzt habe ich das Problem, dass ich diese lib nicht referenzieren kann, wenn sie in einer jar steckt.

Und die Frage die sich mir stellt ist, wie du @Marco13 das ganz transparent für den User mit der JOCL löst. Bzw. was sich hier als Best-Practice erweist.

Eine Idee die mir vorschwebt ist die so-Datei beim Start nach temp zu kopieren und von dort zu laden.[/QUOTE]

Letzteres ist im wesentlichen das, was gemacht wird.

Zu bestimmten Maven-spezifischen Dingen werde ich nicht viel sagen können. Es gibt da dieses „NAR-Plugin“ für Native Libraries, und hier ist das auch für JOCL erwähnt (und auf den Thread verlinkt, in dem ich Hasstiraden über Maven ablasse :rolleyes: ). Aber als das „für mich sinnvollste“ (und praktikabelste) erschien es dann, die Natives einfach mit in die JAR packen zu lassen (das geht ja mit vergleichsweise wenig Aufwand. Die Compilierung der nativen libs bettet sich damit zwar nicht gut in einen Workflow ein, aber da die schon fast prinzipbedingt auf unterschiedlichen Systemen compiliert werden müssen, macht das auch keinen großen Unterschied…).
In dem Zusammenhang verstehe ich das target/native/linux/x86_64 auch nicht ganz. Das hat ja mit der deployten JAR nichts zu tun, sondern ist nur ein Unterverzeichnis des Verzeichnisses, wo die landet - wie und wann das aufgelöst werden soll (oder ob einfach gesagt wird: „Da sind die Natives, kopier’ sie hin, wo du sie brauchst“) erschließt sich mir nicht ganz…

Wie auch immer: Ursprünglich hatte ich die natives unabhängig von der JAR-Datei abgelegt. Dann kann man sie ganz normal in den java.library.path legen und laden und gut. Aber kagge für Maven. Deswegen werden sie jetzt mit in die JAR gepackt. Wenn sie geladen werden müssen, werden

  • Die „resourcen“ aus der JAR gelesen (also die passende native lib fürs aktuelle OS)
  • Sie als Datei ins temp-Verzeichnis gelegt
  • Von dort geladen

Es gibt da ein paar Fragen und Freiheitsgrade (teilweise auch im verlinkten Thread angesprochen). Im speziellen: Wie handlet man das mit der temp-Datei? Man kann mit File#createTempFile zwar eine Temp-Datei erstellen, und mit File#deleteOnExit sagen, dass sie beim Beenden der VM gelöscht werden SOLL - aber wenn diese temp-Datei eine DLL unter Windows ist, dann KANN sie nach dem Laden nicht mehr ohne weiteres gelöscht werden (die Lösung, die Fancy im verlinkten Thread vorgeschlagen hatte, mit Reflection-Hack und Shutdown-Hook, mal außen vor gelassen). Das hat (und hatte, ganz konkret, bei JOCL) zur Folge, dass Leute sich beschwert haben, dass nach 1000 Starts eben 1000 Temp-Dateien rumflogen :expressionless: Jetzt ist es bei JOCL so, dass die Native Datei (mit Versionsnummer) nur EINmal ins Temp-Verzeichnis gelegt, wird, FALLS sie noch nicht existiert.

Das ganze wird in JOCL jetzt relativ JOCL-unabhängig und durch die JOCL/LibUtils.java at master · gpu/JOCL · GitHub - Klasse gelöst. (Es sind nur zwei JOCL-„spezifische“ Funktionen drin). Die versucht zuerst, die Native Library als Datei aus dem java.library.path zu laden. Wenn das nicht klappt, versucht sie, sie wie oben beschrieben als Resource über die Temp-File zu laden.

Wenn die Native(s) für OpenCV mit in die JAR gepackt werden sollen, müßte ggf. der Buildprozess dafür angepasst werden (es würde aber auch gehen, die native händisch mit in die JAR zu packen).

EDIT: Vielleicht sollte ich auch mal „neuere“ Dokumentation dazu lesen, sowas wie Projects With JNI - Maven User - Codehaus könnte weitere hilfreiche (Maven-spezifischere) Tipps enthalten

Besten Dank für die ausführliche Antwort.

Dann lag ich gedanklich nicht ganz falsch. Ich werde mich dann wohl an der LibUtils-Variante orientieren. Das scheint mir mittelfristig die wohl stabilste Lösung zu sein.

Beachte aber auch die folgende Diskussion (schon für den Fall, dass mal du oder irgendjemand SBT verwenden will :D)

http://forum.byte-welt.net/byte-welt-projekte-projects/swogl-jcuda-jocl/jocl/16429-unsatisfiedlinkerror-native-library-tmp-libjocl_0_1_9-linux-x86_64-loade.html#post117984

(Eine Entscheidung habe ich da aber noch nicht getroffen…)

Hatte mir jetzt etwas zusammengebaut, dass mir Open-CV geladen hat. Datei aus dem Jar nach temp kopieren und dann direkt von dort laden. Hat soweit auch alles funktioniert.

Habe mich dann aber letzt endlich dazu entschieden das native Zeug rauszuwerfen und die wesentlichen Funktionen, die nicht allzu viele waren, durch ImageJ zu ersetzen.

Vorteil: Plattformunabhängig und da die Anwendung auch auf einem Raspberry PI laufen soll ohne sehr Zeitaufwendiges kompilieren für die entsprechende Architektur in Betrieb zu nehmen.

Performance ist für den Anwendungsfall ausreichend.

Qualität, also die Erkennungsrate (75%) ist nach der ersten Inaugenscheinnahme sogar leicht besser als die Lösung mit Open-CV (68%).
Mache etwas Objekterkennung.

Ansonsten werde ich hier nun eher etwas Zeit darin investieren, die richtigen Parameter zu setzen. Mal schauen ob etwas Machine-Learning weiterhilft.

Mit JOCL hast du leider nicht die Möglichkeit auf den Native-Kram zu verzichten. Dennoch Danke für die Mühe und deine ausführliche Antwort.