图片和视频去水印软件开发方案JAVA版本

1. 项目概述与环境配置

项目架构设计

本项目采用模块化设计,包含以下核心模块:

  • 图形用户界面(GUI)
  • 图像处理模块(基于OpenCV)
  • 视频处理模块(基于FFmpeg)
  • 文件管理模块

Maven依赖配置

<dependencies>
    <!-- OpenCV图像处理 -->
    <dependency>
        <groupId>org.openpnp</groupId>
        <artifactId>opencv</artifactId>
        <version>4.5.3-2</version>
    </dependency>
    
    <!-- JavaCV (FFmpeg封装) -->
    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>javacv-platform</artifactId>
        <version>1.5.7</version>
    </dependency>
    
    <!-- Swing图形界面 -->
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-controls</artifactId>
        <version>17</version>
    </dependency>
</dependencies>

2. 核心图像去水印实现

基于OpenCV的图像处理类

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.photo.Photo;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;

public class ImageWatermarkRemover {
    private Mat sourceImage;
    private Mat resultImage;
    private Point startPoint = new Point();
    private Point endPoint = new Point();
    private boolean isSelecting = false;
    
    static {
        // 加载OpenCV本地库[2](@ref)
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }
    
    /**
     * 加载图像文件
     */
    public BufferedImage loadImage(String filePath) {
        try {
            sourceImage = Imgcodecs.imread(filePath);
            if (sourceImage.empty()) {
                JOptionPane.showMessageDialog(null, "无法加载图像文件");
                return null;
            }
            return matToBufferedImage(sourceImage);
        } catch (Exception e) {
            JOptionPane.showMessageDialog(null, "图像加载错误: " + e.getMessage());
            return null;
        }
    }
    
    /**
     * 设置水印区域坐标
     */
    public void setWatermarkRegion(int x, int y, int width, int height) {
        this.startPoint.x = x;
        this.startPoint.y = y;
        this.endPoint.x = x + width;
        this.endPoint.y = y + height;
    }
    
    /**
     * 使用图像修复算法去除水印[2,3](@ref)
     */
    public BufferedImage removeWatermark() {
        if (sourceImage == null) {
            JOptionPane.showMessageDialog(null, "请先加载图像");
            return null;
        }
        
        try {
            // 创建水印区域的掩码
            Mat mask = createMaskFromRegion();
            
            // 使用OpenCV的inpainting算法修复水印区域[2](@ref)
            resultImage = new Mat();
            Photo.inpaint(sourceImage, mask, resultImage, 3, Photo.INPAINT_TELEA);
            
            return matToBufferedImage(resultImage);
        } catch (Exception e) {
            JOptionPane.showMessageDialog(null, "去水印处理失败: " + e.getMessage());
            return null;
        }
    }
    
    /**
     * 根据选定的区域创建掩码
     */
    private Mat createMaskFromRegion() {
        Mat mask = Mat.zeros(sourceImage.size(), CvType.CV_8UC1);
        
        // 确保区域在图像范围内
        int x1 = (int) Math.max(0, Math.min(startPoint.x, sourceImage.width() - 1));
        int y1 = (int) Math.max(0, Math.min(startPoint.y, sourceImage.height() - 1));
        int x2 = (int) Math.max(0, Math.min(endPoint.x, sourceImage.width() - 1));
        int y2 = (int) Math.max(0, Math.min(endPoint.y, sourceImage.height() - 1));
        
        // 绘制矩形掩码区域
        Imgproc.rectangle(mask, 
            new Point(x1, y1), 
            new Point(x2, y2), 
            new Scalar(255), // 白色填充
            -1); // 实心矩形
        
        return mask;
    }
    
    /**
     * 保存处理后的图像
     */
    public boolean saveImage(String outputPath) {
        if (resultImage == null || resultImage.empty()) {
            JOptionPane.showMessageDialog(null, "没有可保存的处理结果");
            return false;
        }
        
        try {
            return Imgcodecs.imwrite(outputPath, resultImage);
        } catch (Exception e) {
            JOptionPane.showMessageDialog(null, "保存失败: " + e.getMessage());
            return false;
        }
    }
    
    /**
     * 将OpenCV的Mat转换为BufferedImage
     */
    private BufferedImage matToBufferedImage(Mat mat) {
        try {
            int type = BufferedImage.TYPE_BYTE_GRAY;
            if (mat.channels() > 1) {
                type = BufferedImage.TYPE_3BYTE_BGR;
            }
            
            int bufferSize = mat.channels() * mat.cols() * mat.rows();
            byte[] buffer = new byte[bufferSize];
            mat.get(0, 0, buffer);
            
            BufferedImage image = new BufferedImage(mat.cols(), mat.rows(), type);
            final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
            System.arraycopy(buffer, 0, targetPixels, 0, buffer.length);
            
            return image;
        } catch (Exception e) {
            System.err.println("图像转换错误: " + e.getMessage());
            return null;
        }
    }
}

3. 视频去水印实现

基于FFmpeg的视频处理类

import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import javax.swing.*;
import java.io.File;

public class VideoWatermarkRemover {
    private String inputPath;
    private String outputPath;
    private int x, y, width, height;
    
    /**
     * 设置水印区域参数
     */
    public void setWatermarkRegion(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
    
    /**
     * 使用FFmpeg去除视频水印[5](@ref)
     */
    public boolean removeVideoWatermark(String inputPath, String outputPath) {
        this.inputPath = inputPath;
        this.outputPath = outputPath;
        
        FFmpegFrameGrabber grabber = null;
        FFmpegFrameRecorder recorder = null;
        
        try {
            // 初始化帧抓取器
            grabber = new FFmpegFrameGrabber(inputPath);
            grabber.start();
            
            // 初始化帧录制器
            recorder = new FFmpegFrameRecorder(outputPath, 
                grabber.getImageWidth(), grabber.getImageHeight());
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            recorder.setFormat("mp4");
            recorder.setFrameRate(grabber.getFrameRate());
            recorder.start();
            
            Frame frame;
            Java2DFrameConverter converter = new Java2DFrameConverter();
            int frameCount = 0;
            
            // 进度显示
            JProgressBar progressBar = new JProgressBar(0, 100);
            JOptionPane.showMessageDialog(null, progressBar, "处理进度", 
                JOptionPane.INFORMATION_MESSAGE);
            
            // 逐帧处理
            while ((frame = grabber.grab()) != null) {
                if (frame.image != null) {
                    // 处理当前帧的水印
                    Frame processedFrame = processFrame(frame);
                    recorder.record(processedFrame);
                } else {
                    recorder.record(frame);
                }
                
                frameCount++;
                // 更新进度条
                int progress = (int) ((double) frameCount / grabber.getLengthInFrames() * 100);
                progressBar.setValue(progress);
            }
            
            return true;
            
        } catch (Exception e) {
            JOptionPane.showMessageDialog(null, "视频处理错误: " + e.getMessage());
            return false;
        } finally {
            try {
                if (recorder != null) {
                    recorder.close();
                }
                if (grabber != null) {
                    grabber.close();
                }
            } catch (Exception e) {
                System.err.println("资源释放错误: " + e.getMessage());
            }
        }
    }
    
    /**
     * 使用delogo滤镜处理帧[5](@ref)
     */
    private Frame processFrame(Frame frame) {
        // 这里可以使用JavaCV的滤镜功能
        // 由于JavaCV的滤镜API较复杂,我们可以调用FFmpeg命令行
        // 或者使用以下替代方法:对选定区域进行模糊处理
        
        // 替代方案:使用高斯模糊覆盖水印区域
        // 实际项目中可以考虑使用更复杂的算法
        
        return frame;
    }
    
    /**
     * 通过系统调用FFmpeg命令去除水印(更高效的方法)[5](@ref)
     */
    public boolean removeWatermarkWithFFmpegCommand(String inputPath, String outputPath) {
        try {
            // 构建FFmpeg命令
            String command = String.format(
                "ffmpeg -i \"%s\" -vf \"delogo=x=%d:y=%d:w=%d:h=%d:show=0\" -c:a copy \"%s\"",
                inputPath, x, y, width, height, outputPath);
            
            ProcessBuilder processBuilder = new ProcessBuilder();
            if (System.getProperty("os.name").toLowerCase().contains("win")) {
                processBuilder.command("cmd.exe", "/c", command);
            } else {
                processBuilder.command("bash", "-c", command);
            }
            
            Process process = processBuilder.start();
            int exitCode = process.waitFor();
            
            return exitCode == 0;
            
        } catch (Exception e) {
            JOptionPane.showMessageDialog(null, "FFmpeg处理错误: " + e.getMessage());
            return false;
        }
    }
}

4. 图形用户界面实现

主界面类

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;

public class WatermarkRemoverGUI extends JFrame {
    private ImageWatermarkRemover imageProcessor;
    private VideoWatermarkRemover videoProcessor;
    private JLabel imageLabel;
    private JButton selectImageBtn, selectVideoBtn, removeBtn, saveBtn;
    private JTextField xField, yField, widthField, heightField;
    private BufferedImage currentImage;
    private Rectangle selectedRegion;
    
    public WatermarkRemoverGUI() {
        initializeComponents();
        setupLayout();
        setupEventHandlers();
    }
    
    private void initializeComponents() {
        setTitle("Java去水印工具");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(800, 600);
        
        imageProcessor = new ImageWatermarkRemover();
        videoProcessor = new VideoWatermarkRemover();
        
        // 创建组件
        imageLabel = new JLabel("请选择图片或视频文件", SwingConstants.CENTER);
        imageLabel.setBorder(BorderFactory.createEtchedBorder());
        
        selectImageBtn = new JButton("选择图片");
        selectVideoBtn = new JButton("选择视频");
        removeBtn = new JButton("去除水印");
        saveBtn = new JButton("保存结果");
        
        xField = new JTextField("0", 5);
        yField = new JTextField("0", 5);
        widthField = new JTextField("100", 5);
        heightField = new JTextField("50", 5);
        
        // 初始化区域选择
        selectedRegion = new Rectangle(0, 0, 100, 50);
    }
    
    private void setupLayout() {
        setLayout(new BorderLayout());
        
        // 顶部控制面板
        JPanel controlPanel = new JPanel(new FlowLayout());
        controlPanel.add(new JLabel("X:"));
        controlPanel.add(xField);
        controlPanel.add(new JLabel("Y:"));
        controlPanel.add(yField);
        controlPanel.add(new JLabel("宽度:"));
        controlPanel.add(widthField);
        controlPanel.add(new JLabel("高度:"));
        controlPanel.add(heightField);
        
        controlPanel.add(selectImageBtn);
        controlPanel.add(selectVideoBtn);
        controlPanel.add(removeBtn);
        controlPanel.add(saveBtn);
        
        add(controlPanel, BorderLayout.NORTH);
        add(new JScrollPane(imageLabel), BorderLayout.CENTER);
        
        // 状态栏
        JLabel statusLabel = new JLabel("就绪");
        add(statusLabel, BorderLayout.SOUTH);
    }
    
    private void setupEventHandlers() {
        selectImageBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                selectImageFile();
            }
        });
        
        selectVideoBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                selectVideoFile();
            }
        });
        
        removeBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                removeWatermark();
            }
        });
        
        saveBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                saveResult();
            }
        });
        
        // 添加鼠标监听器用于选择区域
        imageLabel.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                startSelection(evt.getX(), evt.getY());
            }
            
            public void mouseReleased(java.awt.event.MouseEvent evt) {
                endSelection(evt.getX(), evt.getY());
            }
        });
        
        imageLabel.addMouseMotionListener(new java.awt.event.MouseMotionAdapter() {
            public void mouseDragged(java.awt.event.MouseEvent evt) {
                updateSelection(evt.getX(), evt.getY());
            }
        });
    }
    
    private void selectImageFile() {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter(
            "图片文件", "jpg", "jpeg", "png", "bmp"));
        
        if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
            File file = fileChooser.getSelectedFile();
            currentImage = imageProcessor.loadImage(file.getAbsolutePath());
            if (currentImage != null) {
                displayImage(currentImage);
            }
        }
    }
    
    private void selectVideoFile() {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter(
            "视频文件", "mp4", "avi", "mov", "wmv"));
        
        if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
            File file = fileChooser.getSelectedFile();
            JOptionPane.showMessageDialog(this, 
                "视频文件已选择: " + file.getName() + "\n请设置水印区域参数后点击去除水印");
        }
    }
    
    private void startSelection(int x, int y) {
        selectedRegion = new Rectangle(x, y, 0, 0);
        updateCoordinateFields();
    }
    
    private void updateSelection(int x, int y) {
        if (selectedRegion != null) {
            selectedRegion.width = x - selectedRegion.x;
            selectedRegion.height = y - selectedRegion.y;
            updateCoordinateFields();
            repaint(); // 重绘显示选择区域
        }
    }
    
    private void endSelection(int x, int y) {
        updateSelection(x, y);
    }
    
    private void updateCoordinateFields() {
        xField.setText(String.valueOf(selectedRegion.x));
        yField.setText(String.valueOf(selectedRegion.y));
        widthField.setText(String.valueOf(Math.abs(selectedRegion.width)));
        heightField.setText(String.valueOf(Math.abs(selectedRegion.height)));
    }
    
    private void removeWatermark() {
        try {
            int x = Integer.parseInt(xField.getText());
            int y = Integer.parseInt(yField.getText());
            int width = Integer.parseInt(widthField.getText());
            int height = Integer.parseInt(heightField.getText());
            
            imageProcessor.setWatermarkRegion(x, y, width, height);
            BufferedImage result = imageProcessor.removeWatermark();
            
            if (result != null) {
                displayImage(result);
                JOptionPane.showMessageDialog(this, "水印去除完成!");
            }
        } catch (NumberFormatException ex) {
            JOptionPane.showMessageDialog(this, "请输入有效的区域参数");
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(this, "处理错误: " + ex.getMessage());
        }
    }
    
    private void saveResult() {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setSelectedFile(new File("output.jpg"));
        
        if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
            File file = fileChooser.getSelectedFile();
            if (imageProcessor.saveImage(file.getAbsolutePath())) {
                JOptionPane.showMessageDialog(this, "保存成功!");
            }
        }
    }
    
    private void displayImage(BufferedImage image) {
        ImageIcon icon = new ImageIcon(image);
        imageLabel.setIcon(icon);
        imageLabel.setText("");
    }
    
    public static void main(String[] args) {
        // 设置系统样式
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeel());
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new WatermarkRemoverGUI().setVisible(true);
            }
        });
    }
}

5. 高级功能与优化

批量处理功能

/**
 * 批量处理多个文件
 */
public class BatchWatermarkRemover {
    private ImageWatermarkRemover processor;
    
    public BatchWatermarkRemover() {
        processor = new ImageWatermarkRemover();
    }
    
    /**
     * 批量处理指定文件夹中的图片
     */
    public void batchProcessImages(String inputDir, String outputDir, 
                                  int x, int y, int width, int height) {
        File dir = new File(inputDir);
        File[] imageFiles = dir.listFiles((d, name) -> 
            name.toLowerCase().matches(".*\\.(jpg|jpeg|png|bmp)"));
        
        if (imageFiles == null || imageFiles.length == 0) {
            JOptionPane.showMessageDialog(null, "未找到图片文件");
            return;
        }
        
        JProgressBar progressBar = new JProgressBar(0, imageFiles.length);
        JDialog progressDialog = createProgressDialog(progressBar);
        
        new Thread(() -> {
            for (int i = 0; i < imageFiles.length; i++) {
                try {
                    File inputFile = imageFiles[i];
                    File outputFile = new File(outputDir, "processed_" + inputFile.getName());
                    
                    // 处理单个文件
                    processor.loadImage(inputFile.getAbsolutePath());
                    processor.setWatermarkRegion(x, y, width, height);
                    processor.removeWatermark();
                    processor.saveImage(outputFile.getAbsolutePath());
                    
                    // 更新进度
                    final int progress = i + 1;
                    SwingUtilities.invokeLater(() -> 
                        progressBar.setValue(progress));
                    
                } catch (Exception e) {
                    System.err.println("处理失败: " + imageFiles[i].getName() + 
                                     " - " + e.getMessage());
                }
            }
            
            SwingUtilities.invokeLater(() -> {
                progressDialog.dispose();
                JOptionPane.showMessageDialog(null, 
                    "批量处理完成! 处理了 " + imageFiles.length + " 个文件");
            });
        }).start();
        
        progressDialog.setVisible(true);
    }
    
    private JDialog createProgressDialog(JProgressBar progressBar) {
        JDialog dialog = new JDialog();
        dialog.setTitle("批量处理进度");
        dialog.setLayout(new BorderLayout());
        dialog.add(progressBar, BorderLayout.CENTER);
        dialog.setSize(300, 100);
        dialog.setLocationRelativeTo(null);
        dialog.setModal(true);
        return dialog;
    }
}

6. 使用说明和注意事项

环境配置要求

  1. ​​Java环境​​: JDK 8或更高版本
  2. ​​OpenCV本地库​​: 需要配置相应的本地库文件
  3. ​​FFmpeg​​: 视频处理需要安装FFmpeg到系统PATH中

使用步骤

  1. 启动应用程序
  2. 选择要处理的图片或视频文件
  3. 在图像上拖动鼠标选择水印区域,或手动输入坐标参数
  4. 点击"去除水印"按钮执行处理
  5. 预览结果并保存处理后的文件
posted on 2025-11-29 20:55  程序员李铁牛  阅读(7)  评论(0)    收藏  举报