【问题标题】:How to efficently Read the Size of an JPEG Image on Stream如何有效地读取流上 JPEG 图像的大小
【发布时间】:2020-09-17 07:59:41
【问题描述】:

我正在为我们的业务应用程序编写一个小插件,目标是使用条形码阅读器拍照。

一切正常,但问题是条形码阅读器以间隔发送图片,它们非常随机(取决于图像的大小和波特率)。如果不对我收到的字节进行全面分析,就无法判断图片是否已经加载。

目前,我的逻辑尝试通过分别搜索 FF D8FF D9 字节来查找 JPEG 的开始/结束。问题是字节FF D9 可能出现在图像内部。

我显然可以对字节进行一些具体的分析,但是由于 Barcodereader 不断发送数据,在接收字节时执行耗时的操作(调试、CPU、IO 等)最终会出现蓝屏.

我真正想要的是

  1. 读取显示图像大小的字节(我不能 甚至研究大小是否会占用页眉本身/页脚 考虑到自身...我必须为此计算吗? )。

  2. 检查我是否收到了所有个字节。

我会将接收和使用字节的代码(它在数据接收事件上,serialPortish)和正确的完整图像(以字节为单位)和损坏的图像放在一起,这可能会有所帮助。

数据接收事件

        private void ScannerPort_DataReceived(object sender, DataReceivedEventArgs e)
        {
            if (_WaitingForImage)
            {
                List<byte> imageBufferList = new List<byte>(e.RawBuffer);
                {

                    if (imageBufferList[0] == 0x24 && imageBufferList[1] == 0x69)
                    {
                        for (int i = 0; i < 17; i++)
                        {
                            imageBufferList.RemoveAt(0);
                        }
                    }

                    byte[] imageBuffer = imageBufferList.ToArray();
                    _ImageReceiving = false;
                    _WaitingForImage = false;
                    this.OnImageReceived(imageBuffer);
                }

                //imageBufferList.AddRange(e.RawBuffer);

            }

全字节数组

https://codepen.io/NicolaiWalsemann/pen/KKzxaXg

损坏的字节数组

https://codepen.io/NicolaiWalsemann/pen/YzqONxd

解决方案 1

我可以轻松地做一个 Timer,它在 DataReceived 事件第一次被调用后等待 500ms-2000ms。这将使确定我拥有一切,然后我可以尽可能多地解析它。但显然总是不得不无理等待并不是我想要的。

【问题讨论】:

  • 我可能会在里面解释一下 Loop,我们的条码扫描器会在制作图片后发送,“确认”现在将发送图片。它有 17 个字节长。那就是那里的删除循环。但这与图片本身无关......
  • 也许这里的答案会有所帮助:Getting Image size of JPEG from its binary
  • 例如,有些扫描仪供应商似乎有 API 和库,但您是否可能正在使用这样的供应商? Image capturing via SNAPI.dll API for Symbol barcode scanner DS4208
  • 为什么需要_WaitingForImage 守卫?这可能会忽略一些下一个事件处理程序调用,您将丢失下一个接收到的缓冲区。可能它可能包含一些有用的数据。查看 Producer-Consumer 编程模式实现,它可能有助于理解如何正确实现它。例如。使用接收缓冲区的BlockingCollection&lt;byte[]&gt;。条码扫描器是否保证整个图像将立即发送或可以分成多个部分?然后,您必须将这些部分合并到单个数组中。
  • @aepot 我已经减少了代码,只完成了重要的事情,通常那里有条码序列化,_WaitingForImage 关闭了女巫。因为我必须手动将扫描仪设置为图像模式,并且扫描仪没有读取其他内容。并且 WaitingForImage 只会在收到完整图像时设置为 False ,而不是其他方式。所以它实际上对这个问题没有任何作用。

标签: c# stream serial-port jpeg barcode-scanner


【解决方案1】:

我想有人已经回答了这个问题:Detect Eof for JPG images

我说得再好不过了。

由于您将收到大量数据,因此您需要随时进行解析。类似于以下内容。它未经测试,我可能会向后计算计数(大端与小端)。它还假设一个块可能跨越 2 个图像,或者这些块可能会拆分 FFxx 代码和计数。它也没有以任何方式进行优化,但对于小图像可能还可以。

    private List<byte> imageBuffer = new List<byte>();
    private int imageState = 0;
    private int skipBytes = 0;

    private void ScannerPort_DataReceived(object sender, DataReceivedEventArgs e)
    {
        List<byte> tempBuffer = new List<byte>(e.RawBuffer);

        foreach (byte b in tempBuffer)
        {
            _ImageReceiving = true;
            imageBuffer.Add(b);

            switch (imageState)
            {
                case 0: // Searching for FF
                    if(b == 0xFF)
                        imageState = 1;
                    break;
                case 1: // First byte after FF
                    if (b == 0 || b == 1 || (b <= 0xD8 && b >= 0xD1))
                    {
                        // Code is not followed by a count
                        imageState = 0;
                    }
                    else if (b == 0xD9)
                    {
                        // End of image
                        _ImageReceiving = false;
                        this.OnImageReceived(imageBuffer.ToArray());
                        imageBuffer = new List<byte>();
                        imageState = 0;
                    }
                    else
                    {
                        // Code is 
                        imageState = 2;
                    }

                    break;
                case 2: // First count byte,  big endian?
                    skipBytes = ((int) b) * 0x100;
                    imageState = 3;
                    break;
                case 3: // Second count byte
                    skipBytes += b;
                    imageState = 4;
                    break;
                case 4: // skip
                    skipBytes--;
                    if (skipBytes == 0) imageState = 0;
                    break;
            }
        }
    }

【讨论】:

  • 我已经仔细阅读了这篇文章,但问题只是开始了。如果我能够立即收到完整图像。并解析它。这里甚至没有问题。它是条码阅读器以随机间隔的随机间隔发送字节。并且除了字节 x = y 之外,进行任何“大”解析将花费太长时间来处理,并且扫描程序将在发送数据时遇到问题(我知道它的具体原因只是一个快速解释)我需要一种方法,在运行时没有任何循环都可以知道整个事情何时完成......
  • 我不确定如何处理长度未知的缓冲区而没有任何循环,因为 jpg 的结构和数据可能被分割成任意块的要求,甚至可能分割标记或计数. 我已经用一种可能的解决方案更新了答案,尽管未经优化和测试。
  • 1) 不要将字节数组添加到列表中。 2)不要一一计算您跳过的字节数。 3)将图像块组装成 MemoryStream 或其他东西。
  • 还是不好解决。 OP 显然在数据流中执行模式匹配,因此在出现错误的情况下,您应该在第一个提供给自动机的字节之后尝试另一个字节(您将队列从 autotamata 中的第一个字节开始保存到潜在匹配结束)。如果你不这样做,你将失去结果 - FF D8 FF D8
【解决方案2】:

这个我没测试过,不知道对不对。但这就是我解决问题的方式。与其他答案相比,我尝试处理一些注意事项;

  • 将字节数组保留为字节数组
  • 单个读取事件可能包含来自多个图像的部分块
  • MemoryStream 中组装整个图像
  • 使用块长度信息复制或跳过整个块

如果内存缓冲区超过最大大小,您可能还希望将状态设置回 0 并停止复制图像。或者如果在最后一个标题之后没有遇到新标题。

private MemoryStream image = new MemoryStream();
private int state = 0;
private bool copy = false;
private int blockLen = -1;

private void ImageReceived(Stream image) {
    // TODO use whole image buffer
}
private void Received(byte[] block)
{
    var i = 0;
    while (i < block.Length)
    {
        if (state == 4 && blockLen > 0)
        {
            var remaining = block.Length - i;
            if (remaining > blockLen)
                remaining = blockLen;
            if (copy)
                image.Write(block, i, remaining);
            i += remaining;
            blockLen -= remaining;
            if (blockLen <= 0)
                state = 0;
        }
        else
        {
            var b = block[i++];
            switch (state)
            {
                case 0:
                    if (b == 0xFF)
                        state = 1;
                    break;
                case 1:
                    if (b == 0xD8) { // SOI
                        copy = true;

                        image.Seek(0, SeekOrigin.Begin);
                        image.SetLength(0);
                        image.WriteByte((byte)0xFF); // the first byte that we already skipped
                    } else if (b == 0xD9) { // EOI
                        if (copy)
                        {
                            image.WriteByte(b);
                            image.Seek(0, SeekOrigin.Begin);

                            ImageReceived(image);
                        }
                        copy = false;
                        state = 0;
                    } else if (b == 0xFF) { // NOOP
                    } else if ((b & 0xF8) == 0xD0) { // RSTn
                        // You could verify that n cycles from 0-7
                        state = 0;
                    } else {
                        state = 2;
                    }
                    break;
                case 2:
                    blockLen = b << 8;
                    state = 3;
                    break;
                case 3:
                    // length includes the 2 length bytes, which we've just skipped
                    blockLen = (blockLen | b) -2;
                    state = 4;
                    break;
            }
            if (copy)
                image.WriteByte(b);
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-08
    • 2014-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-23
    • 1970-01-01
    • 2010-12-06
    相关资源
    最近更新 更多