I am trying to save an image to jpeg after processing with leptonica. I am using python with ctypes and my code is:
import ctypes
leptlib = "liblept.so"
leptonica = ctypes.cdll.LoadLibrary(leptlib)
filename = "IMAG0724.jpg"
img = leptonica.pixRead(filename)
leptonica.pixConvertTo8.argtypes = [ctypes.c_void_p,
ctypes.c_int32]
pix_image = leptonica.pixConvertTo8(img, False)
leptonica.pixOtsuAdaptiveThreshold.argtypes = [ctypes.c_void_p,
ctypes.c_int32,
ctypes.c_int32,
ctypes.c_int32,
ctypes.c_int32,
ctypes.c_float]
otsu = leptonica.pixOtsuAdaptiveThreshold(pix_image,20,20,0,0,0.1)
leptonica.pixWriteJpeg.argtypes = [ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_int32,
ctypes.c_int32]
leptonica.pixWriteJpeg("otsu-lept", otsu, 75, 0)
This code produces the error:
Error in pixWriteJpeg: pix not defined
I believe this is because I need to do something after applying the otsu but before writing the new image. What am I missing?
EDIT-
I have now ammended the following per leptonica docs http://tpgit.github.io/Leptonica/binarize_8c.html:
leptonica.pixOtsuAdaptiveThreshold.argtypes = [ctypes.c_void_p,
ctypes.c_int32,
ctypes.c_int32,
ctypes.c_int32,
ctypes.c_int32,
ctypes.c_float,
ctypes.c_void_p]
leptonica.pixOtsuAdaptiveThreshold(pix_image,20,20,0,0,0.1, img)
leptonica.pixWriteJpeg("otsu-lept", img, 75, 0)
A new error now occurs:
Maximum supported image dimension is 65500 pixels Error in pixWriteStreamJpeg: internal jpeg error Error in pixWriteJpeg: pix not written to stream
My image resolution is 1552 x 2592 and leptonica.pixWriteJpeg works when the otsu function line is commented out so it seems that the problem is still with the image being returned by the otsu function.
**** EDIT 2 ****
When I check the output img using leptonica it is telling me the width is some large number which seems to vary each time I run the function (eg 149996048) and the height remains correct at the same value as input image. It looks like the otsu function changing the image width to this large value for some reason.
EDIT 3
jsbueno below provided me with the solution to this problem which I will share here. The issue is because I was passing the image directly to the function when it is actually necessary to pass a pointer of a pointer to the function which then works. Final working code is below:
import ctypes
leptlib = "liblept.so"
leptonica = ctypes.cdll.LoadLibrary(leptlib)
filename = "IMAG0724.jpg"
img = leptonica.pixRead(filename)
leptonica.pixConvertTo8.argtypes = [ctypes.c_void_p,
ctypes.c_int32
]
pix_image = leptonica.pixConvertTo8(img, False)
w = leptonica.pixGetWidth(img)
h = leptonica.pixGetHeight(img)
pixa_out = leptonica.pixCreate(w,h,8)
pixa = ctypes.c_void_p(pixa_out)
leptonica.pixOtsuAdaptiveThreshold.argtypes = [ctypes.c_void_p,
ctypes.c_int32,
ctypes.c_int32,
ctypes.c_int32,
ctypes.c_int32,
ctypes.c_float,
ctypes.c_void_p,
ctypes.c_void_p
]
otsu = leptonica.pixOtsuAdaptiveThreshold(pix_image,
20,
20,
0,
0,
0.1,
None,
ctypes.addressof(pixa)
)
leptonica.pixWritePng("otsu-lept", pixa, 8)
I found out what you are doing wrong - if you check on the function signature, it takes optionally 2 muttualy exclusive parameters at the end - one of which is an array for it to return the coefficients, not an image - the very last parameter is an empty image where to draw the application of the algorithm.
Moreover, the 2 last parameters are "pointers to a pointer" of a leptonica PIX object -
You can create a Python variable containing a ctypes.c_void_p object, and pass the
ctypes.addressof
it to the leptonica function.
The docs for the outsu function are as follows:
/*------------------------------------------------------------------*
* Adaptive Otsu-based thresholding *
*------------------------------------------------------------------*/
/*!
* pixOtsuAdaptiveThreshold()
*
* Input: pixs (8 bpp)
* sx, sy (desired tile dimensions; actual size may vary)
* smoothx, smoothy (half-width of convolution kernel applied to
* threshold array: use 0 for no smoothing)
* scorefract (fraction of the max Otsu score; typ. 0.1;
* use 0.0 for standard Otsu)
* &pixth (<optional return> array of threshold values
* found for each tile)
* &pixd (<optional return> thresholded input pixs, based on
* the threshold array)
* Return: 0 if OK, 1 on error
*
PS. While researching this, I managed to build the python-leptonica bindings for leptonica 1.7.1 - as soon as I clean up the mess I did to get there, I should make another release.
For anyone able to run python-leptonica, as it is now (limted to leptonica 1.6.0 without hacking)- the code for using this function would be something along:
import leptonica
import ctypes
img = leptonica.functions.pixRead("t1.png")
imggray = leptonica.functions.pixConvertRGBToGrayMinMax(img, 1)
img = leptonica.functions.pixRead("t1.png")
output = leptonica.functions.pixCreate(imggray.w, imggray.h, 1)
a = ctypes.c_voidp()
leptonica.functions.pixOtsuAdaptiveThreshold(imggray, 20, 20, 0, 0, .1, None, ctypes.addressof(a))
output = leptonica.PIX(from_address=a)
leptonica.functions.pixWritePng("t3.png", c, 1)