无法在 Grails 2.4.4 项目中实例化 OpenCV-4.0.1 CascadeClassifier

Cannot instantiate OpenCV-4.0.1 CascadeClassifier in a Grails 2.4.4 Project

我想从员工的上传文件中裁剪 1x1 的员工图片,以便在我的 Grails 应用程序中设置为他们的头像。我听说 OpenCV 做得很好,所以我在 ImageService 中使用了它。问题是它似乎无法找到(或读取)它需要的 CascadeClassifier XML 文件:

class ImageService {
    final String FRONTAL_FACE_XML = "D:\Devtools\opencv\build\etc\lbpcascades\lbpcascade_frontalface_improved.xml"
    final String ORIGINAL_PICTURE = "D:\Projects\opencv\grails-app\assets\4fc30smaegvq0z3mvgm9yhf6vtv9kv8bgryi9x08wuada8jxu3.jpg"
    final String CROPPED_PICTURE = "D:\Projects\opencv\grails-app\assets\4fc30smaegvq0z3mvgm9yhf6vtv9kv8bgryi9x08wuada8jxu3_100.jpg"

    void opencvtest() {
        // Before I placed the OpenCV dll in the environment path, this line causes an error.
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME)

        // UnsatisfiedLinkError here
        CascadeClassifier faceDetector = new CascadeClassifier(this.getClass().getResource("lbpcascade_frontalface_improved.xml").getPath());

        // Same error as well.
        // File cascadeFile = new File(FRONTAL_FACE_XML);
        // CascadeClassifier faceDetector = new CascadeClassifier(cascadeFile.getAbsolutePath());

        // And also here.
        // CascadeClassifier faceDetector = new CascadeClassifier(FRONTAL_FACE_XML);

        Mat image = HighGui.imread(ORIGINAL_PICTURE)
        faceDetector.detectMultiScale(image, face_Detections)

        Rect rect_Crop = null

        for (Rect rect : face_Detections.toArray()) {
            Core.rectangle(image, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0))
            rectCrop = new Rect(rect.x, rect.y, rect.width, rect.height)
        }

        Mat image_roi = new Mat(image, rectCrop)
        HighGui.imwrite(CROPPED_PICTURE, image_roi)

        return
    }
}

并导致以下错误:

java.lang.UnsatisfiedLinkError org.opencv.objdetect.CascadeClassifier.CascadeClassifier_0(Ljava/lang/String;)J

环境: Windows 7,Java 1.8,Grails 2.4.4

我做过的事情:

  1. 我已经从他们的网站安装了 OpenCV 4.0.1:https://opencv.org/releases.html 并在 D:\opencv.0.1.
  2. 上解压了他们的发行版
  3. 我已将其包含在我的环境路径中:D:\opencv.0.1\build\java\x64
  4. 由于找不到官方的 Maven 依赖项 link,我将在 D:\opencv.0.1\build\java 上找到的 jar 复制到我的 grails lib 目录中。
  5. 我已经尝试将所有 dllxml 复制到 C:\Windows\system32
  6. 认为发行版上的 jar 可能是 "faulty",我将其替换为这个包 compile "org.bytedeco.javacpp-presets:opencv:4.0.1-1.4.4" 直到 BuildConfig.groovy,但仍然会导致相同的错误线.
  7. 我验证了 FRONTAL_FACE_XMLORIGINAL_PICTURE 是否都是正确的文件路径,他们是。

对于此示例,我们将在 Windows 上使用 VC15 和 OpenCV。我还没有学会如何在 Linux.

上移植它

图例:

APPLICATION_PATH = D:\application
JAVA_DLL_PATH = D:\opencv.0.1\build\java\x64
VC_DLL_PATH = D:\opencv.0.1\build\x64\vc15\bin

可选:

  1. 转到 %VC_DLL_PATH% 目录。
  2. 创建一个名为 debug 的目录(或您喜欢的任何名称)并将所有 *d.dll 文件移动到那里。这些以 *d.dll 结尾的文件在您 运行 时可能会导致错误,因为它正在寻找不属于基本 Visual C++ 2015 的 vc15 调试 dll

配置:

  1. %APPLICATION_PATH%\src 目录中创建一个名为 files 的目录(或您喜欢的任何一个)。应该和这个差不多。

    %APPLICATION_PATH% ├── bin\ ├── grails-app\ ├── lib\ ├── ... ├── src\ │ ├── groovy\ │ ├── java\ │ └── files\ --new directory └── ...

    1. %JAVA_DLL_PATH%%VC_DLL_PATH%目录中的所有*.dll复制到新创建的目录%APPLICATION_PATH%\src\files.

    2. 将新创建的目录包含到您的 .classpath 文件中。

    <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry excluding="spring/" kind="src" path="grails-app/conf"/> ... <classpathentry kind="src" path="src/files"/> ... </classpath>

    1. 在您的 %APPLICATION_PATH% 中创建另一个名为 files 的目录(或您喜欢的任何一个)。

    %APPLICATION_PATH% ├── bin\ ├── grails-app\ ├── files\ --new directory └── ...

    1. 将您将要使用的 CascadeClassifiers xml 文件复制到此处。它们位于 \opencv.0.1\build\etc 目录中。

    2. 即使在%APPLICATION_PATH%中复制了dll,我们仍然需要将它们包含在系统路径中。编辑环境变量并将 %JAVA_DLL_PATH%%VC_DLL_PATH% 目录包含到 PATH.

    3. 此外,即使我们复制了dll并将库目录包含到系统路径中,我们仍然需要将其包含到Tomcat JVM 库路径中。打开你的 BuildConfig.groovy 并添加:

    grails.tomcat.jvmArgs = ["-Djava.library.path=D:\opencv.0.1\build\x64\vc15\bin;D:\opencv.0.1\build\java\x64"]

    1. 将目录包含到您的 Config.groovy

    openCV { cascadeClassifiers = "D:\application\files\opencv" home = "D:\opencv\4.0.1\build\x64\vc15\bin" java = "D:\opencv\4.0.1\build\java\x64" }

使用方法:

您需要在 %APPLICATION_PATH%\src\java 中创建 *.java 文件而不是 *.groovy 文件。我还没有检查为什么它不适用于 .groovy 文件。

import grails.util.Holders;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.UUID;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.FileUtils;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Rect;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.objdetect.CascadeClassifier;

public class ImageService {
    static final String[] OPENCV_SUPPORTED_EXTENSION = new String[]{"bmp", "dib", "jp2", "jpe", "jpeg", "jpg", "pbm", "pgm", "png", "ppm", "ras", "sr", "tif", "tiff"};

    public static File cropImage(final File originalFile) {
        loadLibrary();
        return cropImage(originalFile, originalFile.getParentFile(), getCascadeClassifiers());
    }

    public static File cropImage(final File originalFile, final ArrayList<CascadeClassifier> cascadeClassifiers) {
        return cropImage(originalFile, originalFile.getParentFile(), cascadeClassifiers);
    }

    public static File cropImage(final File originalFile, final File targetDirectory) {
        loadLibrary();
        return cropImage(originalFile, originalFile.getParentFile(), getCascadeClassifiers());
    }

    public static File cropImage(final File originalFile, final File targetDirectory, final ArrayList<CascadeClassifier> cascadeClassifiers) {
        ArrayList<File> siblingFiles = getFaces(originalFile, cascadeClassifiers);
        File maxFile = null;
        int maxWidth = 0;

        for(int x = 0; x < siblingFiles.size(); x++) {
            Mat image = Imgcodecs.imread(siblingFiles.get(x).getAbsolutePath(), Imgcodecs.IMREAD_UNCHANGED);

            if(image.width() > maxWidth) {
                maxFile = siblingFiles.get(x);
                maxWidth = image.width();
            }
        }

        File croppedFile = null;
        if(maxFile != null) {
            croppedFile = new File(targetDirectory.getAbsolutePath() +
                File.separator +
                originalFile.getName());

            try {
                FileUtils.copyFile(maxFile, croppedFile);
            }
            catch(IOException e) {}
        }

        for(int y = 0; y < siblingFiles.size(); y++) {
            siblingFiles.get(y).delete();
        }

        System.gc();
        System.runFinalization();

        return croppedFile;
    }

    public static ArrayList<CascadeClassifier> getCascadeClassifiers() {
        ArrayList<CascadeClassifier> classifiers = new ArrayList<CascadeClassifier>();

        final String[] cascadeSupportedExtensions = new String[]{"xml"};
        final String cascadeClassifierPath = Holders.getFlatConfig().get("openCV.cascadeClassifiers").toString();
        File cascadeClassifierDirectory = new File(cascadeClassifierPath);
        ArrayList<File> detectors = new ArrayList<File>(FileUtils.listFiles(cascadeClassifierDirectory, cascadeSupportedExtensions, false));

        for(int y = 0; y < detectors.size(); y++) {
            CascadeClassifier faceDetector = new CascadeClassifier();
            faceDetector.load(detectors.get(y).getAbsolutePath());

            classifiers.add(faceDetector);
        }

        return classifiers;
    }

    public static ArrayList<File> getFaces(final File originalFile) {
        loadLibrary();
        return getFaces(originalFile, getCascadeClassifiers());
    }

    public static ArrayList<File> getFaces(final File originalFile, final ArrayList<CascadeClassifier> cascadeClassifiers) {
        File temporaryFile = new File(originalFile.getParentFile().getAbsolutePath() +
            File.separator +
            UUID.randomUUID().toString() +
            "." + FilenameUtils.getExtension(originalFile.getName()));

        try {
            FileUtils.copyFile(originalFile, temporaryFile);
        }
        catch(IOException e) {}

        final int frame = 9;
        final int offset = 8;
        int rotateCounter = 0;

        Integer marginX, marginY, marginWidth, marginHeight;
        Integer pxPerOffset, excess;

        ArrayList<File> siblingFiles = new ArrayList<File>();

        while(rotateCounter < 4) {
            if(rotateCounter > 0) {
                Mat image = Imgcodecs.imread(temporaryFile.getAbsolutePath(), Imgcodecs.IMREAD_UNCHANGED);

                Core.transpose(image, image);
                Core.flip(image, image, 1);

                Imgcodecs.imwrite(temporaryFile.getAbsolutePath(), image);

                image.release();
            }

            for(int y = 0; y < cascadeClassifiers.size(); y++) {
                CascadeClassifier faceDetector = cascadeClassifiers.get(y);
                Mat image = Imgcodecs.imread(temporaryFile.getAbsolutePath(), Imgcodecs.IMREAD_UNCHANGED);

                MatOfRect faceDetections = new MatOfRect();
                faceDetector.detectMultiScale(image, faceDetections);

                Rect defaultRect = null;
                Rect marginRect = null;
                Rect[] facesRect = faceDetections.toArray();

                for(int z = 0; z < facesRect.length; z++) {
                    defaultRect = facesRect[z];
                    pxPerOffset = defaultRect.width / frame;

                    marginX = defaultRect.x - (pxPerOffset * offset);
                    marginY = defaultRect.y - (pxPerOffset * offset);

                    marginWidth = defaultRect.width + (offset * pxPerOffset * 2);
                    marginHeight = defaultRect.height + (offset * pxPerOffset * 2);

                    excess = Math.max(
                        0 - marginX,
                            Math.max(0 - marginY,
                                Math.max(marginX + marginWidth - image.width(),
                                    Math.max(marginY + marginHeight - image.height(), 0)))
                    );

                    if(excess > 0) {
                        marginX += excess;
                        marginY += excess;

                        marginWidth -= excess * 2;
                        marginHeight -= excess * 2;
                    }

                    marginRect = new Rect(marginX, marginY, marginWidth, marginHeight);
                    Mat imageWithMargin = new Mat(image, marginRect);

                    String croppedFileName = temporaryFile.getParentFile().getAbsolutePath() +
                        File.separator +
                        UUID.randomUUID().toString() + "_" +
                        y + "_" +
                        rotateCounter + "_" +
                        z + "." +
                        FilenameUtils.getExtension(temporaryFile.getName());

                    Imgcodecs.imwrite(croppedFileName, imageWithMargin);

                    siblingFiles.add(new File(croppedFileName));

                    imageWithMargin.release();
                }

                image.release();
            }

            rotateCounter++;
        }

        temporaryFile.delete();

        return siblingFiles;
    }

    public static void loadLibrary() {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

        final String[] urls = new String[]{
            Holders.getFlatConfig().get("openCV.java").toString(),
            Holders.getFlatConfig().get("openCV.home").toString()
        };

        final String[] validExtensions = new String[]{"dll"};
        ArrayList<File> dlls = new ArrayList<File>();
        for(int y = 0; y < urls.length; y++) {
            dlls.addAll(FileUtils.listFiles(new File(urls[y]), validExtensions, false));
        }

        for(int y = 0; y < dlls.size(); y++) {
            for(int z = 0; z < validExtensions.length; z++) {
                System.loadLibrary(dlls.get(y).getName().replace("." + validExtensions[z], ""));
                System.load(dlls.get(y).getAbsolutePath());
            }
        }
    }
}

然后我们将它用于我们的 groovy 服务 类.

class TestService {
    def openCVTest() {
        File picture = new File("D:\original.jpg");
        File savingDirectory = new File("D:\");

        ImageService.cropImage(picture, savingDirectory);

        return;
    }
}