8

Is there a way to encode JPEG at a specific bitrate?

Presently, I'm using imagemagick's convert:

convert Lenna-gray-100.jpeg -quality 1.1111 test.jpeg

Bitrate increases with quality, but it's non-linear. I want to control the bitrate explicitly. It doesn't have to be exact, but I want it reasonably close (within, say 0.1 bpp of the specified setting).

Is there any encoder there that allows images to be encoded at a particular bit-rate? It doesn't have to be imagemagick, I'll take whatever works (preferably on Linux).

A dumb way to do this would be to play around with fractional values to the -quality parameter until something close to the target bitrate comes out, but I'm hoping for a more elegant solution.

EDIT:

So I got bored and decided to do things the quick (but stupid) way.

First, here's a graph of imagemagick's -quality vs bitrate:

alt text

BTW, here's the image I used:

alt text

So the change in bitrate is quite fine for lower quality values, but becomes coarse after about 80.

Here's some sample code to encode an image at some target bitrate. I used OpenCV cause it allows for in-memory JPEG encoding (no I/O necessary). While I originally was going to mock this up with Python, unfortunately the Python OpenCV wrappers don't expose the in-memory encoding functionality. So I wrote it in C++.

Lastly, I was thinking of using linear interpolation on the quality to get closer to the target bitrate, but since cv::imencode only accepts integer parameters, it's not possible to set a non-integer JPEG quality. The quality scale between OpenCV and imagemagick seems to differ somewhat as well, so taking the interpolated quality parameter from OpenCV and using in imagemagick's convert didn't work well.

This means that the output bitrate isn't equal to the target bitrate, especially at higher bitrates ( > 1). But it's close.

Can anyone suggest something better?

Code:

#include <stdio.h>
#include <cv.h>
#include <highgui.h>
#include <assert.h>
#include <vector>

using cv::Mat;
using std::vector;

#define IMENCODE_FMT   ".jpeg"
#define QUALITY_UBOUND 101
#define BITS_PER_BYTE  8

int
main(int argc, char **argv)
{
    if (argc != 4)
    {
        fprintf(stderr, "usage: %s in.png out.jpeg bpp\n", argv[0]);
        return 1;
    }

    char *fname_in = argv[1];
    char *fname_out = argv[2];
    float target;
    sscanf(argv[3], "%f", &target);

    Mat orig = cv::imread(fname_in);
    int pixels = orig.size().width * orig.size().height * orig.channels();

    vector<unsigned char> buf;
    vector<int> params = vector<int>(2);
    params[0] = CV_IMWRITE_JPEG_QUALITY;
    int q;
    double bpp = 0.0;

    for (q = 1; q < QUALITY_UBOUND; ++q)
    {
        params[1] = q;
        cv::imencode(IMENCODE_FMT, orig, buf, params);
        bpp = (double)buf.size() * BITS_PER_BYTE / pixels;
        if (bpp > target)
            break;
    }

    cv::imwrite(fname_out, orig, params);
    printf("wrote %s at %d%% quality, %.2fbpp\n", fname_out, q, bpp);

    return 0;
}

Compile and run using:

g++ -c -Wall -ggdb -I../c -I../blur `pkg-config --cflags opencv` -Wno-write-strings jpeg-bitrate.cpp -o jpeg-bitrate.o
g++ -I../c `pkg-config --cflags opencv` `pkg-config --libs opencv` -lboost_filesystem jpeg-bitrate.o -o jpeg-bitrate.out
rm jpeg-bitrate.o
misha@misha-desktop:~/co/cpp$ ./jpeg-bitrate.out Lenna-gray.png test.jpeg 0.53
wrote test.jpeg at 88% quality, 0.55bpp
mpenkov
  • 21,621
  • 10
  • 84
  • 126
  • 1
    Suggestion: Remove the for loop, replace with search. I can't believe that anyone would want a JPEG encoded with quality factors in the ranges of 1->~30 or ~99->100. You could also create your graph for a variety of different image types and work out a better initial start point for the search. This is all very naive because you don't even consider quality (e.g. PSNR); choosing a different quantisation table may get you the bitrate you want but a much higher quality. – koan Jan 14 '11 at 10:11
  • Thanks for the suggestion. Searching would improve the efficiency of the code, but at the moment it's not really a concern cause it's fast enough as it is. Your're right about quality -- most normal people don't need JPEGs of that low a quality cause they look like rubbish. However, I myself *am* interested in such images, as I'm studying image degradations. The point about quantization tables is interesting -- I think I will look into it. It'll probably require moving away from OpenCV and using something like ijg, cause OpenCV doesn't seem to expose the quantization tables. – mpenkov Jan 14 '11 at 13:17

2 Answers2

4

I know a lot of work exist on controlling the output bitrate of a JPEG encoder (e.g. 1st paper ; 2nd paper), and that such controls exist in JPEG2000. Unfortunately, I'm not sure any kind of bitrate control is standardized for JPEG, or implemented in common libraries. You may have to code your own method, using some kind of binary search for instance...

But again, I may be mistaken - and if so, I'd love to hear about such a library.

Just out of curiosity, what language are you using ?

fulguroblastor
  • 606
  • 5
  • 11
  • Thanks for the answer and the links. My uni doesn't subscribe to SpringerLink, unfortunately, but I had a read of the second paper. To answer your question: for serious image work I use C/C++, but if I end up coding my own method for this, I'll probably just use Python/Bash 'cause I'm lazy. – mpenkov Jan 13 '11 at 11:36
  • And that would be way simpler too. Anyway - let us know if you stumble upon a good solution for this particular problem :) – fulguroblastor Jan 13 '11 at 13:13
  • I updated the question with the sample code. I wouldn't call it a *good* solution but it's the only one I have at the moment. – mpenkov Jan 14 '11 at 05:30
  • +1. I was thinking of posting the same answer, but I wasn't sure myself. It makes sense because the JPEG bitstream is not an *embedded* bitstream like EZW, SPIHT, or JPEG2000. In other words, you cannot simply truncate a JPEG bitstream to receive a lower-quality version of the image, but with the other aforementioned codecs, you (sort of) can. – Steve Tjoa Jan 14 '11 at 15:01
2

The bitrate-quality ratio in JPG is quite dependant on content. If you want to encode at a specific bitrate, I suggest you do it two passes: 1. Encode at a fixed quality factor (closer to your target bitrate is better, could be based on your graph) 2. Based on its size, reencode the original at a greater or lower quality. Again this can be based on your graph or something similar.

You could also repeat the last step indefinitely to get the EXACT bitrate you need.

I'd test this with various extreme cases, like a very noisy/busy image, a black rectangle, or a smooth gradient.

zulu lala
  • 21
  • 1