private void cannyImage() {
if (image != null) {
try {
// Запрос пользователю параметров
String inputMask = JOptionPane.showInputDialog(this,“Enter a mask size (odd number >= 3)”);
int maskSize = Integer.parseInt(inputMask);
if (maskSize < 3 || maskSize % 2 == 0) {
throw new NumberFormatException(“Invalid mask size.”); // бросаем исключение для некорректного размера маски
}
int threshold1 = Integer.parseInt(JOptionPane.showInputDialog(this, “Enter a lower threshold:”));
int threshold2 = Integer.parseInt(JOptionPane.showInputDialog(this, “Enter an upper threshold:”));
// Применяем детектор Канни с заданными параметрами
BufferedImage cannyImage = cannyEdgeDetector(image, maskSize, threshold1, threshold2);
setImageIcon(cannyImage);
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(this, "Invalid input: " + e.getMessage(), “Error”, JOptionPane.ERROR_MESSAGE);
}
}
}

// Реализация детектора Канни
private BufferedImage cannyEdgeDetector(BufferedImage image, int maskSize, int threshold1, int threshold2) {
// 1. Применяем Гауссов фильтр
float[][] mask = gaussianMask(maskSize, maskSize/2);
BufferedImage smoothedImage = applyMask(image, mask);

// 2. Вычисляем градиенты
BufferedImage[] gradientImages = gradient(smoothedImage);

// 3. Находим подавленные границы
BufferedImage suppressedImage = suppression(gradientImages[0], gradientImages[1]);

// 4. Применяем двойную пороговую фильтрацию
BufferedImage thresholdedImage = threshold(suppressedImage, threshold1, threshold2);

// 5. Применяем операцию скелетизации
BufferedImage skeletonImage = skeleton(thresholdedImage);

return skeletonImage;
}

// Метод для вычисления градиентов
private BufferedImage[] gradient(BufferedImage smoothedImage) {
float[][] maskX = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
float[][] maskY = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
BufferedImage imageX = applyMask(smoothedImage, maskX);
BufferedImage imageY = applyMask(smoothedImage, maskY);
return new BufferedImage[] {imageX, imageY};
}

// Метод для подавления не-максимумов
private BufferedImage suppression(BufferedImage imageX, BufferedImage imageY) {
BufferedImage suppressedImage = new BufferedImage(imageX.getWidth(), imageX.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
for (int x = 1; x < imageX.getWidth() - 1; x++) {
for (int y = 1; y < imageX.getHeight() - 1; y++) {
int gradientX = getPixelValue(imageX, x, y);
int gradientY = getPixelValue(imageY, x, y);
int gradientMagnitude = (int) Math.round(Math.sqrt(gradientX * gradientX + gradientY * gradientY));
int gradientAngle = (int) Math.round(Math.toDegrees(Math.atan2(gradientY, gradientX)));
// Подавление не-максимумов
if (gradientX == 0) {
if (getPixelValue(imageY, x, y - 1) > gradientMagnitude || getPixelValue(imageY, x, y + 1) > gradientMagnitude) {
gradientMagnitude = 0;
}
} else {
double tangent = (double) gradientY / (double) gradientX;
if (Math.abs(tangent) < 1) {
if (getPixelValue(imageX, x - 1, y) > gradientMagnitude || getPixelValue(imageX, x + 1, y) > gradientMagnitude) {
gradientMagnitude = 0;
}
} else {
if (getPixelValue(imageY, x, y - 1) > gradientMagnitude || getPixelValue(imageY, x, y + 1) > gradientMagnitude) {
gradientMagnitude = 0;
}
}
}
setPixelValue(suppressedImage, x, y, gradientMagnitude);
}
}
return suppressedImage;
}

// Метод для применения пороговой фильтрации
private BufferedImage threshold(BufferedImage suppressedImage, int threshold1, int threshold2) {
BufferedImage thresholdedImage = new BufferedImage(suppressedImage.getWidth(), suppressedImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
for (int x = 0; x < suppressedImage.getWidth(); x++) {
for (int y = 0; y < suppressedImage.getHeight(); y++) {
int pixelValue = getPixelValue(suppressedImage, x, y);
// Двойная пороговая фильтрация
if (pixelValue <