将JPEG转换成GIF太耗时。

3
我正在尝试从视频相机获取的JPEG中制作GIF动画。但这个过程非常漫长。我使用了两个不同的库。第一个是使用本地C++代码编写的,第二个是Java的一个

我尽可能地压缩帧,但即使如此,仍无法减少生成时间。

本地库处理5秒视频(80帧)需要80-100秒,而Java的库只需要40-60秒(我不知道为什么Java会快两倍,但日志显示了这个结果)。

我根据这个稍微修改了一下C ++算法,因为我遇到了同样的问题(尝试用改变一小段代码和整个learn()功能的两个版本)。

在这里您可以看到一些日志:

这是原生实现中的最后三帧:

D/TimeUtils: Adding frame executed in 949ms
D/TimeUtils: Adding frame executed in 976ms
D/TimeUtils: Adding frame executed in 1028ms
D/TimeUtils: Creating gif with native library executed in 82553ms

这是Java版本中的最后三帧:

D/TimeUtils: Adding frame executed in 541ms
D/TimeUtils: Adding frame executed in 513ms
D/TimeUtils: Adding frame executed in 521ms
D/TimeUtils: Creating gif with nbadal's library executed in 44811ms

也许还有其他有用的日志:

D/CameraActivity: Duration of the captured video is 5000ms
V/CameraActivity: Dimensions are 288w x 288h
D/CameraActivity: Final bitmaps count: 80

TimeUtils.java 包含静态方法,用于检查方法执行时间。

NativeGifConverter.java(仅包含转换功能):

@Override public void createGifFile(String path, List<String> bitmapPaths) {

    Bitmap bitmap = BitmapUtils.retrieve(bitmapPaths.get(0));

    if (init(path, bitmap.getWidth(), bitmap.getHeight(), mNumColors, mQuality, mFrameDelay) != 0) {
      Timber.e("Gifflen init failed");
      return;
    }

    bitmap.recycle();

    for (String bitmapPath : bitmapPaths) {

      bitmap = howLong("Retrieving bitmap", () -> BitmapUtils.retrieve(bitmapPath));

      final int width = bitmap.getWidth();
      final int height = bitmap.getHeight();
      final int[] pixels = new int[width * height];
      final Bitmap finalBitmap = bitmap; // for counting time
      howLongVoid("Retrieving pixels", () -> finalBitmap.getPixels(pixels, 0, width, 0, 0, width, height));
      howLongVoid("Adding frame", () -> addFrame(pixels));

      bitmap.recycle();
    }
    bitmap = null;
    close();
  }

NbadalGifConverter.java(仅转换函数):

  @Override public void createGifFile(String path, List<String> bitmapsNames) {

    final ByteArrayOutputStream bos = new ByteArrayOutputStream();

    final AnimatedGifEncoder encoder = new AnimatedGifEncoder();
    encoder.setDelay(mDelay);
    encoder.setQuality(mQuality);
    encoder.start(bos);

    for (String bitmapName : bitmapsNames) {
      final Bitmap bitmap = howLong("Retrieving bitmap", () -> BitmapUtils.retrieve(bitmapName));
      howLongVoid("Adding frame", () -> encoder.addFrame(bitmap));
    }

    encoder.finish();
    FileUtils.store(bos.toByteArray(), path.substring(0, path.lastIndexOf('.')) + ".gif");
  }

我很乐意展示与此相关的其他代码。非常感谢任何帮助。

[更新]

检索位图的日志:

D/TimeUtils: Retrieving bitmap executed in 3ms
D/TimeUtils: Retrieving bitmap executed in 3ms
D/TimeUtils: Retrieving bitmap executed in 4ms

抱歉,我不是JAVA编码人员,正如您在我的答案中看到的那样,我是C++导向的。无论如何:您要转换的图像有多大?最长的操作不是LZW压缩本身,而是从真彩JPG到GIF中高达256种颜色的颜色量化(或者使用一些高级功能更多)。为了加快速度,您可以尝试以下方法:1.使用某些快速方法将JPG转换为256色图像(例如BMP),然后将其用作GIF编码输入,这可能会显着加快速度。如果您有使用全局GIF调色板的选项,可以尝试创建一些覆盖所需基本颜色的调色板... - Spektre
编码在手机上,所以它可以是真实的。我会使用多线程,但我对gif格式不太了解,我认为我必须按顺序添加帧。 - Anton Shkurenko
是的...有两种方法:1.每个CPU编码到自己的文件中,最后将它们合并。2.每个CPU编码其帧并等待存储到单个文件的命令...第二种方法稍慢,但不需要在最后将N个文件合并为一个...(我使用选项#2)这只有在您拥有多个CPU /核心而不仅仅是1个时才相关。 - Spektre
2
通过帮助他人...保留知识在当今非常重要。 - Spektre
@Spektre 哎呀,终于发了答案,谢谢你! - Anton Shkurenko
显示剩余7条评论
1个回答

4
首先,我要感谢@Spektre的答案:Effective gif/image color quantization? 我的同事和我将其从C++翻译成了Java。它在4倍的时间内展现了良好的结果。我将尝试进一步改进它,但这已经比我之前使用的AnimatedGifEncoder.java好多了。
以下是代码:
public static final int MAX_COLOR_COUNT = 65536;

/**
 * @param pixels rgb 888
 * @param palette int[256]
 * @return indices of colors in palette
 */
private int[][][] createPalette(int[] pixels, int[] palette) {

  final int[] histogram = new int[MAX_COLOR_COUNT]; // pixel count histogram
  final int[] indices = new int[MAX_COLOR_COUNT]; // here index is color value

  for (int i = 0; i < MAX_COLOR_COUNT; i++) {
    indices[i] = i;    
  }

  // creating histogram
  for (int color : pixels) {
    //                   0001 1111             0111 1110 0000         1111 1000 0000 0000
    color = ((color >> 3) & 0x1F) | ((color >> 5) & 0x7E0) | ((color >> 8) & 0xF800);
    if (histogram[color] < Integer.MAX_VALUE) { // picture must be really big
      histogram[color]++;
    }
  }

  // removing zeros
  int j = 0;
  for (int i = 0; i < MAX_COLOR_COUNT; i++) {
    histogram[j] = histogram[i];
    indices[j] = indices[i];
    if (histogram[j] != 0) {
      j++;
    }
  }
  final int histograms = j;

  // bubble sort
  for (int i = 1; i != 0; ) {
    i = 0;
    for (int x = 0, y = 1; y < histograms; x++, y++) {
      if (histogram[x] < histogram[y]) {
        i = histogram[x];
        histogram[x] = histogram[y];
        histogram[y] = i;
        i = indices[x];
        indices[x] = indices[y];
        indices[y] = i;
        i = 1;
      }
    }
  }

  final int[][][] colorMap = new int[32][64][32];

  int colorTableIndex = 0, x = 0;
  for (; x < histograms; x++) { // main colors
    final int color = indices[x];
    // 1f (16) = 0001 1111 (2)
    // 3f (16) = 0011 1111 (2)
    // (1111 1)(111 111)(1 1111)
    final int b = color & 0x1f;
    final int g = (color >> 5) & 0x3f;
    final int r = (color >> 11) & 0x1f;

    // skip if similar color already in palette[]
    int a = 0, i = 0;
    for (; i < colorTableIndex; i++) {
      final byte tempB = (byte) ((palette[i] >> 3) & 0x1f);
      final byte tempG = (byte) ((palette[i] >> 10) & 0x3f);
      final byte tempR = (byte) ((palette[i] >> 19) & 0x1f);

      // if difference between two colors is pretty small
      // taxicab distance
      int difference = tempB - b;
      if (difference < 0) {
        difference = -difference;
      }
      a = difference;
      difference = tempG - g;
      if (difference < 0) {
        difference = -difference;
      }
      a += difference;
      difference = tempR - r;
      if (difference < 0) {
        difference = -difference;
      }
      a += difference;
      if (a <= 2) { // smaller than 16/8
        a = 1;
        break;
      }
      a = 0;
    }

    if (a != 0) {
      colorMap[r][g][b] = i; // map to existing color
    } else {
      colorMap[r][g][b] = colorTableIndex; // map to new index

      // 1111 1000 1111 1100 1111 1000
      palette[colorTableIndex] = b << 3 | (g << 10) | (r << 19); // fill this index with new color
      colorTableIndex++;
      if (colorTableIndex >= 256/*palette.length*/) {
        x++;
        break;
      }
    }
  }   // colorTableIndex = new color table size

  for (; x < histograms; x++) { // minor colors

    final int color = indices[x];

    final int b = color & 0x1f;
    final int g = (color >> 5) & 0x3f;
    final int r = (color >> 11) & 0x1f;

    // find closest color
    int minDistance = -1;
    int colorIndex = 0;
    for (int a, i = 0; i < colorTableIndex; i++) {
      final byte tempB = (byte) ((palette[i] >> 3) & 0x1f);
      final byte tempG = (byte) ((palette[i] >> 10) & 0x3f);
      final byte tempR = (byte) ((palette[i] >> 19) & 0x1f);

      int difference = tempB - b;
      if (difference < 0) {
        difference = -difference;
      }
      a = difference;
      difference = tempG - g;
      if (difference < 0) {
        difference = -difference;
      }
      a += difference;
      difference = tempR - r;
      if (difference < 0) {
        difference = -difference;
      }
      a += difference;
      if ((minDistance < 0) || (minDistance > a)) {
        minDistance = a;
        colorIndex = i;
      }
    }
    colorMap[r][g][b] = colorIndex;
  }

  return colorMap;
}

private byte[] map(int[] pixels, int[][][] colorMap) {
  final int pixelsLength = pixels.length;

  final byte[] mapped = new byte[pixelsLength];
  for (int i = 0; i < pixelsLength; i++) {
    final int color =
        ((pixels[i] >> 3) & 0x1F) | ((pixels[i] >> 5) & 0x7E0) | ((pixels[i] >> 8) & 0xF800);

    final int b = color & 0x1f;
    final int g = (color >> 5) & 0x3f;
    final int r = (color >> 11) & 0x1f;

    mapped[i] = (byte) colorMap[r][g][b];
  }
  return mapped;
}

嗨Anton,我也有同样的疑问:) 你是如何将代码放置在AnimatedGifEncoder中的? - user2756345
3
这是我的代码完整来源:https://gist.github.com/tonyshkurenko/80dfafe9604c12661f57,只需从Hugo的@DebugLog中删除一些日志,将Timber中的一些日志删除,删除我的TimeUtils,并且它不包含AnimatedGifEncoder具有的某些属性,但如果您比较源代码,这些属性很容易添加 :) - Anton Shkurenko
嗨,@AntonShkurenko,我不知道该如何感谢你,同事!我在StackOverflow上点赞了你的大部分答案!谢谢! - Zikkoua
@Zikkoua 哈哈,谢谢 :) 但是StackOverflow有防御算法,它几乎把所有的都拿走了 :) 很高兴,我能帮到你! - Anton Shkurenko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接