All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.intuit.karate.robot.OpenCvUtils Maven / Gradle / Ivy

The newest version!
/*
 * The MIT License
 *
 * Copyright 2022 Karate Labs Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.intuit.karate.robot;

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.WindowConstants;
import org.bytedeco.javacpp.DoublePointer;
import org.bytedeco.javacpp.Pointer;
import org.bytedeco.javacv.CanvasFrame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.bytedeco.javacv.Java2DFrameUtils;
import org.bytedeco.javacv.OpenCVFrameConverter;
import static org.bytedeco.opencv.global.opencv_core.findNonZero;
import static org.bytedeco.opencv.global.opencv_core.minMaxLoc;
import static org.bytedeco.opencv.global.opencv_core.bitwise_not;
import static org.bytedeco.opencv.global.opencv_imgcodecs.*;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
import org.bytedeco.opencv.opencv_core.AbstractScalar;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.Point;
import org.bytedeco.opencv.opencv_core.Point2f;
import org.bytedeco.opencv.opencv_core.Point2fVector;
import org.bytedeco.opencv.opencv_core.Rect;
import org.bytedeco.opencv.opencv_core.Scalar;
import org.bytedeco.opencv.opencv_core.Size;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author pthomas3
 */
public class OpenCvUtils {
    
    private static final Logger logger = LoggerFactory.getLogger(OpenCvUtils.class);
    
    private OpenCvUtils() {
        // only static methods
    }
    
    public static Region find(int strictness, RobotBase robot, Region source, byte[] bytes, boolean resize) {
        Region found = find(strictness, robot, toMat(source.captureGreyScale()), read(bytes), resize);
        if (found == null) {
            return null;
        }
        return found.toAbsolute(source);
    }

    public static Region find(int strictness, RobotBase robot, Mat source, Mat target, boolean resize) {
        List found = find(strictness, false, robot, source, target, resize);
        if (found.isEmpty()) {
            return null;
        }
        return found.get(0);
    }

    public static List findAll(int strictness, RobotBase robot, Region source, byte[] bytes, boolean resize) {
        List found = find(strictness, true, robot, toMat(source.captureGreyScale()), read(bytes), resize);
        List list = new ArrayList(found.size());
        for (Region r : found) {
            list.add(r.toAbsolute(source));
        }
        return list;
    }

    public static Mat rescale(Mat mat, double scale) {
        Mat resized = new Mat();
        resize(mat, resized, new Size(), scale, scale, CV_INTER_AREA);
        return resized;
    }

    private static final int TARGET_MINVAL_FACTOR = 150; // magic number, lower is stricter
    private static final int BLOCK_SIZE = 5;

    private static List getPointsBelowThreshold(Mat src, double threshold) {
        Mat dst = new Mat();
        threshold(src, dst, threshold, 1, CV_THRESH_BINARY_INV);
        Mat non = new Mat();
        findNonZero(dst, non);
        int len = (int) non.total();
        int xPrev = -BLOCK_SIZE;
        int yPrev = -BLOCK_SIZE;
        int countPrev = 0;
        int xSum = 0;
        int ySum = 0;
        List points = new ArrayList(len);
        for (int i = 0; i < len; i++) {
            Pointer ptr = non.ptr(i);
            Point p = new Point(ptr);
            int x = p.x();
            int y = p.y();
            int xDelta = Math.abs(x - xPrev);
            int yDelta = Math.abs(y - yPrev);
            if (xDelta < BLOCK_SIZE && yDelta < BLOCK_SIZE) {
                countPrev++;
                xSum += x;
                ySum += y;
            } else {
                if (countPrev > 0) {
                    int xFinal = Math.floorDiv(xSum, countPrev);
                    int yFinal = Math.floorDiv(ySum, countPrev);
                    // logger.debug("end: {}:{}", xFinal, yFinal);
                    points.add(new int[]{xFinal, yFinal});
                }
                xSum = x;
                ySum = y;
                countPrev = 1;
            }
            xPrev = x;
            yPrev = y;
        }
        if (countPrev > 0) {
            int xFinal = Math.floorDiv(xSum, countPrev);
            int yFinal = Math.floorDiv(ySum, countPrev);
            points.add(new int[]{xFinal, yFinal});
        }
        return points;
    }

    private static Region toRegion(RobotBase robot, int[] p, double scale, int targetWidth, int targetHeight) {
        int x = (int) Math.round(p[0] / scale);
        int y = (int) Math.round(p[1] / scale);
        int width = (int) Math.round(targetWidth / scale);
        int height = (int) Math.round(targetHeight / scale);
        return new Region(robot, x, y, width, height);
    }

    private static int[] templateAndMin(int strictness, double scale, Mat source, Mat target, Mat result) {
        Mat resized = scale == 1 ? source : rescale(source, scale);
        matchTemplate(resized, target, result, CV_TM_SQDIFF);
        DoublePointer minValPtr = new DoublePointer(1);
        DoublePointer maxValPtr = new DoublePointer(1);
        Point minPt = new Point();
        Point maxPt = new Point();
        minMaxLoc(result, minValPtr, maxValPtr, minPt, maxPt, null);
        int minVal = (int) minValPtr.get();
        int x = minPt.x();
        int y = minPt.y();
        return new int[]{x, y, minVal};
    }

    private static int collect(int strictness, List found, boolean findAll, RobotBase robot, Mat source, Mat target, double scale) {
        int targetWidth = target.cols();
        int targetHeight = target.rows();
        int targetMinVal = targetWidth * targetHeight * TARGET_MINVAL_FACTOR * strictness;  
        Mat result = new Mat();
        int[] minData = templateAndMin(strictness, scale, source, target, result);
        int minValue = minData[2];
        if (minValue > targetMinVal) {
            logger.debug("no match at scale {}, minVal: {} / {} at {}:{}", scale, minValue, targetMinVal, minData[0], minData[1]);
            if (robot != null && robot.debug) {
                Rect rect = new Rect(minData[0], minData[1], targetWidth, targetHeight);
                Mat temp = drawOnImage(source, rect, Scalar.RED);
                show(temp, scale + " " +  minData[0] + ":" + minData[1] + " " + minValue + " / " + targetMinVal);
            }
            return minData[2];
        }
        logger.debug("found match at scale {}, minVal: {} / {} at {}:{}", scale, minValue, targetMinVal, minData[0], minData[1]);
        if (findAll) {
            List points = getPointsBelowThreshold(result, targetMinVal);
            for (int[] p : points) {
                Region region = toRegion(robot, p, scale, targetWidth, targetHeight);
                found.add(region);
            }
        } else {
            Region region = toRegion(robot, minData, scale, targetWidth, targetHeight);
            found.add(region);
        }
        return minValue;
    }

    public static List find(int strictness, boolean findAll, RobotBase robot, Mat source, Mat target, boolean resize) {
        List found = new ArrayList();
        collect(strictness, found, findAll, robot, source, target, 1);
        if (!found.isEmpty()) {
            return found;
        }
        int stepUp = collect(strictness, found, findAll, robot, source, target, 1.1);
        if (!found.isEmpty()) {
            return found;
        }
        int stepDown = collect(strictness, found, findAll, robot, source, target, 0.9);
        if (!found.isEmpty()) {
            return found;
        }
        boolean goUpFirst = stepUp < stepDown;
        for (int step = 2; step < 6; step++) {
            double scale = 1 + 0.1 * step * (goUpFirst ? 1 : -1);
            collect(strictness, found, findAll, robot, source, target, scale);
        }
        if (!findAll && !found.isEmpty()) {
            return found;
        }
        for (int step = 2; step < 6; step++) {
            double scale = 1 + 0.1 * step * (goUpFirst ? -1 : 1);
            collect(strictness, found, findAll, robot, source, target, scale);
        }        
        return found;
    }

    public static Mat loadAndShowOrExit(File file, int flags) {
        Mat image = read(file, flags);
        show(image, file.getName());
        return image;
    }

    public static BufferedImage readImageAsGreyScale(File file) {
        Mat mat = read(file, IMREAD_GRAYSCALE);
        return toBufferedImage(mat);
    }

    public static byte[] toBytes(BufferedImage img) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            ImageIO.write(img, "png", baos);
            return baos.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Mat read(File file) {
        return read(file, IMREAD_GRAYSCALE);
    }

    public static Mat read(byte[] bytes) {
        return read(bytes, IMREAD_GRAYSCALE);
    }

    public static Mat read(byte[] bytes, int flags) {
        Mat image = imdecode(new Mat(bytes), flags);
        if (image.empty()) {
            throw new RuntimeException("image decode failed");
        }
        return image;
    }

    public static Mat read(File file, int flags) {
        Mat image = imread(file.getAbsolutePath(), flags);
        if (image.empty()) {
            throw new RuntimeException("image not found: " + file.getAbsolutePath());
        }
        return image;
    }

    public static File save(BufferedImage image, File file) {
        try {
            ImageIO.write(image, "png", file);
            return file;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public static void show(byte[] bytes, String title) {
        Mat mat = read(bytes);
        show(toBufferedImage(mat), title);
    }    

    public static void show(Mat mat, String title) {
        show(toBufferedImage(mat), title);
    }

    public static void show(Image image, String title) {
        CanvasFrame canvas = new CanvasFrame(title, 1);
        canvas.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        canvas.showImage(image);
    }

    public static void save(Mat image, File file) {
        imwrite(file.getAbsolutePath(), image);
    }

    public static Mat drawOnImage(Mat image, Point2fVector points) {
        Mat dest = image.clone();
        int radius = 5;
        Scalar red = new Scalar(0, 0, 255, 0);
        for (int i = 0; i < points.size(); i++) {
            Point2f p = points.get(i);
            circle(dest, new Point(Math.round(p.x()), Math.round(p.y())), radius, red);
        }
        return dest;
    }

    public static Mat drawOnImage(Mat image, Rect overlay, Scalar color) {
        Mat dest = image.clone();
        rectangle(dest, overlay, color);
        return dest;
    }
    
    public static Mat negative(Mat src) {
        Mat dest = new Mat();
        bitwise_not(src, dest);
        return dest;
    }
    
    public static Mat toMat(BufferedImage bi) {
        return Java2DFrameUtils.toMat(bi);
    }    

    public static BufferedImage toBufferedImage(Mat mat) {
        OpenCVFrameConverter.ToMat openCVConverter = new OpenCVFrameConverter.ToMat();
        Java2DFrameConverter java2DConverter = new Java2DFrameConverter();
        return java2DConverter.convert(openCVConverter.convert(mat));
    }    
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy