Jump to content
JGMS

Howto use P4D Demo29 with PIL and OpenCV

Recommended Posts

I try to use P4D Demo 29 as the basis for image handling functions.
The demo code works like a charm for PIL-based functions, but I can't get it to deal with CV2 (OpenCV) functions. I added the conversions of the PIL- to a CV2-image (OpenCV) and the reverse after processing, but apparently, I missed a clue.

 

In the mainform there is a Timage (image1) that is passed as a Var parameter to the gamma correction function:
  PyForm.PyGammaCorrection( Image1, gammasetting ); {e.g. range between 0.05 and 2}
 

I have prepared and successfully tested all Python code in PyScripter.👌

 Hence the problem is in the Delphi part of the routine.

Here is my code:


function TPyForm.ImageToPyBytes(AGraphic : TGraphic) : Variant;
var
  _stream : TMemoryStream;
  _bytes : PPyObject;
begin
  _stream := TMemoryStream.Create();
  try
    AGraphic.SaveToStream(_stream);
    _bytes := GetPythonEngine.PyBytes_FromStringAndSize( _stream.Memory, _stream.Size );
    Result := VarPythonCreate(_bytes);
    GetPythonEngine.Py_DECREF(_bytes);
  finally
    _stream.Free;
  end;
end;
/////////////////////////////////////////////////////////////////////////


procedure TPyForm.PyGammaCorrection(VAR Image1 : Timage; Setting: double );
var
  _im : Variant;
  _stream : TMemoryStream;
  pargs: PPyObject;
  presult :PPyObject;
  P : PAnsiChar;
  Len : NativeInt;
  PythonCode : TstringList;

begin

 TRY
  PythonCode := TstringList.Create;
	 With PythonCode DO
	 begin
	   Add('from PIL import Image' );
	   Add('import cv2 ');
	   Add('import numpy as np ');
	   Add('from io import BytesIO' );

	   Add('def adjust_gamma(image, gamma=1.0): ');
	   Add('  invGamma = 1.0 / gamma ');
	   Add('  table = np.array([((i / 255.0) ** invGamma) * 255 for i in np.arange(0, 256)]).astype("uint8")  ');
	   Add('  return cv2.LUT(image, table) ');

	   Add('def ImageToBytes(image):' );
	   Add('  stream = BytesIO()' );
	   Add('  image.save(stream, image.format)' );
	   Add('  return stream.getvalue()');

	   Add('def get_opencv_img_from_buffer(buffer, flags): ');
	   Add('  bytes_as_np_array = np.frombuffer(buffer.read(), dtype=np.uint8) ');
	   Add('  return cv2.imdecode(bytes_as_np_array, flags) ');

	   Add('def ProcessImage(data):');
	   Add('  img = get_opencv_img_from_buffer(BytesIO(data), cv2.COLOR_RGB2BGR) ');
	   Add('  gamma =  ' + Setting.ToString );
	   Add('  new_im = Image.fromarray( cv2.cvtColor( adjust_gamma(img, gamma), cv2.COLOR_BGR2RGB ) )' );
	   Add('  return new_im');
	 end;

  GetPythonEngine.ExecStrings(PythonCode);
  _im := MainModule.ProcessImage(ImageToPyBytes(Image1.Picture.Graphic));

    // We have to call PyString_AsStringAndSize because the image may contain zeros  (P4D Demo 29)
    with GetPythonEngine do
    begin
      pargs := MakePyTuple( [ExtractPythonObjectFrom(_im)] );
      try
        presult := PyObject_Call( ExtractPythonObjectFrom(MainModule.ImageToBytes), pargs, nil); //  <<=====================  problem causing code line. What about "MainModule.ImageToBytes"?
        try
          if PyBytes_AsStringAndSize(presult, P, Len) < 0 then
          begin
          //  ShowMessage('This does not work and needs fixing');
            Abort;
          end;
          _stream := TMemoryStream.Create();
          try
            _stream.Write(P^, Len);
            _stream.Position := 0;
            Image1.Picture.Graphic.LoadFromStream(_stream);
          finally
            _stream.Free;
          end;
        finally
          Py_XDECREF(pResult);
        end;
      finally
        Py_DECREF(pargs);
      end;
    end;

 Finally
   PythonCode.Free;
 END;
end;


The error occurs in the line with "PyEval_CallObjectWithKeywords" (or like in the code with the equivalent "PyObject_Call" instead)
the resulting image ("_im: in the code) shows '<PIL.Image.Image image mode=RGB size=4928x3264 at 0x19B16B45BB0>' when inspected, which is OK.
"pargs" shows $19B0845BD00, which to me looks at least better than "nil", but "presult" does show "nil", thus what causes the program to Abort.

 

Can anybody please tell me what causes the problem and how to get it to work?

Many thanks,

  Jan

 

 

 

Share this post


Link to post

Add some error checking after the call.

GetPythonEngine.CheckError;

 

What is the error you are getting?

 

It might help to post a minimal project.

 

Share this post


Link to post
Posted (edited)

Also have a look at Demo 35 to see how you transfer arrays and array-like objects from Python to Delphi using the buffer protocol.

Edited by pyscripter

Share this post


Link to post

Thank you @pyscripter
.
The  code line "if PyBytes_AsStringAndSize(presult, P, Len) < 0 then" results the Delphi error "Project ... raised exception class $C0000005 with message 'c0000005 ACCESS_VIOLATION'.", since "presult" is an empty Pansichar.

GetPythonEngine.CheckError results "Runtime error 201 at ...".

 

I will prepare a minimal project later today, and will study demo 35 as well.

Share this post


Link to post
2 hours ago, JGMS said:

The  code line "if PyBytes_AsStringAndSize(presult, P, Len) < 0

I meant call CheckError before that.

Share this post


Link to post

I have found the solution to get the original posted code to work just fine, by adding 1 line right before the return in ProcessImage:

 

Add('  new_im.format = "JPEG"');

Very simple, in hindsight. Any way, I learned a lot.
However, I did not find a way to delete or update the 'miniproject' files, as yet.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×