html免费网站模板带后台/开发网站的流程是
这一步是最难的,参考了很多论文和博文,最终选择 X 轴投影法(垂直投影法)、CFS连通域法、惯性滴水算法。惯性大水滴滴水算法效果不明显舍弃。验证码切割最怕的就是黏连字符,相比干扰线和噪点就难多了。像这种黏连字符就很难了。
1、CFS连通域法
缺点:某些字符分割错误,比如 "回" 会被 分割成 一个外框 "口" 和 一个内部 "口",这里验证码没有这个问题。
黏连字符没法分割。
优点:优点还是很明显的,只要字符之间不黏连,不论是旋转或者扭曲都能准确的分割。
这里可以用来初步的切割,先把能切割的字符切出来。
分割前,先将图片处理成灰度图片
package com.demo.verify;import java.awt.Color;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;import javax.imageio.ImageIO;/*** CFS连通域法分割*/
public class CFS {private static int minPix = 50;// 单个图片最小的像素数width*height,下面会抛弃小于这个像素数的小图块public static void main(String[] args) throws Exception {String imageFile = "C:/verify/6097.png";File file = new File(imageFile);BufferedImage sourceImage = ImageIO.read(file);List<BufferedImage> list = new ArrayList<>();getImgCell(sourceImage, list);for (int j = 0; j < list.size(); j++) {BufferedImage subImg = list.get(j);String prex = file.getName().split("\\.")[0];String suffix = file.getName().split("\\.")[1];String filename = prex + "-" + j + "." + suffix;ImageIO.write(subImg, "png", new File(file.getParentFile().getPath(), filename));}}/*** CFS连通域法* @param img 需要拆解的图(java.awt.image.BufferedImage)* @param subImgs* @throws Exception* @author lihuaishu* @date 2021年12月22日 下午12:57:22*/public static void getImgCell(BufferedImage img, List<BufferedImage> subImgs) throws Exception {// 获取图片宽高if (img != null) {int width = img.getWidth();int height = img.getHeight();HashMap<Point, Integer> pointMap;// 一个图块的点(黑色点)Point point;// 用于装填每个图块的点数据List<HashMap<Point, Integer>> pointList = new ArrayList<HashMap<Point, Integer>>();// 根据宽高遍历图片中的所有点进行计算for (int x = 0; x < width; ++x) {for (int y = 0; y < height; ++y) {// 找到一个点label:if (isBlack(img.getRGB(x, y))) {point = new Point(x, y);// java.awt.Pointfor (HashMap<Point, Integer> pointHashMap : pointList) {if (pointHashMap.get(point) != null) {break label;// 跳到标签处,此时不会再执行下面的内容.}}pointMap = new HashMap<Point, Integer>();// 这个用法很关键,根据Map的KEY值不能重复的特点避免重复填充pointpointMap.put(point, 1);// 根据当前坐标点,递归获取与该点连接的所有点getAllPoint(x, y, img, pointMap, 0);pointList.add(pointMap);break;}}}BufferedImage imgCell;int l, t, r, b, w, h, index;// 根据提取出来的point创建各个碎图块for (int i = 0; i < pointList.size(); ++i) {pointMap = pointList.get(i);// 图片的左,上,右,下边界以及宽,高l = t = r = b = w = h = index = 0;for (Point p : pointMap.keySet()) {if (index == 0) {// 用第一个点来初始化碎图的四个边界l = p.x;t = p.y;r = p.x;b = p.y;} else {// 再根据每个点与原有的点进行比较取舍四个边界的值l = Math.min(l, p.x);t = Math.min(t, p.y);r = Math.max(r, p.x);b = Math.max(b, p.y);}index++;}w = r - l + 1;h = b - t + 1;// 去除杂点(小于50像素数量的点集不要)if (w * h < minPix)continue;// 创建个图片空壳子(里面的所有点值都是0,即黑色)imgCell = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);// 先将所有点替换成白色(反正我没有找到new BufferedImage的时候可以初始化像素色值的)for (int x = 0; x < w; ++x) {for (int y = 0; y < h; ++y) {imgCell.setRGB(x, y, 0xffffffff);// 图片换成白底}}// 图像对应点换成黑色(如果上面换成白底的一步不做的话下面这里可以替换成白色,就变成了黑底白色的碎图了)for (Point p : pointMap.keySet()) {imgCell.setRGB(p.x - l, p.y - t, 0);}// 将切好的图放入上文传入的容器中(不传入容器的话这里可以用于返回)subImgs.add(imgCell);}}}/*** 根据当前坐标点,递归获取与该点连接的所有点* @param x 当前点x轴坐标* @param y 当前点y轴坐标* @param img 原图* @param pointMap 一个图块的点(黑色点),可能是一个字符也可能是多个字符* @param diguiTime 递归次数*/private static void getAllPoint(int x, int y, BufferedImage img, HashMap<Point, Integer> pointMap, int diguiTime) {diguiTime++;// 递归次数if (diguiTime >= 500) {throw new RuntimeException("递归次数大于500");}// 左边Point pl = new Point(x - 1, y);if (x - 1 >= 0 && isBlack(img.getRGB(x - 1, y)) && pointMap.get(pl) == null) {pointMap.put(pl, 1);getAllPoint(x - 1, y, img, pointMap, diguiTime);}// 右边Point pr = new Point(x + 1, y);if (x + 1 < img.getWidth() && isBlack(img.getRGB(x + 1, y)) && pointMap.get(pr) == null) {pointMap.put(pr, 1);getAllPoint(x + 1, y, img, pointMap, diguiTime);}// 上边Point pt = new Point(x, y - 1);if (y - 1 >= 0 && isBlack(img.getRGB(x, y - 1)) && pointMap.get(pt) == null) {pointMap.put(pt, 1);getAllPoint(x, y - 1, img, pointMap, diguiTime);}// 下边Point pb = new Point(x, y + 1);if (y + 1 < img.getHeight() && isBlack(img.getRGB(x, y + 1)) && pointMap.get(pb) == null) {pointMap.put(pb, 1);getAllPoint(x, y + 1, img, pointMap, diguiTime);}diguiTime--;}/*** 判断是否为黑色像素点* @param rgb* @return*/private static boolean isBlack(int rgb) {Color color = new Color(rgb);if (color.getRed() + color.getGreen() + color.getBlue() <= 300) {return true;}return false;}}
这里需要注意一下,minPix = 50;// 单个图片最小的像素数width*height,这个用来剔除不需要的毛刺。
效果:
切割后有3个块: