【亲测实用的图片滑块验证码】

亲测实用的图片滑块验证码

  1. 准备图片 存放于resources目录下image文件下

在这里插入图片描述

  1. 编写滑块验证工具类以及返回实体对象
package com.facemetric.tradingsystem.commons.captcha;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import java.util.Random;
import java.util.UUID;

public class SlidePuzzleUtil {
    private final static Logger logger = LoggerFactory.getLogger(SlidePuzzleUtil.class);

    private static String BASE64_CUTTURE = "data:image/png;base64,";

    // 大图宽度(原图裁剪拼图后的背景图)***---一定要和前端显示框大小匹配***
    private static final Integer bigWidth = 414;
    // 大图高度  ***---一定要和前端显示框大小匹配***
    private static final Integer bigHeight = 253;
    // 小图宽度(滑块拼图)
    private static int smallWidth = 60;
    // 小图高度
    private static int smallHeight = 60;
    // 小圆半径,即拼图上的凹凸轮廓半径
    private static final Integer smallCircle = 8;
    // 小圆距离点
    private static int smallCircleR1 = smallCircle / 2;

    /**
     * 生成滑块拼图验证码
     *
     * @param input
     * @return 返回null, 表示生成滑块拼图验证码异常
     */
    public static SliderPuzzleInfo createImage(InputStream input) {
        SliderPuzzleInfo sliderPuzzleInfo = new SliderPuzzleInfo();
        try {
            // 1.获取原图对象
            BufferedImage originalImage = ImageIO.read(input);
            // 规范原图的大小
            BufferedImage bigImage = resizeImage(originalImage, bigWidth, bigHeight, true);

            // 2.随机生成离左上角的(X,Y)坐标,上限为 [bigWidth-smallWidth, bigHeight-smallHeight]。最好离大图左边远一点,上限不要紧挨着大图边界
            Random random = new Random();
            // X范围:[2*smallWidth, bigWidth - 2*smallWidth - smallCircle)
            int randomX = random.nextInt(bigWidth - 4 * smallWidth - smallCircle) + 2 * smallWidth;
            // Y范围:[smallCircle, bigHeight - smallHeight - smallCircle)
            int randomY = random.nextInt(bigHeight - smallHeight - 2 * smallCircle) + smallCircle;
            logger.info("原图大小:{} x {},大图大小:{} x {},随机生成的坐标:(X,Y)=({},{})", originalImage.getWidth(), originalImage.getHeight(), bigImage.getWidth(), bigImage.getHeight(),
                    randomX, randomY);

            // 3.创建小图对象
            BufferedImage smallImage = new BufferedImage(smallWidth, smallHeight, BufferedImage.TYPE_4BYTE_ABGR);

            // 4.随机生成拼图轮廓数据
            int[][] slideTemplateData = getSlideTemplateData(smallWidth, smallHeight, smallCircle, smallCircleR1);

            // 5.从大图中裁剪拼图。抠原图,裁剪拼图
            cutByTemplate(bigImage, smallImage, slideTemplateData, randomX, randomY);

            sliderPuzzleInfo.setPosX(randomX);
            sliderPuzzleInfo.setPosY(randomY);
            //sliderPuzzleInfo.setBigWidth(bigWidth);
            //sliderPuzzleInfo.setBigHeight(bigHeight);
            //sliderPuzzleInfo.setBigImage(bigImage);
            sliderPuzzleInfo.setBigImageBase64(getImageBASE64(bigImage));
            //sliderPuzzleInfo.setSmallWidth(smallWidth);
            //sliderPuzzleInfo.setSmallHeight(smallHeight);
            //sliderPuzzleInfo.setSmallImage(smallImage);
            sliderPuzzleInfo.setSmallImageBase64(getImageBASE64(smallImage));
        } catch (Exception e) {
            sliderPuzzleInfo = null;
            logger.info("创建生成滑块拼图验证码异常,e=", e);
        } finally {
            return sliderPuzzleInfo;
        }
    }

    /**
     * 获取拼图图轮廓数据
     *
     * @param smallWidth
     * @param smallHeight
     * @param smallCircle
     * @param r1
     * @return 0和1,其中0表示没有颜色,1有颜色
     */
    private static int[][] getSlideTemplateData(int smallWidth, int smallHeight, int smallCircle, int r1) {
        // 拼图轮廓数据
        int[][] data = new int[smallWidth][smallHeight];

        //拼图去掉凹凸的白色距离
        // 不写smallCircleR1时,凹凸为半圆
        int xBlank = smallWidth - smallCircle - smallCircleR1;
        int yBlank = smallHeight - smallCircle - smallCircleR1;

        // 圆的位置
        int rxa = xBlank / 2;
        int ryb = smallHeight - smallCircle;
        double rPow = Math.pow(smallCircle, 2);

        /**
         * 计算需要的拼图轮廓(方块和凹凸),用二维数组来表示,二维数组有两张值,0和1,其中0表示没有颜色,1有颜色
         * 圆的标准方程 (x-a)²+(y-b)²=r²,标识圆心(a,b),半径为r的圆
         */
        for (int i = 0; i < smallWidth; i++) {
            for (int j = 0; j < smallHeight; j++) {
                // 圆在拼图下方内
                double topR = Math.pow(i - rxa, 2) + Math.pow(j - 2, 2);
                // 圆在拼图下方外
                double downR = Math.pow(i - rxa, 2) + Math.pow(j - ryb, 2);
                // 圆在拼图左侧内 || (i <= xBlank && leftR <= rPow)
                //double leftR = Math.pow(i - 2, 2) + Math.pow(j - rxa, 2);
                // 圆在拼图右侧外
                double rightR = Math.pow(i - ryb, 2) + Math.pow(j - rxa, 2);
                if ((j <= yBlank && topR <= rPow) || (j >= yBlank && downR >= rPow)
                        || (i >= xBlank && rightR >= rPow)) {
                    data[i][j] = 0;
                } else {
                    data[i][j] = 1;
                }
            }
        }
        return data;
    }


    /**
     * 裁剪拼图
     *
     * @param bigImage          - 原图规范大小之后的大图
     * @param smallImage        - 小图
     * @param slideTemplateData - 拼图轮廓数据
     * @param x                 - 坐标x
     * @param y                 - 坐标y
     */
    private static void cutByTemplate(BufferedImage bigImage, BufferedImage smallImage, int[][] slideTemplateData, int x, int y) {
        int[][] martrix = new int[3][3];
        int[] values = new int[9];
        //拼图去掉凹凸的白色距离 // 不写smallCircleR1时,凹凸为半圆
        int xBlank = smallWidth - smallCircle - smallCircleR1;
        int yBlank = smallHeight - smallCircle - smallCircleR1;

        // 创建shape区域,即原图抠图区域模糊和抠出小图
        /**
         * 遍历小图轮廓数据,创建shape区域。即原图抠图处模糊和抠出小图
         */
        for (int i = 0; i < smallImage.getWidth(); i++) {
            for (int j = 0; j < smallImage.getHeight(); j++) {
                // 获取大图中对应位置变色
                //logger.info("随机生成的坐标:(X,Y)=({},{}),(i,j=({},{}),获取原图大小:{} x {}", x, y, i, j, x + i, y + j);
                int rgb_ori = bigImage.getRGB(x + i, y + j);

                //0和1,其中0表示没有颜色,1有颜色
                int rgb = slideTemplateData[i][j];
                if (rgb == 1) {
                    // 设置小图中对应位置变色
                    smallImage.setRGB(i, j, rgb_ori);

                    // 大图抠图区域高斯模糊
                    readPixel(bigImage, x + i, y + j, values);
                    fillMatrix(martrix, values);
                    bigImage.setRGB(x + i, y + j, avgMatrix(martrix));

                    //边框颜色
                    Color white = new Color(230, 230, 230);
                    Color black = new Color(20, 20, 20);
                    //左侧边界,加重高亮阴暗
                    if (j < yBlank) {
                        bigImage.setRGB(x, y + j, Color.LIGHT_GRAY.getRGB());
                        smallImage.setRGB(0, j, Color.GRAY.getRGB());
                    }
                } else {
                    // 这里把背景设为透明
                    smallImage.setRGB(i, j, rgb_ori & 0x00ffffff);
                }
            }
        }
    }

    /**
     * 图片转BASE64
     *
     * @param image
     * @return
     * @throws IOException
     */
    public static String getImageBASE64(BufferedImage image) throws IOException {
        byte[] imagedata = null;
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        ImageIO.write(image, "png", bao);
        imagedata = bao.toByteArray();
        String BASE64IMAGE = Base64.getEncoder().encodeToString(imagedata);
        BASE64IMAGE = BASE64IMAGE.replaceAll("\n", "").replaceAll("\r", "");
        return BASE64_CUTTURE + BASE64IMAGE;
    }

    /**
     * 改变图片大小
     *
     * @param image  原图
     * @param width  目标宽度
     * @param height 目标高度
     * @return 目标图
     */
    public static BufferedImage resizeImage(final Image image, int width, int height, boolean type) {
        BufferedImage bufferedImage;
        if (type) {
            bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        } else {
            bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        }

        final Graphics2D graphics2D = bufferedImage.createGraphics();
        graphics2D.setComposite(AlphaComposite.Src);
        // below three lines are for RenderingHints for better image quality at cost of
        // higher processing time
        graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphics2D.setStroke(new BasicStroke(5, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
        graphics2D.drawImage(image, 0, 0, width, height, null);
        graphics2D.dispose();
        return bufferedImage;
    }


    private static void readPixel(BufferedImage img, int x, int y, int[] pixels) {
        int xStart = x - 1;
        int yStart = y - 1;
        int current = 0;
        for (int i = xStart; i < 3 + xStart; i++) {
            for (int j = yStart; j < 3 + yStart; j++) {
                int tx = i;
                if (tx < 0) {
                    tx = -tx;

                } else if (tx >= img.getWidth()) {
                    tx = x;
                }
                int ty = j;
                if (ty < 0) {
                    ty = -ty;
                } else if (ty >= img.getHeight()) {
                    ty = y;
                }
                pixels[current++] = img.getRGB(tx, ty);

            }
        }

    }

    private static void fillMatrix(int[][] matrix, int[] values) {
        int filled = 0;
        for (int i = 0; i < matrix.length; i++) {
            int[] x = matrix[i];
            for (int j = 0; j < x.length; j++) {
                x[j] = values[filled++];
            }
        }
    }

    private static int avgMatrix(int[][] matrix) {
        int r = 0;
        int g = 0;
        int b = 0;
        for (int i = 0; i < matrix.length; i++) {
            int[] x = matrix[i];
            for (int j = 0; j < x.length; j++) {
                if (j == 1) {
                    continue;
                }
                Color c = new Color(x[j]);
                r += c.getRed();
                g += c.getGreen();
                b += c.getBlue();
            }
        }
        return new Color(r / 8, g / 8, b / 8).getRGB();
    }
}
package com.facemetric.tradingsystem.commons.captcha;

import java.awt.image.BufferedImage;
/**
 1. 返回实体对象
*/
public class SliderPuzzleInfo {
    /**
     * 大图宽度
     */
    private Integer bigWidth;

    /**
     * 大图高度
     */
    private Integer bigHeight;

    /**
     * 大图转BASE64字符串
     */
    private String bigImageBase64;

    /**
     * 大图
     */
    private BufferedImage bigImage;

    /**
     * 随机坐标Y
     */
    private Integer posY;
    /**
     * 随机坐标X
     */
    private Integer posX;

    /**
     * 小图宽度
     */
    private Integer smallWidth;
    /**
     * 小图高度
     */
    private Integer smallHeight;

    /**
     * 小图转BASE64字符串
     */
    private String smallImageBase64;

    /**
     * 小图
     */
    private BufferedImage smallImage;

    /**
     * 标识
     */
    private String captUuid;

    public String getCaptUuid() {
        return captUuid;
    }

    public void setCaptUuid(String captUuid) {
        this.captUuid = captUuid;
    }

    public Integer getBigWidth() {
        return bigWidth;
    }

    public void setBigWidth(Integer bigWidth) {
        this.bigWidth = bigWidth;
    }

    public Integer getBigHeight() {
        return bigHeight;
    }

    public void setBigHeight(Integer bigHeight) {
        this.bigHeight = bigHeight;
    }

    public String getBigImageBase64() {
        return bigImageBase64;
    }

    public void setBigImageBase64(String bigImageBase64) {
        this.bigImageBase64 = bigImageBase64;
    }

    public BufferedImage getBigImage() {
        return bigImage;
    }

    public void setBigImage(BufferedImage bigImage) {
        this.bigImage = bigImage;
    }

    public Integer getPosY() {
        return posY;
    }

    public void setPosY(Integer posY) {
        this.posY = posY;
    }

    public Integer getPosX() {
        return posX;
    }

    public void setPosX(Integer posX) {
        this.posX = posX;
    }

    public Integer getSmallWidth() {
        return smallWidth;
    }

    public void setSmallWidth(Integer smallWidth) {
        this.smallWidth = smallWidth;
    }

    public Integer getSmallHeight() {
        return smallHeight;
    }

    public void setSmallHeight(Integer smallHeight) {
        this.smallHeight = smallHeight;
    }

    public String getSmallImageBase64() {
        return smallImageBase64;
    }

    public void setSmallImageBase64(String smallImageBase64) {
        this.smallImageBase64 = smallImageBase64;
    }

    public BufferedImage getSmallImage() {
        return smallImage;
    }

    public void setSmallImage(BufferedImage smallImage) {
        this.smallImage = smallImage;
    }

    public SliderPuzzleInfo(Integer bigWidth, Integer bigHeight, String bigImageBase64, BufferedImage bigImage, Integer posY, Integer posX, Integer smallWidth, Integer smallHeight, String smallImageBase64, BufferedImage smallImage) {
        this.bigWidth = bigWidth;
        this.bigHeight = bigHeight;
        this.bigImageBase64 = bigImageBase64;
        this.bigImage = bigImage;
        this.posY = posY;
        this.posX = posX;
        this.smallWidth = smallWidth;
        this.smallHeight = smallHeight;
        this.smallImageBase64 = smallImageBase64;
        this.smallImage = smallImage;
    }

    public SliderPuzzleInfo() {
    }
}
  1. controlller层代码调用
/**
     * 生成滑块拼图验证码
     */
    @RequestMapping(value = "/rest/captchaCode", method = RequestMethod.GET)
    public SliderPuzzleInfo getImageCode(){
        Random random = new Random();
        int num = random.nextInt(4);
        String[] images = new String[]{"bgimages/2.jpg", "bgimages/4.jpg", "bgimages/6.jpg", "bgimages/7.jpg"};
        //匹配代码打包找不到图片资源  转化为 流的形式
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(images[num]);
        SliderPuzzleInfo sliderPuzzleInfo = SlidePuzzleUtil.createImage(resourceAsStream);
        if (sliderPuzzleInfo == null) {
            // 要是生成失败
            throw new NotErrorCause("生成滑块验证码失败,验证码生成为空");
        }
        String captUuid = UUID.randomUUID().toString().replace("-", "");
        sliderPuzzleInfo.setCaptUuid(captUuid);
        //将坐标存缓存中  方便取值
        this.cacheService.set(captUuid, sliderPuzzleInfo.getPosX());
        sliderPuzzleInfo.setPosX(null);
        return sliderPuzzleInfo;
    }

.

4 滑块验证代码

 /**
     * 得到图片验证的坐标,进行有效性校对
     *
     * @param captUuid
     * @param ImageX
     * @return
     */
    @RequestMapping(
            value = {"/rest/verifyCode"},
            method = {RequestMethod.GET}
    )
    public Boolean verifyImage(@RequestParam("captUuid") String captUuid, @RequestParam("ImageX") String ImageX) {
        Integer xPosition = this.cacheService.get(captUuid);
        if (null == xPosition) {
            return false;
        }
        //计算验证图片坐标的误差值
        int absX = Math.abs(Integer.parseInt(ImageX) - xPosition);
        if (absX < 6) {//也可以将坐标转化为 百分比形式验证(容错值 float tolerant = 0.02f)
            this.cacheService.delete(captUuid);
            return true;
        }
        return false;
    }

最终效果
在这里插入图片描述注:参考大神:https://blog.csdn.net/qq_42402854/article/details/125112511

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
滑块验证码是一种常见的验证码形式,旨在验证用户是否为人类。PyTorch是一个开源的深度学习框架,可以用它来实现滑块验证码识别系统。 滑块验证码通常由两个部分组成:滑块图片和背景图片滑块图片上有一个滑块,用户需要通过拖动滑块将其放到正确的位置,以完成验证。 要实现滑块验证码的识别,可以按照以下步骤进行: 1. 数据准备:收集大量的滑块验证码图片,并将其划分为训练集和测试集。训练集用于训练模型,测试集用于评估模型的性能。 2. 特征提取:使用PyTorch加载滑块验证码图片,并对其进行预处理。可以使用卷积神经网络(CNN)来提取图片的特征。 3. 训练模型:使用PyTorch构建一个深度学习模型,可以选择使用已经预训练好的模型作为基础模型,如ResNet、VGG等。然后,将提取的特征输入到模型中,通过反向传播来优化模型的参数。 4. 模型评估:使用测试集对训练好的模型进行评估,计算准确率、精度等指标,判断模型的性能。 5. 模型应用:将训练好的模型部署到实际的滑块验证码系统中,用户在登录或注册时需要完成验证码验证。用户拖动滑块,系统会根据用户的操作判断是否为真实用户。 通过使用PyTorch深度学习框架,我们可以方便地实现滑块验证码的识别。它提供了丰富的工具和算法,可以帮助我们构建高性能的滑块验证码识别系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值