【问题标题】:how to prevent or deal with byte overflow and byte underflow when manipulate individual pixel in Opencv 3.1 Java在 Opencv 3.1 Java 中操作单个像素时如何防止或处理字节溢出和字节下溢
【发布时间】:2017-04-28 07:42:36
【问题描述】:

我正在尝试使用 OpenCV 3.1 Java 操作单个图像像素。 OpenCV 将图像读取为字节数组。我想将彩色图像转换为灰度图像,而不用函数 Y = R*0.299 + G*0.587 + B*0.114 改变图像通道的总数,因此结果仍然是 RGB 图像(3 个颜色通道)。根据我的理解,因为 0.299 + 0.587 + 0.114 = 1 的和,虽然字节范围从 -128 到 127,但在转换为字节时应该没有下溢或上溢的问题。但是结果很奇怪,因为图像的一些白色区域变成了黑色,而一些黑色区域变成了白色。我假设发生了下溢或溢出。这是我的代码:

List<Mat> channels = new ArrayList<>();
Core.split(intImage, channels);
int totalInt = (int)(channels.get(0).total());
byte[] blue = new byte[totalInt];
byte[] green = new byte[totalInt];
byte[] red = new byte[totalInt];
channels.get(0).get(0, 0, blue);
channels.get(1).get(0, 0, green);
channels.get(2).get(0, 0, red);
for (int i = 0; i < totalInt; i++) {
    byte s = (byte)(blue[i]*0.114 + green[i]*0.587 + red[i]*0.299);
    blue[i] = red[i] = green[i] = s;
}
channels.get(0).put(0, 0, blue);
channels.get(1).put(0, 0, green);
channels.get(2).put(0, 0, red);
Mat gray = new Mat();
Core.merge(channels, gray);

我尝试将图像转换为CvType.CV_16S,它代表无符号短,到目前为止它没有问题。转换为 CV_8UC3 仍然是字节。我担心堆问题,因为当我尝试使用 CV_32S 的 int 时,一些大图像会发生堆错误。所以这是我的问题:

  • 如果可以,我该如何防止或处理这些上溢/下溢。我仍在考虑使用字节,因为它会减少堆/内存的使用。
  • 如果第一个是唯一的选项,我如何才能将 CV_16S 直接转换为 BufferedImage 而无需转换回原始的 Mat 类型,因为我正在使用 Swing 显示图像。

我找到了从Mat转换为BufferedImage的方法如下:

public BufferedImage toBufferedImage(Mat matrix) {
    int type = BufferedImage.TYPE_BYTE_GRAY;
    if (matrix.channels() > 1) {
        type = BufferedImage.TYPE_3BYTE_BGR;
    }
    BufferedImage image = new BufferedImage(matrix.cols(), 
            matrix.rows(), type);
    final byte[] targetPixels = 
            ((DataBufferByte)image.getRaster().getDataBuffer()).getData();
    matrix.get(0, 0, targetPixels);
    return image;
}

请解释一下,字节转换发生了什么。

【问题讨论】:

    标签: java image opencv


    【解决方案1】:

    中的计算

    blue[i]*0.114 + green[i]*0.587 + red[i]*0.299
    

    (您应该肯定将其更改为

    red[i]*0.299 + green[i]*0.587 + blue[i]*0.114
    

    保持 RGB 顺序不变!)

    与字节的值一起发生。正如您已经注意到的,这些值是。这会导致计算错误的结果。

    虽然对于(有符号的)byte,负值 -16 和正值 240 用相同的位模式表示,但它们仍然是不同的值,在计算过程中必须考虑到这一点。

    考虑以下示例:

    public class PixelBytes
    {
        public static void main(String[] args)
        {
            byte r = (byte)(100);
            byte g = (byte)(240);
            byte b = (byte)0;
            compute(r, g, b);
        }
    
        private static byte compute(byte r, byte g, byte b)
        {
            byte sr = (byte) (r * 0.299);
            byte sg = (byte) (g * 0.587);
            byte sb = (byte) (b * 0.114);
            byte s = (byte) (sr + sg + sb);
    
            byte tr = (byte) (toUnsignedInt(r) * 0.299);
            byte tg = (byte) (toUnsignedInt(g) * 0.587);
            byte tb = (byte) (toUnsignedInt(b) * 0.114);
            byte t = (byte) (tr + tg + tb);
    
            System.out.println("For " + r + " " + g + " " + b);
            System.out.println("add " + sr + " " + sg + " " + sb + " to get " + s);
            System.out.println("or  " + tr + " " + tg + " " + tb + " tp get " + t);
    
            return s;
        }
    
        // Same as Byte#toUnsignedInt in Java 8
        private static int toUnsignedInt(byte b)
        {
            return ((int) b) & 0xff;        
        }
    }
    

    输出将是

    For 100 -16 0
    add 29 -9 0 to get 20
    or  29 -116 0 tp get -87
    

    清楚地表明结果不同,这取决于是否使用负值,或者是否首先将值转换为它们的“真实”无符号值。

    所以灰度像素值的计算可以用这样的方法来完成:

    private static byte computeLuminance(byte r, byte g, byte b)
    {
        int ir = r & 0xFF;
        int ig = g & 0xFF;
        int ib = b & 0xFF;
        return (byte)(ir * 0.299 + ig * 0.587 + ib * 0.114);
    }
    

    【讨论】:

    • 非常感谢。有效。我注意到那些 & 运算符,但我不知道该怎么做。是的,你是对的。我应该保持RGB的顺序,这样就清楚了。哦,是的,Mat 按 BGR 顺序排列 RGB 是真的吗?无论如何,再次感谢您。
    • ...Mat 是否按 BGR 顺序存储 RGB? - 这是几种可能的图像格式之一,但通常您希望顺序是 RGB .
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-12
    • 1970-01-01
    • 2016-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多