图片和视频去水印软件开发方案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. 使用说明和注意事项
环境配置要求
- Java环境: JDK 8或更高版本
- OpenCV本地库: 需要配置相应的本地库文件
- FFmpeg: 视频处理需要安装FFmpeg到系统PATH中
使用步骤
- 启动应用程序
- 选择要处理的图片或视频文件
- 在图像上拖动鼠标选择水印区域,或手动输入坐标参数
- 点击"去除水印"按钮执行处理
- 预览结果并保存处理后的文件
微信号:tieniu6636
浙公网安备 33010602011771号