JavaOpenCV学习笔记(2):某易滑块位置识别


某易滑块图片分析

某易的滑块验证,发起请求后会分别返回一张 JPG 背景图和一张 PNG 滑块图。

背景图中会有一块明显的浅色缺口,其余部分会有比较复杂的轮廓。

背景图

滑块图具有一定的高度,但可能会与背景图高度不相等。

滑块

滑块和缺口是明显对应的,只要提取背景图的边缘和滑块的轮廓,再进行模板匹配,就可以得到正确的滑动距离。

边缘检测的写法

使用 Imgproc.filter2D 方法,传入边缘检测算子,即可实现边缘检测。也可以直接调用 ScharrCannyLaplacian 等方法。

sobel 算子为例:

package opencv.edge;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import utils.ImageUtils;
import utils.ImageViewer;

import static org.opencv.imgcodecs.Imgcodecs.IMREAD_GRAYSCALE;

/**
 * @author yuanbug
 * date: 2020/5/13
 */
public class Sobel {
    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    private static final String IMAGE_PATH = "D:/test/lena.jpg";

    public static final Mat SOBEL_X_KERNEL = new Mat(9, 9, CvType.CV_32F) {{
        put(0, 0, -1);
        put(0, 1, 0);
        put(0, 2, 1);
        put(1, 0, -2);
        put(1, 1, 0);
        put(1, 2, 2);
        put(2, 0, -1);
        put(2, 1, 0);
        put(2, 2, 1);
    }};

    public static final Mat SOBEL_Y_KERNEL = new Mat(9, 9, CvType.CV_32F) {{
        put(0, 0, -1);
        put(0, 1, -2);
        put(0, 2, -1);
        put(1, 0, 0);
        put(1, 1, 0);
        put(1, 2, 0);
        put(2, 0, 1);
        put(2, 1, 2);
        put(2, 2, 1);
    }};

    public static void main(String[] args) {
        Mat source = Imgcodecs.imread(IMAGE_PATH, IMREAD_GRAYSCALE);
        Mat xDestination = new Mat(source.rows(), source.cols(), source.type());
        Mat yDestination = new Mat(source.rows(), source.cols(), source.type());
        Imgproc.filter2D(source, xDestination, -1, SOBEL_X_KERNEL);
        Imgproc.filter2D(source, yDestination, -1, SOBEL_Y_KERNEL);
        new ImageViewer(ImageUtils.horizontalConcat(source, xDestination, yDestination), "sobel", true);
    }
}

运行结果:

XY两个方向上的sobel边缘检测

轮廓检测的写法

要做轮廓检测可以使用 findContours 函数。对于复杂图片,需要先做一下自适应阈值化。

代码也很简单:

package opencv.contour;

import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import utils.ImageUtils;
import utils.ImageViewer;
import java.util.ArrayList;
import java.util.List;

import static org.opencv.imgcodecs.Imgcodecs.IMREAD_GRAYSCALE;

/**
 * @author yuanbug
 * date: 2020/5/13
 */
public class Contour {
    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    private static final String IMAGE_PATH = "D:/test/lena.jpg";

    public static void main(String[] args) {
        Mat source = Imgcodecs.imread(IMAGE_PATH, IMREAD_GRAYSCALE);
        Mat destination = source.clone();
        Mat contoursMat = new Mat(source.rows(), source.width(), source.type());
        List<MatOfPoint> contours = new ArrayList<>();
        Imgproc.adaptiveThreshold(destination, destination, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY_INV, 7, 10);
        Imgproc.findContours(destination, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE, new Point(0, 0));
        Imgproc.drawContours(contoursMat, contours, -1, new Scalar(255, 0, 0, 0));
        new ImageViewer(ImageUtils.horizontalConcat(source, destination, contoursMat), "轮廓", true);
    }
}

运行结果:

轮廓检测

模板匹配的写法

做模板匹配,需要注意结果 Mat 的行数、列数、格式要求,剩下的也就只有调用库函数了。

调包谁不会啊:

package opencv.match;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import static org.opencv.imgcodecs.Imgcodecs.IMREAD_COLOR;
import static org.opencv.imgcodecs.Imgcodecs.IMREAD_GRAYSCALE;

/**
 * @author yuanbug
 * date: 2020/5/13
 */
public class TemplateMatch {
    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    private static final String LENA_PATH = "D:/test/lena.jpg";
    private static final String LENA_FACE_PATH = "D:/test/face.jpg";

    public static void main(String[] args) {
        Mat lena = Imgcodecs.imread(LENA_PATH, IMREAD_COLOR);
        Mat face = Imgcodecs.imread(LENA_FACE_PATH, IMREAD_COLOR);
        Mat result = new Mat(lena.rows() - face.rows() + 1, lena.cols() - face.cols() + 1, CvType.CV_32FC1);
        Imgproc.matchTemplate(lena, face, result, Imgproc.TM_CCORR_NORMED);
        // 拿位置
        Core.MinMaxLocResult locResult = Core.minMaxLoc(result);
        double x = locResult.maxLoc.x;
        double y = locResult.maxLoc.y;
        // 画矩形
        Imgproc.rectangle(lena, new Point(x, y), new Point(x + face.cols(), y + face.rows()), new Scalar(0, 0, 255), 2, Imgproc.LINE_AA);
        HighGui.imshow("模板匹配", lena);
        HighGui.waitKey();
    }
}

运行结果:

模板匹配

某易滑块位置识别

无非就是把前面的几个操作一锅乱炖而已:

package opencv;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import utils.ImageUtils;
import java.util.ArrayList;
import java.util.List;

import static org.opencv.imgcodecs.Imgcodecs.IMREAD_GRAYSCALE;

/**
 * @author yuanbug
 * date: 2020/5/13
 */
public class NeteaseSlideCrack {
    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    private static final String BACKGROUND_PATH = "D:/test/wyyd_bg.jpg";
    private static final String PIECE_PATH = "D:/test/wyyd_hk.png";

    private static Mat findBackgroundEdge() {
        Mat background = Imgcodecs.imread(BACKGROUND_PATH, IMREAD_GRAYSCALE);
        Mat backgroundEdge = new Mat(background.rows(), background.cols(), background.type());
        // 乱写的两个阈值,效果似乎还不错
        Imgproc.Canny(background, backgroundEdge, 300, 500);
        return backgroundEdge;
    }

    private static Mat findPieceContour() {
        Mat piece = Imgcodecs.imread(PIECE_PATH, IMREAD_GRAYSCALE);
        Mat pieceContour = new Mat(piece.rows(), piece.width(), piece.type());
        List<MatOfPoint> contours = new ArrayList<>();
        // 这一步自适应阈值化不做也可以,滑块的轮廓本来就很清晰,做一下可以减少滑块内部图案的影响
        Imgproc.adaptiveThreshold(piece, piece, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY_INV, 3, 5);
        Imgproc.findContours(piece, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE, new Point(0, 0));
        Imgproc.drawContours(pieceContour, contours, -1, new Scalar(255, 255, 255));
        return pieceContour;
    }

    public static void main(String[] args) {
        Mat pieceContour = findPieceContour();
        Mat backgroundEdge = findBackgroundEdge();
        Mat preProcessResult = ImageUtils.horizontalConcat(backgroundEdge, pieceContour);
        Mat result = new Mat(backgroundEdge.rows() - pieceContour.rows() + 1, backgroundEdge.cols() - pieceContour.cols() + 1, CvType.CV_32FC1);
        Imgproc.matchTemplate(backgroundEdge, pieceContour, result, Imgproc.TM_CCORR_NORMED);
        Core.MinMaxLocResult locResult = Core.minMaxLoc(result);
        double x = locResult.maxLoc.x;
        double y = locResult.maxLoc.y;
        System.out.println("水平偏移:" + x);
        // 转回彩图,方便看清楚矩形红边
        Imgproc.cvtColor(backgroundEdge, backgroundEdge, Imgcodecs.IMREAD_COLOR);
        Imgproc.rectangle(backgroundEdge, new Point(x, y), new Point(x + pieceContour.cols(), y + pieceContour.rows()), new Scalar(0, 0, 255), 2, Imgproc.LINE_AA);
        HighGui.imshow("预处理", preProcessResult);
        HighGui.imshow("模板匹配", backgroundEdge);
        HighGui.waitKey();
    }
}

运行结果:

匹配成功

另一组例子

用 Java 调 OpenCV 还是挺简单的,目前遇到的最大麻烦在于搞清楚每个函数到底是在哪个类里面,明明写 Python 的时候全部都用 cv2 就行。


文章作者: yuanbug
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 yuanbug !
评论
  目录