如何摆脱谱图中的垂直条纹?

时间:2015-11-01 20:27:45

标签: java audio fft spectrogram

在我高中写作的论文范围内,我选择从头开始创建自己的音频文件到频谱图转换器,以便从这些频谱图中创建景观。

我已经实现了FFT的实现,并使用它来制作高度图,谱图。但是当频率变得密集时,我常常会以垂直条纹的形式出现奇怪的伪影,如下图所示。

Spectrogram of Mozart's Requiem, Lacrimosa

示例正好在开头,窗口长度为2048,并且是log ^ 2-scale。我正在使用的FFT是完美的,我已经将它与其他人进行了比较,并且它们产生了相同的结果。

这是将幅度转换为频率并将其存储在2D阵列中的函数:

private void transform(int from, int until) {
    double val, step;
    for(int i=from; i<until; i++) {
        for(int j=0; j<n; j++)
            chunk[j] = data[0][i*n+j+start];

        fft.realForward(chunk);

        for(int j=0; j<height; j++) {
            val = Math.sqrt(chunk[2*j]*chunk[2*j] + chunk[2*j+1]*chunk[2*j+1]);
            map[i][j] = val;
        }
    }
}

现在我的问题:这些垂直条纹来自哪里,我该如何摆脱它们?

我目前不使用窗函数,每个计算都相互串联,这意味着没有重叠。这是制作频谱图时最简单的方法。它可以帮助引入窗口函数或进行每个计算,而不管框架是否已经参与先前的计算,也就是说重叠框架窗口

另外,为了获得更好的结果,还有哪些方法可以改进我的基本方法?

这是整个班级。我从音频文件中提供数据和所有必要信息:

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;

import javax.imageio.ImageIO;
import javax.swing.*;

import org.jtransforms.fft.DoubleFFT_1D;

public class Heightmap extends JFrame implements WindowListener{
    public static final int LOG_SCALE = 0;
    public static final int LOG_SQUARE_SCALE = 1;
    public static final int SQUARE_SCALE = 2;
    public static final int LINEAR_SCALE = 3;

    private BufferedImage heightmap;
    private FileDialog chooser; 

    private JMenuBar menuBar;
    private JMenu fileMenu;
    private JMenuItem save, close;

    private DoubleFFT_1D fft;
    private int[][] data;
    private double[][] map;
    private double[] chunk;
    private int width, height, n, start, scale;
    private String name;

    private boolean inactive;

    public Heightmap(int[][] data, int resolution, int start,
            int width, int height, int scale, String name) {

        this.data = data;
        this.n = resolution;
        this.start = start;
        this.width = width;
        this.height = height;
        this.scale = scale;
        this.name = name;

        fft = new DoubleFFT_1D(n);
        map = new double[width][height];
        heightmap = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        chunk = new double[n];      

        System.out.println("Starting transformation...");

        long time;
        time = System.currentTimeMillis();
        transform();
        time = System.currentTimeMillis() - time;
        System.out.println("Time taken for calculation: "+time+" ms");

        time = System.currentTimeMillis();
        makeHeightmap();
        initComponents();
        time = System.currentTimeMillis() - time;
        System.out.println("Time taken for drawing heightmap: "+time+" ms");

    }

    private void initComponents() {
        this.setSize(width, height);
        this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        this.setResizable(false);
        this.setLocationRelativeTo(null);
        this.setTitle(name);

        createMenuBar();
        chooser = new FileDialog(this, "Choose a directory", FileDialog.SAVE);
        chooser.setDirectory("/Users/<user>/Desktop");

        this.addMouseListener(new HeightmapMouseListener());
        this.addKeyListener(new HeightmapKeyListener());
        this.addWindowListener(this);

        this.setVisible(true);

    }

    private void createMenuBar() {
        menuBar = new JMenuBar();
        fileMenu = new JMenu();

        fileMenu.setText("File");

        save = new JMenuItem("Save...", KeyEvent.VK_S);
        save.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.META_DOWN_MASK));
        save.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                chooser.setVisible(true);

                String fileName = chooser.getFile();
                String dir = chooser.getDirectory();
                chooser.setDirectory(dir);

                if(fileName != null) {
                    try {
                        File outputfile = new File(dir + fileName + ".png");
                        ImageIO.write(heightmap, "png", outputfile);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Saved "+fileName+".png to "+dir);
                }
            }
        });

        close = new JMenuItem("Close", KeyEvent.VK_C);
        close.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.META_DOWN_MASK));
        close.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                setVisible(false);
                dispose();
            }
        });

        fileMenu.add(save);
        fileMenu.addSeparator();
        fileMenu.add(close);

        menuBar.add(fileMenu);
        this.setJMenuBar(menuBar);
    }

    public void paint(Graphics g) {
        g.drawImage(heightmap, 0, 0, null);
    }

    private void transform() {
        transform(0, width);
    }
    private void transform(int from, int until) {
        double max = Double.MIN_VALUE;
        double min = Double.MAX_VALUE;
        double val, step;
        for(int i=from; i<until; i++) {
            for(int j=0; j<n; j++) {
                chunk[j] = data[0][i*n+j+start];
            }
            fft.realForward(chunk);

            for(int j=0; j<height; j++) {
                val = Math.sqrt(chunk[2*j]*chunk[2*j] + chunk[2*j+1]*chunk[2*j+1]);

                if(val > max)
                    max = val;
                if(val < min)
                    min = val;
                map[i][j] = val;
            }

            if(min != 0) {
                step = max/(max-min);
                for(int j=0; j<height; j++)
                    map[i][j] = (map[i][j]-min)*step;
            }
        }
    }

    /*
     * Paints heightmap into the BufferedImage
     */
    private void makeHeightmap() {
        double max = 0;
        switch(scale) {
        case LOG_SCALE: max = Math.log(findMax(map)+1); break;
        case LOG_SQUARE_SCALE: max = Math.pow(Math.log(findMax(map)+1), 2); break;
        case SQUARE_SCALE: max = Math.sqrt(findMax(map)); break;
        case LINEAR_SCALE: max = findMax(map); break;
        default: max = Math.pow(Math.log(findMax(map)+1), 2); break;
        }
        double stepsize = 255.0/max;
        int val, rgb;

        for(int x=0; x<width; x++)
            for(int y=0; y<height; y++) {
                switch(scale) {
                case LOG_SCALE: val = (int) (Math.log(map[x][y]+1)*stepsize); break;
                case LOG_SQUARE_SCALE: val = (int) (Math.log(map[x][y]+1)*stepsize); val *= val; break;
                case SQUARE_SCALE: val = (int) (Math.sqrt(map[x][y])*stepsize); break;
                case LINEAR_SCALE: val = (int) (map[x][y]*stepsize); break;
                default: val = (int) (Math.log(map[x][y]+1)*stepsize); val *= val; break;
                }
                rgb = 255<<24 | val<<16 | val<<8 | val;
                heightmap.setRGB(x, height-y-1, rgb);
            }

    }

    private double findMax(double[][] data) {
        double max = 0;
        for(double[] val1: data)
            for(double d: val1)
                if(d > max)
                    max = d;
        return max;
    }

    private class HeightmapKeyListener implements KeyListener {
        boolean busy = false;

        public void keyPressed(KeyEvent e) {

            if(e.getKeyCode() == KeyEvent.VK_RIGHT && !busy && start < data[0].length-width*n) {
                busy = true;

                for(int x=0; x<width-1; x++)
                    map[x] = map[x+1].clone();

                start += n;
                transform(width-1, width);              
                makeHeightmap();
                repaint();
                busy = false;
            }
            else if(e.getKeyCode() == KeyEvent.VK_LEFT && !busy && start > 0) {
                busy = true;

                for(int x=width-1; x>0; x--)
                    map[x] = map[x-1];

                start -= n;
                transform(0, 1);
                makeHeightmap();
                repaint();
                busy = false;
            }
        }

        public void keyReleased(KeyEvent e) {   }
        public void keyTyped(KeyEvent e) {  }
    }

    private class HeightmapMouseListener implements MouseListener {


        public void mouseClicked(MouseEvent e) {
            if(inactive) {
                inactive = false;
                return;
            }

            long time = System.currentTimeMillis();

            int posX = e.getX();
            int diff = posX - width/2;  //difference between old and new center in pixels
            int oldStart = start;

            start = start + diff*n;
            if(start < 0) start = 0;
            int maxFrame = data[0].length-width*n;
            if(start > maxFrame) start = maxFrame;
            if(start == oldStart) return;

            System.out.println("Changing center...");

            int absDiff = Math.abs(diff);
            if(start < oldStart) {  //shift the start backward, recalculate the start
                for(int x=width-1; x>=absDiff; x--)
                    map[x] = map[x-absDiff].clone();
                transform(0, absDiff);
            }
            else if(start > oldStart) { //shift the back forward, recalculate the back
                for(int x=0; x<width-absDiff; x++)
                    map[x] = map[x+absDiff].clone();
                transform(width-absDiff, width);
            }

            makeHeightmap();
            repaint();
            System.out.println("Time taken: "+(System.currentTimeMillis()-time)+" ms");

        }

        public void mousePressed(MouseEvent e) {    }
        public void mouseReleased(MouseEvent e) {   }
        public void mouseEntered(MouseEvent e) {    }
        public void mouseExited(MouseEvent e) { }
    }

    public void windowActivated(WindowEvent arg0) { }
    public void windowClosed(WindowEvent arg0) {    }
    public void windowClosing(WindowEvent arg0) {   }
    public void windowDeactivated(WindowEvent arg0) {
        inactive = true;
    }
    public void windowDeiconified(WindowEvent arg0) {   }
    public void windowIconified(WindowEvent arg0) { }
    public void windowOpened(WindowEvent arg0) {    }

}

编辑: 实现窗口函数极大地改善了结果。我真的不明白窗口函数会做什么,因此低估了它的影响。

然而,在这样做之后,我尝试映射频率为10kHz的余弦波(再次)产生了一些奇怪的伪像: enter image description here

这可能是什么原因?我通过剪切0到0以及255到255之间的所有内容来实现溢出保护,而不做任何改变。

1 个答案:

答案 0 :(得分:1)

这种类型的工件可能是由于在颜色映射函数之前或之中溢出或超出参数边界,或者可能是某些函数(log?)返回NaN值。您可以通过为超出范围或非法值添加一些断言来找到它。