Добавить в код детектор границ Канни с возможностью задания маски и
основных параметров. 
public class ImageReader extends JFrame implements ActionListener {

	private JButton openButton, processButton, brightnessButton, contrastButton, sawtoothButton, gaussButton,cannyButton;
	private JLabel imageLabel;
	private BufferedImage image;
	private BigDecimal imageWidth, imageHeight;

	public ImageReader() {
		super("Image Reader");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		// initialize imageWidth and imageHeight to zero to avoid NullPointerException
		imageWidth = BigDecimal.ZERO;
		imageHeight = BigDecimal.ZERO;

		openButton = new JButton("Open");
		openButton.addActionListener(this);

		processButton = new JButton("Process");
		processButton.addActionListener(this);

		brightnessButton = new JButton("Brightness");
		brightnessButton.addActionListener(this);
		
		contrastButton = new JButton("Contrasts");
		contrastButton.addActionListener(this);
		
		sawtoothButton = new JButton("Sawtooth");
		sawtoothButton.addActionListener(this);
		
		gaussButton = new JButton("Gauss");
		gaussButton.addActionListener(this);
		
		cannyButton = new JButton("Canny");
		cannyButton.addActionListener(this);

		imageLabel = new JLabel();
		imageLabel.setVerticalAlignment(JLabel.CENTER);
		imageLabel.setHorizontalAlignment(JLabel.CENTER);

		JPanel buttonPanel = new JPanel();
		buttonPanel.add(openButton);
		buttonPanel.add(processButton);
		buttonPanel.add(brightnessButton);
		buttonPanel.add(contrastButton);
		buttonPanel.add(sawtoothButton);
		buttonPanel.add(gaussButton);
		buttonPanel.add(cannyButton);


		JScrollPane scrollPane = new JScrollPane(imageLabel);
		add(scrollPane, BorderLayout.CENTER);

		add(buttonPanel, BorderLayout.NORTH);

		setExtendedState(JFrame.MAXIMIZED_BOTH); // set the window to full screen
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setVisible(true);

		openImage();
	}

	private BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) {
		BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, originalImage.getType());
		Graphics2D g2d = resizedImage.createGraphics();
		g2d.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null);
		g2d.dispose();
		return resizedImage;
		}

		private void setImageIcon(BufferedImage image) {
		ImageIcon icon = new ImageIcon(image);
		imageLabel.setIcon(icon);
		}

	
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == openButton) {
			openImage();
		} else if (e.getSource() == processButton) {
			processImage();
		} else if (e.getSource() == brightnessButton) {
			brightnessImage();
				
		}
		else if (e.getSource() == contrastButton) {
			contrastImage();
	}
		else if (e.getSource() == sawtoothButton) {
			sawtoottImage();
	}
		else if (e.getSource() == gaussButton) {
			gaussImage();
	}
	
		else if (e.getSource() == cannyButton) {
			cannyImage();
	}
		
	}
	private void processImage() {
		if (image != null) {
			BufferedImage binarizedImage = binarizeImage(image);
			setImageIcon(binarizedImage);
		}
	}
	private void brightnessImage() {
		if (image != null) {
			BufferedImage brightnessSliceImage = brightnessSlice(image, 100, 200); // яркость в диапазоне [100, 200]
			setImageIcon(brightnessSliceImage);
		}
	}
	private void contrastImage() {
		if (image != null) {
		BufferedImage contrastStretchedImage = linearContrastStretch(image);
		setImageIcon(contrastStretchedImage);
		}
		}
	
	private void sawtoottImage() {
		if (image != null) {
		BufferedImage[] sawtoothContrastImages = new BufferedImage[4];
		sawtoothContrastImages[0] = sawtoothContrast(image, 0.2f, 0.8f, 1f);
		// Пилообразное контрастирование с различными значениями коэффициента наклона
		sawtoothContrastImages[1] = sawtoothContrast(image, 0.5f, 0.5f, 1f);
		sawtoothContrastImages[2] = sawtoothContrast(image, 0.75f, 0.25f, 1f);
		sawtoothContrastImages[3] = sawtoothContrast(image, 0.9f, 0.1f, 1f);
		// Выводим четыре пилообразных контраста
		for(int i=0;i<4;i++) {
		ImageIcon icon = new ImageIcon(sawtoothContrastImages[i]);
		JOptionPane.showMessageDialog(this, icon, "Sawtooth Contrast "+(i+1), JOptionPane.INFORMATION_MESSAGE);
		}
		setImageIcon(sawtoothContrastImages[0]);
		}
		}
	
	
	private void gaussImage() {
		if (image != null) {
		String inputMask = JOptionPane.showInputDialog(this, "Enter a mask size (odd number):");
		int maskSize = Integer.parseInt(inputMask);
		float[][] mask = gaussianMask(maskSize, maskSize/2);
		BufferedImage gaussImage = applyMask(image, mask);
		setImageIcon(gaussImage);
		}
		}
	




	private BufferedImage applyMask(BufferedImage image, float[][] mask) {
		BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
		int maskWidth = mask[0].length;
		int maskHeight = mask.length;
		int maskCenterX = maskWidth/2;
		int maskCenterY = maskHeight/2;
		for (int i = 0; i < image.getWidth(); i++) {
		for (int j = 0; j < image.getHeight(); j++) {
		float red = 0, green = 0, blue = 0;
		for (int k = i-maskCenterX, x=0; k<=i+maskCenterX; k++, x++) {
		for (int l = j-maskCenterY, y=0; l<=j+maskCenterY; l++, y++) {
		if(k>=0 && k<image.getWidth() && l>=0 && l<image.getHeight()) {
		int rgb = image.getRGB(k, l);
		float maskValue = mask[y][x];
		red += ((rgb >> 16) & 0xff) * maskValue;
		green += ((rgb >> 8) & 0xff) * maskValue;
		blue += (rgb & 0xff) * maskValue;
		}
		}
		}
		int newRGB = ((int)red << 16) | ((int)green << 8) | (int)blue;
		newImage.setRGB(i, j, newRGB);
		}
		}
		return newImage;
		}

		private float[][] gaussianMask(int size, float sigma) {
		float[][] mask = new float[size][size];
		float sum = 0;
		int center = size/2;
		for(int i=0;i<size;i++) {
		for(int j=0;j<size;j++) {
		float x = i-center;
		float y = j-center;
		float value = (float) (1/(2*Math.PI*sigma*sigma) * Math.exp(-(x*x+y*y)/(2*sigma*sigma)));
		mask[i][j] = value;
		sum += value;
		}
		}
		// normalizing mask
		for(int i=0;i<size;i++) {
		for(int j=0;j<size;j++) {
		mask[i][j] /= sum;
		}
		}
		return mask;
		}
	
	
	
	private BufferedImage binarizeImage(BufferedImage image) {
		int threshold = 128; // пороговое значение
		BufferedImage binarizedImage = new BufferedImage(image.getWidth(), image.getHeight(),
				BufferedImage.TYPE_BYTE_BINARY);
		for (int i = 0; i < image.getWidth(); i++) {
			for (int j = 0; j < image.getHeight(); j++) {
				int rgb = image.getRGB(i, j);
				int r = (rgb & 0x00ff0000) >> 16;
				int g = (rgb & 0x0000ff00) >> 8;
				int b = rgb & 0x000000ff;
				int gray = (r + g + b) / 3; // среднее значение яркости
				if (gray > threshold) {
					binarizedImage.setRGB(i, j, Color.WHITE.getRGB());
				} else {
					binarizedImage.setRGB(i, j, Color.BLACK.getRGB());
				}
			}
		}
		return binarizedImage;
	}

	private BufferedImage brightnessSlice(BufferedImage image, int minBrightness, int maxBrightness) {
		BufferedImage brightnessSliceImage = new BufferedImage(image.getWidth(), image.getHeight(),
		BufferedImage.TYPE_INT_RGB);
		BufferedImage blackImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
		for (int i = 0; i < image.getWidth(); i++) {
		for (int j = 0; j < image.getHeight(); j++) {
		int rgb = image.getRGB(i, j);
		int r = (rgb & 0x00ff0000) >> 16;
		int g = (rgb & 0x0000ff00) >> 8;
		int b = rgb & 0x000000ff;
		int brightness = (int) (0.2126 * r + 0.7152 * g + 0.0722 * b); // расчет яркости пикселя
		if (brightness >= minBrightness && brightness <= maxBrightness) {
		brightnessSliceImage.setRGB(i, j, rgb);
		} else {
		brightnessSliceImage.setRGB(i, j, Color.BLACK.getRGB());
		blackImage.setRGB(i, j, rgb);
		}
		}
		}
		// выводим два среза: яркостной с черным цветом и без изменения
		ImageIcon icon = new ImageIcon(brightnessSliceImage);
		ImageIcon icon2 = new ImageIcon(blackImage);
		JOptionPane.showMessageDialog(this, icon, "Brightness Slice with Black", JOptionPane.INFORMATION_MESSAGE);
		JOptionPane.showMessageDialog(this, icon2, "Original Image", JOptionPane.INFORMATION_MESSAGE);
		return brightnessSliceImage;
		}
	private BufferedImage linearContrastStretch(BufferedImage image) {
		int redMin = 255, greenMin = 255, blueMin = 255;
		int redMax = 0, greenMax = 0, blueMax = 0;
		// находим минимальное и максимальное значения яркости по каждому цветовому каналу
		for (int i = 0; i < image.getWidth(); i++) {
		for (int j = 0; j < image.getHeight(); j++) {
		int rgb = image.getRGB(i, j);
		int r = (rgb >> 16) & 0xff;
		int g = (rgb >> 8) & 0xff;
		int b = rgb & 0xff;
		if (r < redMin) {
		redMin = r;
		}
		if (g < greenMin) {
		greenMin = g;
		}
		if (b < blueMin) {
		blueMin = b;
		}
		if (r > redMax) {
		redMax = r;
		}
		if (g > greenMax) {
		greenMax = g;
		}
		if (b > blueMax) {
		blueMax = b;
		}
		}
		}
		// линейное растяжение контраста
		float redK = 255f / (redMax - redMin);
		float greenK = 255f / (greenMax - greenMin);
		float blueK = 255f / (blueMax - blueMin);
		BufferedImage contrastStretchedImage1 = new BufferedImage(image.getWidth(), image.getHeight(),
		BufferedImage.TYPE_INT_RGB);
		BufferedImage contrastStretchedImage2 = new BufferedImage(image.getWidth(), image.getHeight(),
		BufferedImage.TYPE_INT_RGB);
		BufferedImage contrastStretchedImage3 = new BufferedImage(image.getWidth(), image.getHeight(),
		BufferedImage.TYPE_INT_RGB);
		for (int i = 0; i < image.getWidth(); i++) {
		for (int j = 0; j < image.getHeight(); j++) {
		int rgb = image.getRGB(i, j);
		int r = (rgb >> 16) & 0xff;
		int g = (rgb >> 8) & 0xff;
		int b = rgb & 0xff;
		// новые значения яркости
		int newR = (int) ((r - redMin) * redK);
		int newG = (int) ((g - greenMin) * greenK);
		int newB = (int) ((b - blueMin) * blueK);
		int newRGB1 = (newR << 16) | 0x000000 | 0x000000;
		int newRGB2 = 0x000000 | (newG << 8) | 0x000000;
		int newRGB3 = 0x000000 | 0x000000 | newB;
		contrastStretchedImage1.setRGB(i, j, newRGB1);
		contrastStretchedImage2.setRGB(i, j, newRGB2);
		contrastStretchedImage3.setRGB(i, j, newRGB3);
		}
		}
		// выводим три контраста линейного растяжения
		ImageIcon icon1 = new ImageIcon(contrastStretchedImage1);
		ImageIcon icon2 = new ImageIcon(contrastStretchedImage2);
		ImageIcon icon3 = new ImageIcon(contrastStretchedImage3);
		JOptionPane.showMessageDialog(this, icon1, "Red Channel Contrast Stretch", JOptionPane.INFORMATION_MESSAGE);
		JOptionPane.showMessageDialog(this, icon2, "Green Channel Contrast Stretch", JOptionPane.INFORMATION_MESSAGE);
		JOptionPane.showMessageDialog(this, icon3, "Blue Channel Contrast Stretch", JOptionPane.INFORMATION_MESSAGE);
		return image;
		}
	


		private BufferedImage sawtoothContrast(BufferedImage image, float k1, float k2, float k3) {
		BufferedImage sawtoothContrastImage = new BufferedImage(image.getWidth(), image.getHeight(),
		BufferedImage.TYPE_INT_RGB);
		for (int i = 0; i < image.getWidth(); i++) {
		for (int j = 0; j < image.getHeight(); j++) {
		int rgb = image.getRGB(i, j);
		int r = (rgb >> 16) & 0xff;
		int g = (rgb >> 8) & 0xff;
		int b = rgb & 0xff;
		// новые значения яркости
		int newR = (int) ((r - 128) * k1 + 128);
		int newG = (int) ((g - 128) * k2 + 128);
		int newB = (int) ((b - 128) * k3 + 128);
		int newRGB = (newR << 16) | (newG << 8) | newB;
		sawtoothContrastImage.setRGB(i, j, newRGB);
		}
		}
		return sawtoothContrastImage;
		}
		

    private void openImage() {
    	JFileChooser fileChooser = new JFileChooser();
    	int option = fileChooser.showOpenDialog(this);
    	if (option == JFileChooser.APPROVE_OPTION) {
    		File file = fileChooser.getSelectedFile();
    		try {
    			image = ImageIO.read(file);
    			setImageIcon(image); // set the opened image to the imageLabel
    			imageWidth = new BigDecimal(image.getWidth()); // set the width of the image
    			imageHeight = new BigDecimal(image.getHeight()); // set the height of the image
    		} catch (IOException e) {
    			JOptionPane.showMessageDialog(this, "Error opening image", "Error", JOptionPane.ERROR_MESSAGE);
    		}
    	}
    }
    

    public static void main(String[] args) {
        new ImageReader();
    }
}