首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >成功加载OpenCV本机库Spring引导应用程序,但在使用

成功加载OpenCV本机库Spring引导应用程序,但在使用
EN

Stack Overflow用户
提问于 2020-07-06 03:43:16
回答 2查看 745关注 0票数 1

我试图在Spring中使用OpenCV。OpenCV jar及其本机库是从源代码构建的,并放置在我的引导jar中。我已经将它们从jar中提取出来并成功加载,但是在使用时会引发错误。以下是我尝试的总结

  • 使用来自OpenCV的开孔包。工作得很好,当我再次用System.loadLibrary(Core.NATIVE_LIBRARY_NAME)加载库时,它会抛出UnsatisfiedLinkError: Native library is already loaded in another classloader。删除第二个负载是有效的,但是由于它们没有我需要的版本,所以我必须手动构建OpenCV才能使用。
  • 使用控制台项目测试我的OpenCV构建(jar和本机库),手动将它们添加到java.library.path中,运行良好。
  • 尝试从nu.pattern.OpenCV复制代码以加载我的构建,并对其进行相应修改。库加载时没有任何错误,请尝试再次用System.loadLibrary(Core.NATIVE_LIBRARY_NAME)加载,没有错误。尝试使用任何OpenCV方法,都会引发错误。

这是我的密码

OpenCVLoader.java

代码语言:javascript
运行
复制
import org.opencv.core.Core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.file.AccessDeniedException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class OpenCVLoader {
    private static final Logger LOGGER = LoggerFactory.getLogger(OpenCVLoader.class);
    private static final String NATIVE_LIB_NAME = "libopencv_java3411.dylib";

    private static Path extractNativeBinary() {
        Path nativeLibTemporaryPath = null;
        try (InputStream nativeLibInputStream = new ClassPathResource(NATIVE_LIB_NAME).getInputStream()) {
            nativeLibTemporaryPath = new TemporaryDirectory().markDeleteOnExit().getPath().resolve("./" + NATIVE_LIB_NAME).normalize();
            Files.createDirectories(nativeLibTemporaryPath.getParent());
            Files.copy(nativeLibInputStream, nativeLibTemporaryPath);
            return nativeLibTemporaryPath;
        } catch (IOException ex) {
            LOGGER.error(ex.getMessage());
        }
        return nativeLibTemporaryPath;
    }


    private static class TemporaryDirectory {
        static final String OPENCV_PREFIX = "opencv";
        final Path path;

        public TemporaryDirectory() {
            try {
                path = Files.createTempDirectory(OPENCV_PREFIX);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        public Path getPath() {
            return path;
        }

        public TemporaryDirectory markDeleteOnExit() {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    delete();
                }
            });

            return this;
        }

        private void delete(Path path) {
            if (!Files.exists(path)) {
                return;
            }

            try {
                Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult postVisitDirectory(final Path dir, final IOException e) throws IOException {
                        Files.deleteIfExists(dir);
                        return super.postVisitDirectory(dir, e);
                    }

                    @Override
                    public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs)
                            throws IOException {
                        Files.deleteIfExists(file);
                        return super.visitFile(file, attrs);
                    }
                });
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        public void delete() {
            delete(path);
        }
    }

    /**
     * Exactly once per {@link ClassLoader}, attempt to load the native library (via {@link System#loadLibrary(String)} with {@link Core#NATIVE_LIBRARY_NAME}). If the first attempt fails, the native binary will be extracted from the classpath to a temporary location (which gets cleaned up on shutdown), that location is added to the {@code java.library.path} system property and {@link ClassLoader#usr_paths}, and then another call to load the library is made. Note this method uses reflection to gain access to private memory in {@link ClassLoader} as there's no documented method to augment the library path at runtime. Spurious calls are safe.
     */
    public static void loadShared() {
        SharedLoader.getInstance();
    }

    /**
     * @see <a href="http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom">Initialization-on-demand holder idiom</a>
     */
    private static class SharedLoader {
        // Class loader error messages indicating OpenCV is not found on java.library.path
        private static final List<String> errorMessages = Arrays.asList(
                String.format("no %s in java.library.path", Core.NATIVE_LIBRARY_NAME),
                String.format("%s (Not found in java.library.path)", Core.NATIVE_LIBRARY_NAME)
        );

        private Path libraryPath;

        private SharedLoader() {
            try {
                System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
            } catch (final UnsatisfiedLinkError ule) {

                /* Only update the library path and load if the original error indicates it's missing from the library path. */
                if (ule == null || !openCVNotFoundInJavaLibraryPath(ule.getMessage())) {
                    throw ule;
                }

                /* Retain this path for cleaning up the library path later. */
                this.libraryPath = extractNativeBinary();

                addLibraryPath(libraryPath.getParent());
                System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

            }
        }

        /**
         * Check if any error fragment is contained in the errorMessage
         * @param errorMessage the message to check
         * @return true if any error fragment matches, false otherwise
         */
        private boolean openCVNotFoundInJavaLibraryPath(String errorMessage) {
            for (String errorFragment : errorMessages) {
                if (errorMessage.contains(errorFragment)) {
                    return true;
                }
            }

            return false;
        }

        /**
         * Cleans up patches done to the environment.
         */
        @Override
        protected void finalize() throws Throwable {
            super.finalize();

            if (null == libraryPath) {
                return;
            }

            removeLibraryPath(libraryPath.getParent());
        }

        private static class Holder {
            private static final SharedLoader INSTANCE = new SharedLoader();
        }

        public static SharedLoader getInstance() {
            return Holder.INSTANCE;
        }

        /**
         * Adds the provided {@link Path}, normalized, to the {@link ClassLoader#usr_paths} array, as well as to the {@code java.library.path} system property. Uses the reflection API to make the field accessible, and maybe unsafe in environments with a security policy.
         *
         * @see <a href="http://stackoverflow.com/q/15409223">Adding new paths for native libraries at runtime in Java</a>
         */
        private static void addLibraryPath(final Path path) {
            final String normalizedPath = path.normalize().toString();

            try {
                final Field field = ClassLoader.class.getDeclaredField("usr_paths");
                field.setAccessible(true);

                final Set<String> userPaths = new HashSet<>(Arrays.asList((String[]) field.get(null)));
                userPaths.add(normalizedPath);

                field.set(null, userPaths.toArray(new String[userPaths.size()]));

                System.setProperty("java.library.path", System.getProperty("java.library.path") + File.pathSeparator + normalizedPath);

            } catch (IllegalAccessException e) {
                throw new RuntimeException("Failed to get permissions to set library path");
            } catch (NoSuchFieldException e) {
                throw new RuntimeException("Failed to get field handle to set library path");
            }
        }
    }

}

ObjectDetectionService.java

代码语言:javascript
运行
复制
public class ObjectDetectionService {
    static {
        OpenCVLoader.loadShared(); // loaded fine,
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME); //try to load 1 more time, no exception is thrown
        Mat testMat = new Mat(); // UnsastifiedLinkError;

    }
}

堆栈跟踪

代码语言:javascript
运行
复制
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-07-06 10:02:18,639 ERROR [restartedMain] --- [org.springframework.boot.SpringApplication][SpringApplication.java:837] Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'objectDetectionService' defined in file [/Users/minhtus/personal/ppr/build/classes/java/main/com/fptu/swp/ppr/detection/service/ObjectDetectionService.class]: Instantiation of bean failed; nested exception is java.lang.UnsatisfiedLinkError: org.opencv.core.Mat.n_Mat()J
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1320)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1214)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:895)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
    at com.fptu.swp.ppr.Application.main(Application.java:30)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: java.lang.UnsatisfiedLinkError: org.opencv.core.Mat.n_Mat()J
    at org.opencv.core.Mat.n_Mat(Native Method)
    at org.opencv.core.Mat.<init>(Mat.java:23)
    at com.fptu.swp.ppr.detection.service.ObjectDetectionService.<clinit>(ObjectDetectionService.java:32)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:204)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:87)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1312)
    ... 23 common frames omitted
EN

回答 2

Stack Overflow用户

发布于 2020-11-09 12:18:48

我的解决方案是禁用devtools,正如评论中所建议的那样。

票数 0
EN

Stack Overflow用户

发布于 2020-07-06 09:50:11

您必须将适当的OpenCV dlls包含到项目中。最简单的方法是将代码打包到jar文件中,并将OpenCV dlls放在jar文件所在的目录中。这样,它们就会自动包含在您的类路径中。

票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62748932

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档