前言
在网上找了很多提取图片轮廓的,结果不是很理想,之前使用opencv实现了提取湖泊轮廓,但是在打包到linux环境的时候出现一堆坑,感觉很麻烦并且占用服务器资源太大了,于是便使用gpt及自己的理解写出了一些使用ImageIO实现提取图片轮廓的代码,在此做一个笔记,存在一些瑕疵,希望有大佬能帮我解决
高德api获取地图图片
/**
* 图片高度
*/
public static final int HEIGHT = 1024;
/**
* 图片宽度
*/
public static final int WIDTH = 1024;
/**
* 高德apikey
*/
public static final String MAP_KEY = "your_key";
private static void saveLakeImage(double latitude, double longitude, int zoom, String savePath) {
String apiUrl = "https://restapi.amap.com/v3/staticmap?" +
"location=" + longitude + "," + latitude +
"&zoom=" + zoom +
"&size=" + HEIGHT + "*" + WIDTH +
"&key=" + MAP_KEY;
HttpURLConnection connection = null;
try {
URL url = new URL(apiUrl);
connection = (HttpURLConnection) url.openConnection();
InputStream in = connection.getInputStream();
FileOutputStream out = new FileOutputStream(savePath);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
图片转base64
/**
* 获取文件base64编码
*
* @param path 文件路径
* @param urlEncode 如果Content-Type是application/x-www-form-urlencoded时,传true
* @return base64编码信息,不带文件头
* @throws IOException IO异常
*/
static String getFileContentAsBase64(String path, boolean urlEncode) throws IOException {
byte[] b = Files.readAllBytes(Paths.get(path));
String base64 = Base64.getEncoder().encodeToString(b);
if (urlEncode) {
base64 = URLEncoder.encode(base64, "utf-8");
}
return base64;
}
灰度转换
// 灰度转换
private static void getGrayImage(BufferedImage image, String grayPath) throws IOException {
int width = image.getWidth();
int height = image.getHeight();
// 遍历图像像素,查找边缘像素
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// 获取像素的RGB值
int rgb = image.getRGB(x, y);
// 如果像素为黑色,则将其作为轮廓像素
if (isWithinColorRange(rgb)) {
image.setRGB(x, y, Color.BLACK.getRGB());
} else {
image.setRGB(x, y, Color.white.getRGB());
}
}
}
File outputFile = new File(grayPath);
ImageIO.write(image, "jpg", outputFile);
}
private static boolean isWithinColorRange(int rgb) {
Color color1 = new Color(162, 203, 254); // 第一个颜色范围的RGB值
Color color2 = new Color(172, 209, 254); // 第二个颜色范围的RGB值
Color pixelColor = new Color(rgb);
return (pixelColor.getRed() >= color1.getRed() && pixelColor.getRed() <= color2.getRed()) &&
(pixelColor.getGreen() >= color1.getGreen() && pixelColor.getGreen() <= color2.getGreen()) &&
(pixelColor.getBlue() >= color1.getBlue() && pixelColor.getBlue() <= color2.getBlue());
}
获取轮廓坐标
public static void getLake(String grayPath, String lakePath,double lat,double lng,int zoom) throws IOException {
String base64 = getFileContentAsBase64(grayPath, false);
// 解码图像数据
byte[] imageBytes = java.util.Base64.getDecoder().decode(base64);
ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes);
BufferedImage sourceImage = ImageIO.read(bis);
// 边缘跟踪
BufferedImage edgeImage = traceEdges(sourceImage);
ImageIO.write(edgeImage, "jpg", new File(lakePath));
// 将结果图像写入文件
List<Point> contours = findContours(edgeImage);
List<double[]> contourCoordinates = new ArrayList<>();
List<double[]> lake = new ArrayList<>();
Point center = new Point(edgeImage.getWidth() / 2, edgeImage.getHeight() / 2);
if (contours.size() > 0) {
for (Point point : contours) {
contourCoordinates.add(new double[]{point.x, point.y});
lake.add(toLocation(lat,lng, zoom, center, point.x, point.y));
}
}
System.out.println(JSON.toJSONString(contourCoordinates));
System.out.println(JSON.toJSONString(lake));
}
//边缘跟踪
private static BufferedImage traceEdges(BufferedImage sourceImage) {
int width = sourceImage.getWidth();
int height = sourceImage.getHeight();
BufferedImage edgeImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
// 使用Sobel算子进行边缘检测
int[][] sobelX = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
int[][] sobelY = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
for (int y = 1; y < height - 1; y++) {
for (int x = 1; x < width - 1; x++) {
int gx = 0;
int gy = 0;
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
int pixel = sourceImage.getRGB(x + i, y + j) & 0xFF; // 获取灰度值
gx += sobelX[i + 1][j + 1] * pixel;
gy += sobelY[i + 1][j + 1] * pixel;
}
}
int magnitude = (int) Math.sqrt(gx * gx + gy * gy);
if (magnitude > 100) { // 使用阈值进行二值化
edgeImage.setRGB(x, y, Color.BLACK.getRGB());
} else {
edgeImage.setRGB(x, y, Color.WHITE.getRGB());
}
}
}
return edgeImage;
}
//获取中心点最近的轮廓像素坐标
private static List<Point> findContours(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
boolean[][] visited = new boolean[width][height]; // 用于记录每个像素是否已被访问
double centerX = width / 2.0;
double centerY = height / 2.0;
double minDistance = Double.MAX_VALUE;
List<Point> nearestContour = null;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (image.getRGB(x, y) == Color.BLACK.getRGB() && !visited[x][y]) {
List<Point> contour = floodFill(image, x, y, visited, width, height);
// 计算轮廓到中心点的距离
double distance = calculateContourDistance(contour, centerX, centerY);
if (distance < minDistance) {
minDistance = distance;
nearestContour = contour;
}
}
}
}
return nearestContour;
}
//获取轮廓坐标的算法
private static List<Point> floodFill(BufferedImage image, int x, int y, boolean[][] visited, int width, int height) {
List<Point> contour = new ArrayList<>();
Stack<Point> stack = new Stack<>();
stack.push(new Point(x, y));
while (!stack.isEmpty()) {
Point point = stack.pop();
int px = point.x;
int py = point.y;
if (px >= 0 && px < width && py >= 0 && py < height && image.getRGB(px, py) == Color.BLACK.getRGB() && !visited[px][py]) {
contour.add(point);
image.setRGB(px, py, 0); // 将像素点设置为0,表示已访问
visited[px][py] = true; // 标记为已访问
stack.push(new Point(px + 1, py));
stack.push(new Point(px - 1, py));
stack.push(new Point(px, py + 1));
stack.push(new Point(px, py - 1));
}
}
visited[x][y] = true; // 标记为已访问
return contour;
}
问题
因为无法将轮廓坐标像素抽取成一个像素点连成的坐标系,导致坐标存在大量冗余,页面图形展示不好,并且上述方法对轮廓超出图像的处理会报错