Формат PGM P2 Примените медианный фильтр и сохраните внесенные изменения. - PullRequest
0 голосов
/ 17 декабря 2018

Я пытаюсь применить медианный фильтр к изображению PGM P2, а затем сохранить изображение как PGM.Мой парсер работает просто отлично.Проблема в том, что моей функции медианного фильтра требуется растровое изображение, но его нетрудно преобразовать в PGM-изображение в растровое изображение, но я не могу преобразовать растровое изображение (с примененным медианным фильтром) обратно в формат PGM.Решение состоит в том, чтобы либо преобразовать это растровое изображение обратно в PGM после применения медианной функции (код ниже), либо использовать функцию медианного фильтра на изображении PGM вместо растрового изображения.

public class PGMImage
{
    public string Format { get; set; }
    public int Width { get; set; }
    public int Height { get; set; }
    public int MaxGrayLevel { get; set; }
    public byte[] PixelData { get; set; }

    public bool HasValidMetadata
    {
        get
        {
            return Width > 0 && Height > 0 && MaxGrayLevel > 0 && !string.IsNullOrWhiteSpace(Format);
        }
    }
}

public static PGMImage LoadFromFile(string path)
{
    PGMImage image = new PGMImage();

    using (StreamReader reader = File.OpenText(path))
    {
        string line = string.Empty;

        while ((line = reader.ReadLine()) != null)
        {
            if (string.IsNullOrEmpty(line) || line.StartsWith("#"))
            {
                continue;
            }

            // Read format
            if (image.Format == null)
            {
                if (line.Trim() != "P2")
                {
                    throw new PGMFormatException("Unsupported file format.");
                }

                image.Format = line;
                continue;
            }

            // Read width and height
            string[] widthAndHeight = line.Split(new string[] { " ", "\n", "\t" }, StringSplitOptions.RemoveEmptyEntries);
            if (image.Width == 0 || image.Height == 0)
            {
                image.Width = Convert.ToInt32(widthAndHeight[0]);
                image.Height = Convert.ToInt32(widthAndHeight[1]);
                continue;
            }

            // Read max gray value
            if (image.MaxGrayLevel == 0)
            {
                image.MaxGrayLevel = Convert.ToInt32(line);
                break;
            }
        }

        // Validate metadata
        if (!image.HasValidMetadata)
        {
            throw new PGMFormatException("Metadata could not be read or it is invalid.");
        }

        // Read pixel data
        byte[] pixelData = new byte[image.Width * image.Height];
        int readPixels = 0;

        while ((line = reader.ReadLine()) != null)
        {
            byte[] pixels = line.Split(new string[] { " ", "\n", "\t" }, StringSplitOptions.RemoveEmptyEntries).Select(e => Convert.ToByte(e)).ToArray();

            for (int i = 0; i < pixels.Length; i++)
            {
                pixelData[readPixels + i] = pixels[i];
            }

            readPixels += pixels.Length;
        }

        image.PixelData = pixelData;
    }

    return image;
}

public static void Save(this PGMImage image, string path)
{
    using (StreamWriter writer = new StreamWriter(path))
    {
        // Writing format
        writer.WriteLine(image.Format);

        // Writing width and height
        writer.WriteLine($"{image.Width} {image.Height}");

        // Writing max gray level
        writer.WriteLine(image.MaxGrayLevel);

        // Writing pixel data
        string pixelLine = string.Empty;

        for (int i = 0; i < image.PixelData.Length; i++)
        {
            string currentPixel = $"{image.PixelData[i]} ";

            if (pixelLine.Length + currentPixel.Length >= 70) // 70 is line's max length
            {
                writer.WriteLine(pixelLine);
                pixelLine = currentPixel;
            }
            else
            {
                pixelLine += currentPixel;
            }
        }

        if (pixelLine.Length > 0)
        {
            writer.WriteLine(pixelLine);
        }
    }
}

public Bitmap ToBitmap()
{
    Bitmap bitmap = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed);

    ColorPalette palette = bitmap.Palette;
    for (int i = 0; i < 256; i++)
        palette.Entries[i] = Color.FromArgb(255, i, i, i);
    bitmap.Palette = palette;

    BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);
    Marshal.Copy(PixelData, 0, bmpData.Scan0, PixelData.Length);
    bitmap.UnlockBits(bmpData);

    return bitmap;
}

Моя медианная функция фильтра ниже.Вы можете видеть, что это плохой метод расширения растрового изображения, потому что я хочу сохранить загруженное изображение PGM с примененным медианным фильтром в формате PGM P2, поэтому мне нужно преобразовать полученное растровое изображение в PGM, который я не могусделать.

public static Bitmap MedianFilter(this Bitmap sourceBitmap, int matrixSize, int bias = 0, bool grayscale = false)
{
    BitmapData sourceData = sourceBitmap.LockBits(new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

    byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height];
    byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];

    Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length);

    sourceBitmap.UnlockBits(sourceData);

    if (grayscale == true)
    {
        float rgb = 0;

        for (int k = 0; k < pixelBuffer.Length; k += 4)
        {
            rgb = pixelBuffer[k] * 0.11f;
            rgb += pixelBuffer[k + 1] * 0.59f;
            rgb += pixelBuffer[k + 2] * 0.3f;

            pixelBuffer[k] = (byte)rgb;
            pixelBuffer[k + 1] = pixelBuffer[k];
            pixelBuffer[k + 2] = pixelBuffer[k];
            pixelBuffer[k + 3] = 255;
        }
    }

    int filterOffset = (matrixSize - 1) / 2;
    int calcOffset = 0;

    int byteOffset = 0;

    List<int> neighbourPixels = new List<int>();
    byte[] middlePixel;

    for (int offsetY = filterOffset; offsetY < sourceBitmap.Height - filterOffset; offsetY++)
    {
        for (int offsetX = filterOffset; offsetX < sourceBitmap.Width - filterOffset; offsetX++)
        {
            byteOffset = offsetY * sourceData.Stride + offsetX * 4;

            neighbourPixels.Clear();

            for (int filterY = -filterOffset; filterY <= filterOffset; filterY++)
            {
                for (int filterX = -filterOffset; filterX <= filterOffset; filterX++)
                {
                    calcOffset = byteOffset + (filterX * 4) + (filterY * sourceData.Stride);
                    neighbourPixels.Add(BitConverter.ToInt32(pixelBuffer, calcOffset));
                }
            }

            neighbourPixels.Sort();

            middlePixel = BitConverter.GetBytes(neighbourPixels[filterOffset]);

            resultBuffer[byteOffset] = middlePixel[0];
            resultBuffer[byteOffset + 1] = middlePixel[1];
            resultBuffer[byteOffset + 2] = middlePixel[2];
            resultBuffer[byteOffset + 3] = middlePixel[3];
        }
    }

    Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
    BitmapData resultData = resultBitmap.LockBits(new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);

    Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);

    resultBitmap.UnlockBits(resultData);

    return resultBitmap;
}

Я попытался реализовать ту же функцию, что и выше, но на этот раз для изображения PGM, потому что таким образом мне не нужно преобразовывать растровое изображение в PGM, потому что это уже будет PGM.Во время компиляции ошибок нет, но я не вижу разницы в изображении после его применения.Кроме того, отсутствует функциональность размера матрицы.

Итак, мои вопросы:

  1. Как создать метод расширения ToPGM(), который преобразует растровое изображение (с примененным медианным фильтром) обратно вPGM P2 format?
  2. Как сделать функцию медианы для изображения PGM (включая параметр размера матрицы).

Код фильтра медианы моего изображения PGM приведен ниже, но в нем отсутствует матрицаразмер, как я уже говорил выше, и я не уверен, работает ли он или нет, потому что я не могу найти разницу в изображении (возможно, размер матрицы недостаточно велик, например 7x7, для более заметных результатов, но я 'я не уверен в этом).

public static PGMImage ApplyMedianFilter(this PGMImage inputImage, int matrixSize)
{
    PGMImage outputImage = inputImage;

    int readBytes = 0;
    byte[,] pixelsData = new byte[inputImage.Width, inputImage.Height];
    for (int y = 0; y < inputImage.Height; y++)
    {
        int counter = 0;

        for (int x = 0; x < inputImage.Width; x++)
        {
            pixelsData[x, y] = inputImage.PixelData[x + readBytes];
            counter++;
        }

        readBytes += counter;
    }

    byte[,] tempArray = new byte[inputImage.Width, inputImage.Height];

    for (int x = 0; x < inputImage.Width - 1; x++)
    {
        for (int y = 0; y < inputImage.Height - 1; y++)
        {
            List<int> validNeighbors = inputImage.GetAllNeighbors(pixelsData, x, y);

            validNeighbors.Sort();
            int medianIndex = validNeighbors.Count / 2;
            byte medianPixel = (byte)validNeighbors[medianIndex];

            tempArray[x, y] = medianPixel;
        }
    }

    outputImage.PixelData = new byte[inputImage.Width * inputImage.Height];

    for (int y = 0; y < inputImage.Height; y++)
    {
        for (int x = 0; x < inputImage.Width; x++)
        {
            outputImage.PixelData[y * inputImage.Width + x] = tempArray[x, y];
        }
    }

    return outputImage;
}
...