1

I am using the JuicyPixels image processing library to ingest an image and convert it into a Matrix type. I want to change the resolution of the image to a user specified dimension, so I can feed it into a neural network.

My program so far reads an image and converts the image to a Data.Matrix Matrix. The image type goes from DynamicImage -> Image Pixel8 ->[[Int]] -> Matrix Int

I have tried using the resize function from Haskell Image Processing (HIP) library but it takes and outputs a type Image arr cs e which I don't know how to process.

Here is my code so far:


import Codec.Picture         
import Codec.Picture.Types
import Control.Arrow
import Data.Ratio 
import Data.Monoid
import Graphics.Image.Processing
import qualified Graphics.Image as I
import qualified Data.Matrix as M
import System.FilePath.Posix (splitExtension)

-- take image input with the filepath given as a DynamicImage type
to2DMatrix :: FilePath -> String -> (Int, Int) -> IO ()
to2DMatrix fp = do
    image <- readImage fp
    case image of
      Left _ -> putStrLn $ "Sorry, not a supported codec for " ++ fp
      Right dynimg -> do
        -- convert dynamic image to greyscale and then to a 2d matrix
        let rle = twoDToMatrix $ pixelToInt $ greyscaleImage $ changeResolution dynimg 
        let (name, _) = splitExtension fp
        writeFile (name ++ ".txt") (show rle)
      Right _ -> putStrLn "Unhandled file type"

changeResolution :: DynamicImage -> String -> (Int, Int)  -> DynamicImage
changeResolution border (dim1, dim2) img = I.resize border (dim1, dim2) img

-- convert DynamicImage to a Pixel8 image
greyscaleImage :: DynamicImage -> Image Pixel8
greyscaleImage = convertRGB8 >>> pixelMap greyscalePixel

-- convert PixelsRGB8 image to Pixel8 image
greyscalePixel :: PixelRGB8 -> Pixel8
greyscalePixel (PixelRGB8 r g b) = round (wr + wg + wb)
  where wr = toRational r * (3 % 10)
        wg = toRational g * (59 % 100)
        wb = toRational b * (11 % 100)

-- convert Pixel8 image to a 2-d matrix of integers
pixelToInt :: Image Pixel8 -> [[Int]]
pixelToInt =
  map reverse . reverse .  snd . pixelFold -- because of the direction pixelFold works in, and the direction
    (\(lastY, ps:pss) x y p ->             -- you add things to lists, reverse and map reverse are necessary
      if y == lastY                        -- to make the output not mirrored horizontaly and vertically
        then (y, (fromIntegral p:ps):pss)
        else (y, [fromIntegral p]:ps:pss))
    (0,[[]])

-- converts list of lists to Data.Matrix type Matrix
twoDToMatrix :: [[Int]] -> M.Matrix Int
twoDToMatrix lists = M.fromLists lists

EDIT: Changed the program to remove changeResolution function since I realized I can just convert the image to grayscale using the convert function or by using readImageY. Here's the updated code:

to2DMatrix :: FilePath -> Border(Interface.Pixel I.Y Word8) -> (Int, Int) -> IO ()
to2DMatrix fp bor (dim1, dim2)= do
    eimg <- I.readImageExact VS fp
    case eimg of
      Left _ -> putStrLn $ "Sorry, not a supported codec for " ++ fp
      Right img -> do
        let imgGray :: Interface.Image VS I.Y Word8
            imgGray = convert (img)
        let new_res :: Interface.Image VS I.Y Word8
            new_res = I.resize imgGray bor (dim1, dim2) 
        let rle = twoDToMatrix $ pixelToInt $ toJPImageY8 new_res
        let (name, _) = splitExtension fp
        writeFile (name ++ ".txt") (show rle)

I get the following error. How do you convert from arr to VS?

Couldn't match expected type ‘Interface.Image VS I.Y Word8’
                  with actual type ‘Interface.Image arr0 I.Y Word8
                                    -> Interface.Image arr0 I.Y Word8’
    • In the expression: resize imgGray bor (dim1, dim2)
      In an equation for ‘new_res’:
          new_res = resize imgGray bor (dim1, dim2)
      In the expression:
        do let imgGray :: Interface.Image VS I.Y Word8
               imgGray = convert (img)
           let new_res :: Interface.Image VS I.Y Word8
               new_res = resize imgGray bor ...
           let rle = twoDToMatrix $ pixelToInt $ toJPImageY8 new_res
           let (name, _) = splitExtension fp
           ....
   |
34 |             new_res = I.resize imgGray bor (dim1, dim2)
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FifthCode
  • 65
  • 6
  • 1
    This are the functions that you are looking for: https://hackage.haskell.org/package/hip-1.5.6.0/docs/Graphics-Image-IO-Formats.html#g:9 – lehins Jun 11 '20 at 09:16
  • Thanks! Now, I am using `fromJPImageY8` function to convert from a greyscale image in `Image Pixel8` format. Then, I use the `resize` function from Graphics.Image to change the resolution. Lastly, when I try to use `toJPImageY8` to go back to a `Image Pixel8`, it gives me an error that it couldn't match expected type `Image VS Y Word8` with `Image arr0 Y Word8`. Any thoughts? – FifthCode Jun 11 '20 at 14:43
  • 1
    make sure you have a proper number of arguments supplied to each function. In your question I don't see you supplying `Border` resolution technique anywhere and also your `changeResolution` function has a totally wrong type signature. – lehins Jun 11 '20 at 15:49
  • I updated the code above if you'd like to take a look. I am not sure how to represent `arr` in type signatures. – FifthCode Jun 11 '20 at 16:18
  • 1
    Now it is easy. As I said, make sure you have all of the arguments supplied and in the correct order. You are missing interpolation method. `new_res = resize Bilinear bor (dim1, dim2) imgGray`. Also you can simplify reading as well, not only conversion. `hip` uses `JuicyPixels` by default to read in images. – lehins Jun 11 '20 at 16:28
  • Thank you! I missed that the interpolation was two arguments. How do you convert between `Image VS Y Double` and `Image VS Y Word8`? I am using the `readImageY` function now to read an image in grayscale, but want to convert it to Type `Image Pixel8` using `toJPImageY8` which requires precision to be `Word8`. – FifthCode Jun 11 '20 at 17:27
  • See this link for the question in my last comment: https://stackoverflow.com/questions/62330791/how-to-convert-precision-in-image-type-from-double-to-word8-using-hip – FifthCode Jun 11 '20 at 18:10

1 Answers1

1

The resize function in the Haskell Image Processing (HIP) library uses Image arr cs e type so I found it easier to read the image with the HIP library, instead of using JuicyPixels.

The types go from Image VS Y Double -> Image VS Y Word8 -> Pixel8 -> [[Int]] -> Matrix Int.

Another advantage of using HIP is that we can read the image in grayscale Pixel Y instead of doing the conversions later. Here is the code with the changes:

import Codec.Picture         
import Codec.Picture.Types
import Graphics.Image.Processing
import qualified Graphics.Image as I
import qualified Graphics.Image.Interface as Interface
import Graphics.Image.ColorSpace
import Data.Word (Word8)
import qualified Data.Matrix as M

to2DMatrix :: FilePath  -> (Int, Int) -> IO (Maybe (M.Matrix Int)) 
to2DMatrix fp (dim1, dim2)= do
    eimg <- I.readImageY VS fp 
    let new_res :: Interface.Image VS I.Y Word8
        new_res = I.resize Bilinear Edge  (dim1, dim2) $ Interface.map conv eimg
    let rle = twoDToMatrix $ pixelToInt $ toJPImageY8 new_res
    return $ Just (rle)

conv :: Interface.Pixel I.Y Double -> Interface.Pixel I.Y Word8 
conv d = fmap Interface.toWord8 d

pixelToInt :: Image Pixel8 -> [[Int]]
pixelToInt =
  map reverse . reverse .  snd . pixelFold
    (\(lastY, ps:pss) x y p ->
      if y == lastY 
        then (y, (fromIntegral p:ps):pss)
        else (y, [fromIntegral p]:ps:pss))
    (0,[[]])
twoDToMatrix :: [[Int]] -> M.Matrix Int
twoDToMatrix lists = M.fromLists lists

conv converts the each pixel from Double to toWord8. The image needs to have Word8 precision for toJPImageY8 function.

FifthCode
  • 65
  • 6
  • 1
    I knew you'll be able to figure it out. You answer is correct and as library author I can endorse you accepting it as the correct one. Two suggestions I can make: it is better to resize the image with pixels being in `Double` precision and convert to `Word8` only after the resize. This has to do with the way that interpolation is currently implemented. Another suggestion I have is to not use lists for conversion to `Matrix`, use something like `toVector` from `HIP` and `reshape` from `hmatrix` – lehins Jun 12 '20 at 09:43