1

I have a data that is (2448*2048) 5Mpixel image data, but the picturebox only has (816*683) about 500,000 pixels, so I lowered the pixels and I only need a black and white image, so I used the G value to create the image, but The image I output is shown in the following figure. Which part of my mistake?

 public int[,] lowered(int[,] greenar)
    {
        int[,] Sy = new int[816, 683];
        int x = 0;
        int y = 0;
        for (int i = 1; i < 2448; i += 3)
        {
            for (int j = 1; j < 2048; j += 3)
            {
                Sy[x, y] = greenar[i, j];
                y++;
            }
            y = 0;
            x++;
        }
        return Sy;
    }


static Bitmap Create(int[,] R, int[,] G, int[,] B)
    {
        int iWidth = G.GetLength(1);
        int iHeight = G.GetLength(0);
        Bitmap Result = new Bitmap(iWidth, iHeight,
        System.Drawing.Imaging.PixelFormat.Format24bppRgb);
        Rectangle rect = new Rectangle(0, 0, iWidth, iHeight);
        System.Drawing.Imaging.BitmapData bmpData = Result.LockBits(rect,
        System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
        IntPtr iPtr = bmpData.Scan0;
        int iStride = bmpData.Stride;
        int iBytes = iWidth * iHeight * 3;
        byte[] PixelValues = new byte[iBytes];
        int iPoint = 0;
        for (int i = 0; i < iHeight; i++)
        {
            for (int j = 0; j < iWidth; j++)
            {
                int iG = G[i, j];
                int iB = G[i, j];
                int iR = G[i, j];
                PixelValues[iPoint] = Convert.ToByte(iB);
                PixelValues[iPoint + 1] = Convert.ToByte(iG);
                PixelValues[iPoint + 2] = Convert.ToByte(iR);
                iPoint += 3;
            }
        }
        System.Runtime.InteropServices.Marshal.Copy(PixelValues, 0, iPtr, iBytes);
        Result.UnlockBits(bmpData);
        return Result;

    }

https://upload.cc/i1/2018/04/26/WHOXTJ.png

Nyerguds
  • 5,360
  • 1
  • 31
  • 63
余昀哲
  • 11
  • 1
  • 1
    Scanlines are aligned at the `DWORD` boundary. That is why [you have `Stride`](https://stackoverflow.com/a/2186121/11683) which you are fetching and then not using. – GSerg Apr 26 '18 at 08:10
  • Which programming language is this? – Nyerguds Apr 26 '18 at 13:50
  • programming language is C# – 余昀哲 Apr 26 '18 at 14:18
  • Unless you are sure that the picture is _already_ grayscale, only taking the green channel is _not the same_ as creating a grayscale version of the image... – Nyerguds Apr 28 '18 at 12:20
  • I found one important error: your function to lower the data _switches x and y_. In fact, your code confuses them on multiple occasions. To be 100% clear: the first index in your 2-dimensional array, **is it height or width?** – Nyerguds Apr 28 '18 at 12:31
  • Did you check out my answer? If it helped you, please mark it as solution. – Nyerguds May 07 '18 at 16:53

2 Answers2

0

You don't need to downsample your image, you can do it in this way. Set picturebox property BackgroundImageLayout as either zoom or stretch and assign it as:

picturebox.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
picturebox.BackgroundImage = bitmap;

System.Windows.Forms.ImageLayout.Zoom will automatically adjust your bitmap to the size of picturebox.

user8190410
  • 1,264
  • 2
  • 8
  • 15
  • you don't need to call lowered() function. Assign the output of Create() function to the BackgroundImage property of picturebox – user8190410 Apr 26 '18 at 15:22
  • I know i try it, but the image output is the same – 余昀哲 Apr 26 '18 at 15:50
  • check convert image to grayscale here https://github.com/techmn/ImageProcessing/blob/master/BasicImageProcessing/BasicImageProcessing/ImageOperation.cs – user8190410 Apr 26 '18 at 15:55
0

You seem to be constantly mixing up your x and y offsets, which can easily be avoided simply by actually calling your loop variables x and y whenever you loop through image data. Also, image data is generally saved line by line, so your outer loop should be the Y loop going over the height, and the inner loop should process the X coordinates on one line, and should thus loop over the width.

Also, I'm not sure where your original data comes from, but in most of the cases I've seen where the image data is in multidimensional arrays like this, the Y is actually the first index in the array. Your actual image building function also assumes this, since it uses G.GetLength(0) to get the height of the image. But your channel resize function doesn't; it makes a multidimensional array as new int[816, 683], which would be a 683*816 image, not 816*683 as you said. So that certainly seems wrong.

Since you confirmed it to be [x,y], I adapted this solution to use it like that.

That aside, you hardcoded a lot of values in your functions, which is very bad practice. If you know you will reduce the image to 1/3rd by taking only one in three pixels, just give that 3 as parameter.

The reduction code:

public static Int32[,] ResizeChannel(Int32[,] origChannel, Int32 lossfactor)
{
    Int32 newWidth = origChannel.GetLength(0) / lossfactor;
    Int32 newHeight = origChannel.GetLength(1) / lossfactor;
    // to avoid rounding errors
    Int32 origHeight = newHeight * lossfactor;
    Int32 origWidth = newWidth *lossfactor;
    Int32[,] newChannel = new Int32[newWidth, newHeight];
    Int32 newX = 0;
    Int32 newY = 0;
    for (Int32 y = 1; y < origHeight; y += lossfactor)
    {
        newX = 0;
        for (Int32 x = 1; x < origWidth; x += lossfactor)
        {
            newChannel[newX, newY] = origChannel[x, y];
            newX++;
        }
        newY++;
    }
    return newChannel;
}

The actual build code, as was remarked by GSerg in the comments, is wrong because you don't take the stride into account. The stride is the actual byte length of each line of pixels, and this is not just width * BytesPerPixel, since it gets rounded up to the next multiple of 4 bytes.

So you need to initialize your array as height * stride, not as height * width * 3, and you need to skip your write offset to the next multiple of the stride whenever you go to a lower Y line, rather than assuming it will just get there automatically because your X processing adds 3 for each pixel. Because it will not get there automatically, unless, by pure coincidence, your image width happens to be a multiple of 4 pixels.

Also, if you only use one channel for this, there is no reason to give it all three channels. Just give a single one.

public static Bitmap CreateGreyImage(Int32[,] greyChannel)
{
    Int32 width = greyChannel.GetLength(0);
    Int32 height = greyChannel.GetLength(1);
    Bitmap result = new Bitmap(width, height, PixelFormat.Format24bppRgb);
    Rectangle rect = new Rectangle(0, 0, width, height);
    BitmapData bmpData = result.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
    Int32 stride = bmpData.Stride;
    // stride is the actual line width in bytes.
    Int32 bytes = stride * height;
    Byte[] pixelValues = new Byte[bytes];
    Int32 offset = 0;
    for (Int32 y = 0; y < height; y++)
    {
        Int32 workOffset = offset;
        for (Int32 x = 0; x < width; x++)
        {
            pixelValues[workOffset + 0] = (Byte)greyChannel[x, y];
            pixelValues[workOffset + 1] = (Byte)greyChannel[x, y];
            pixelValues[workOffset + 2] = (Byte)greyChannel[x, y];
            workOffset += 3;
        }
        // Add stride to get the start offset of the next line
        offset += stride;
    }
    Marshal.Copy(pixelValues, 0, bmpData.Scan0, bytes);
    result.UnlockBits(bmpData);
    return result;
}

Now, this works as expected if your R, G and B channels are indeed identical, But if they are not, you have to realize there is a difference between reducing the image to grayscale and just building a grey image from the green channel. On a colour image, you will get totally different results if you take the blue or red channel instead.

This was the code I executed for this:

Int32[,] greyar = ResizeChannel(greenar, 3);
Bitmap newbm = CreateGreyImage(greyar);
Nyerguds
  • 5,360
  • 1
  • 31
  • 63
  • I tried the above method and changed the greenar image data [2448,2048] to [2048,2448] but the output image is still wrong https://imgur.com/a/2rSGm9y – 余昀哲 May 09 '18 at 09:34
  • I can't help you if you can't at least confirm whether the input array is [Y,X] or [X,Y]. All of this is written in the assumption it is [Y,X]; in fact your original code `Bitmap Create()` code assumes this too. I tested this with output extracted from an image, and it worked. – Nyerguds May 09 '18 at 09:36
  • The image data is captured by a camera, and the camera provides a method for capturing image data. – 余昀哲 May 09 '18 at 09:43
  • What is the camera, and what is the method? The specs could tell us what we need to know. – Nyerguds May 09 '18 at 09:45
  • I can confirm that the image array I got is [x,y] – 余昀哲 May 09 '18 at 09:47
  • Then switching all `GetLength(0)` and `GetLength(1)` calls and switching the arguments in all array [height, width] and [y,x] references should solve your problem. – Nyerguds May 09 '18 at 09:51
  • THe problem remains after I try. – 余昀哲 May 25 '18 at 06:24