Определение размера изображения JPEG (JFIF)

Мне нужно найти размер изображения JPEG (JFIF). Изображение не сохраняется как отдельный файл, поэтому я не могу использовать GetFileSizeни один другой API, такой как этот (изображение помещается в поток, и никакого другого заголовка нет, кроме обычных заголовков JPEG / JFIF) ).

Я провел небольшое исследование и выяснил, что изображения JPEG состоят из разных частей, каждая из которых начинается с маркера кадра ( 0xFF 0xXX) и размера этого кадра. Используя эту информацию, я смог разобрать много информации из файла.

Проблема в том, что я не могу найти размер сжатых данных, так как кажется, что для сжатых данных нет маркера кадра. Кроме того, кажется, что сжатые данные следуют за FFDAмаркером SOS ( ), а изображение заканчивается маркером Конец изображения (EOI) ( FFD9).

Для этого можно было бы искать маркер EOI от байта к байту, но я думаю, что сжатые данные могут содержать эту комбинацию байтов, верно?

Существует ли простой и правильный способ определения общего размера изображения? (Я бы предпочел некоторый код / ​​идею без какой-либо внешней библиотеки )

По сути, мне нужно расстояние (в байтах) между началом изображения (SOI- FFE0) и концом изображения (EOI- FFD9).

12.10.2009 21:35:41
Хм ... маркер SOS в файле JFIF? .. Я чувствую, что что-то упустил в спецификациях ...
jayarjo 3.10.2010 17:33:25
Оригинальный пост сказал "нет файла". Он говорит, что есть SOS и EOI. Каким-то образом у него есть встроенный поток JFIF без каких-либо внешних оболочек.
Jesse Chisholm 19.09.2014 18:11:58
4 ОТВЕТА
РЕШЕНИЕ

Сжатые данные не будут включать байты SOI или EOI, поэтому вы в безопасности. Но комментарий, данные приложения или другие заголовки могут. К счастью, вы можете идентифицировать и пропустить эти разделы по мере того, как дается длина.

Спецификация JPEG говорит вам, что вам нужно:
http://www.w3.org/Graphics/JPEG/itu-t81.pdf

Посмотрите на Таблицу B.1 на странице 32. Символы, которые имеют *, не имеют поля длины после него (RST, SOI, EOI, TEM). Другие делают.

Вам нужно будет пропустить различные поля, но это не так уж плохо.

Как пройти:

  1. Начните читать SOI ( FFD8). Это начало. Это должно быть первым делом в потоке.

    • Затем перейдите к файлу, найдите больше маркеров и пропустите заголовки:

    • Маркер SOI ( FFD8): поврежденное изображение. Вы должны были уже найти ВЗ!

    • ТЕМ ( FF01): автономный маркер, продолжайте.

    • RST ( FFD0через FFD7): автономный маркер, продолжайте. Можно проверить , что маркеры перезапуска подсчитывать от FFD0через FFD7и повторить, но это не является необходимым для измерения длины.

    • Маркер EOI ( FFD9): Вы сделали!

    • Любой маркер, который не является RST, SOI, EOI, TEM ( FF01через FFFEминус исключения, указанные выше): после маркера считайте следующие 2 байта, это 16-битная длина байта с прямым порядком байтов этого заголовка кадра (не включая 2 -байтовый маркер, но включая поле длины). Пропустите указанное количество (обычно длина минус 2, так как вы уже получили эти байты).

    • Если вы получили конец файла перед EOI, значит, у вас поврежденное изображение.

    • Как только вы получили EOI, вы прошли через JPEG и должны иметь длину. Вы можете начать снова, прочитав другой SOI, если вы ожидаете более одного JPEG в вашем потоке.

39
21.10.2009 17:47:20
Это мне очень помогло, но я нашел другую ссылку, в которой говорилось, что когда вы найдете маркер SOS, вам нужно просто начать читать данные в поисках маркера EOI, и это будет конец. gvsoft.homedns.org/exif/Exif-explanation.html Это соответствует тому, что я вижу, с изображением, которое я работаю в банкомате.
Tom Ritter 16.11.2009 01:36:24
Вы имеете в виду, что все эти маркеры присутствуют в JFIF? Я думал, что они являются частью спецификации EXIF, которая, в свою очередь, вообще несовместима с JFIF? .. Действительно ли я что-то упускаю?
jayarjo 3.10.2010 17:34:51
Чего здесь не хватает, так это того, что когда вы находите маркер SOS (начало сканирования), вы должны пропустить не только сам сегмент маркера, но также и тот энтропийно-кодированный сегмент, который следует сразу за ним. Маркеры не могут появляться внутри сегментов с энтропийным кодированием, поэтому просто продолжайте сканирование до тех пор, пока вы не прочитаете FF, после чего следует любой байт, не равный 0. (См. B.1.1.5 «Сегменты с энтропийным кодированием», примечание 2.)
devconsole 20.02.2012 16:28:20
Как раз то, что мне нужно. Спасибо.
vy32 7.06.2013 22:28:09
это правильно, обратитесь к этому для получения дополнительной информации: media.mit.edu/pia/Research/deepview/exif.html
Trong Vu 11.11.2016 08:50:03

Поскольку у вас нет размещенного языка, я не уверен, что это будет работать, но:

Можете ли вы Stream.Seek(0, StreamOffset.End);и затем занять позицию потока?

Пожалуйста, уточните, какую платформу вы используете.

Реальный факт в том, что если заголовок файла не указывает ожидаемый размер, вы должны искать (или читать) до конца изображения.

РЕДАКТИРОВАТЬ

Поскольку вы пытаетесь выполнять потоковую передачу нескольких файлов, вы можете использовать дружественный для потоковой передачи формат контейнера.

OGG должен хорошо подходить для этого.

JPEG на самом деле уже поддерживает потоковую передачу, но вы должны убедиться, что у каждого файла есть действительный терминатор, прежде чем отправлять его в поток, иначе вы рискуете разбить ваше приложение с неожиданным вводом.

2
12.10.2009 22:09:37
Я мог бы использовать C или Perl, любой из них подойдет. Однако обратите внимание, что я не могу использовать какую-либо форму GetFileSize / GetStreamSize, так как мой поток может содержать несколько изображений или любую другую информацию после изображения.
botismarius 12.10.2009 21:43:14
Проблема возникает, если вы передаете неполный файл JPEG. Вы никогда не увидите терминатора. Ожидается, что файлы JPEG будут единственными в потоке. Смотрите последнее предложение моего ответа.
John Gietzen 12.10.2009 21:48:14
Итак, в принципе, вы считаете, что вы не можете поместить два изображения в формате JPEG в один поток без добавления дополнительного заголовка? Другими словами, вы имеете в виду, что заголовки JPEG не могут определить, насколько велико изображение JPEG?
botismarius 12.10.2009 22:01:49
Да, довольно. Я не совсем уверен, что JFIF не содержит эту информацию, но если вы отправляете два файла за раз в потоке, вы обязательно должны сделать свое собственное кадрирование. формат Ogg Container может быть тем, что вы ищете.
John Gietzen 12.10.2009 22:08:27
Это не мой выбор, чтобы изменить формат потока. Так оно и есть, и я должен обработать его как можно лучше :)
botismarius 13.10.2009 17:14:18

В python вы можете просто прочитать весь файл в строковый объект и найти первое вхождение FF E0 и последнее вхождение FF D9. Предположительно, это начало и конец, который вы ищете?

f = open("filename.jpg", "r")
s = f.read()
start = s.find("\xff\xe0")
end = s.rfind("\xff\xd9")
imagesize = end - start
0
12.10.2009 21:58:25
\ Xff \ xd9 вполне возможно иметь место в середине изображения в формате JPEG. Шансы любых двух байтов, соответствующих этому шаблону, равны 1/65536.
Mark Ransom 12.10.2009 22:25:17
Да это правда. Однако, если у вас есть действительный файл JPEG, find и rfind вернут первое и последнее вхождения строки соответственно. Я думаю, что вполне безопасно предположить, что первое вхождение - это начало, а последнее вхождение - это конец данных изображения?
Chinmay Kanchi 12.10.2009 23:11:29
@cgkanchi: проблема в том, чтобы найти последний , когда вы не знаете, где находится конец изображения. Похоже, OP предназначен для потоковой передачи нескольких файлов JPEG через один и тот же поток.
Greg Hewgill 12.10.2009 23:33:32
Также проблема в том, что у OP нет «файла», а есть поток, в котором есть содержимое после изображения JFIF / JPEG.
Jesse Chisholm 19.09.2014 18:17:52

Может как то так

int GetJpgSize(unsigned char *pData, DWORD FileSizeLow, unsigned short *pWidth, unsigned short *pHeight)
{
  unsigned int i = 0;


  if ((pData[i] == 0xFF) && (pData[i + 1] == 0xD8) && (pData[i + 2] == 0xFF) && (pData[i + 3] == 0xE0)) {
    i += 4;

    // Check for valid JPEG header (null terminated JFIF)
    if ((pData[i + 2] == 'J') && (pData[i + 3] == 'F') && (pData[i + 4] == 'I') && (pData[i + 5] == 'F')
        && (pData[i + 6] == 0x00)) {

      //Retrieve the block length of the first block since the first block will not contain the size of file
      unsigned short block_length = pData[i] * 256 + pData[i + 1];

      while (i < FileSizeLow) {
        //Increase the file index to get to the next block
        i += block_length; 

        if (i >= FileSizeLow) {
          //Check to protect against segmentation faults
          return -1;
        }

        if (pData[i] != 0xFF) {
          return -2;
        } 

        if (pData[i + 1] == 0xC0) {
          //0xFFC0 is the "Start of frame" marker which contains the file size
          //The structure of the 0xFFC0 block is quite simple [0xFFC0][ushort length][uchar precision][ushort x][ushort y]
          *pHeight = pData[i + 5] * 256 + pData[i + 6];
          *pWidth = pData[i + 7] * 256 + pData[i + 8];

          return 0;
        }
        else {
          i += 2; //Skip the block marker

          //Go to the next block
          block_length = pData[i] * 256 + pData[i + 1];
        }
      }

      //If this point is reached then no size was found
      return -3;
    }
    else {
      return -4;
    } //Not a valid JFIF string
  }
  else {
    return -5;
  } //Not a valid SOI header

  return -6;
}  // GetJpgSize
3
26.08.2013 12:18:27