JGMS 2 Posted January 8 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
pyscripter 689 Posted January 9 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
pyscripter 689 Posted January 9 (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 January 9 by pyscripter Share this post Link to post
JGMS 2 Posted January 9 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
pyscripter 689 Posted January 9 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
JGMS 2 Posted January 9 The error reads: Project .... raised exception class EPyAttributeError with message 'AttributeError: 'Image' object has no attribute '__class__''. Attached the promised miniproject. Thank you @pyscripter for your time. Project_MiniDemo29.dpr Project_MiniDemo29.dproj MiniDemo29_Unit1.dfm MiniDemo29_Unit1.pas MiniDemo29_Unit2.dfm MiniDemo29_Unit2.pas Share this post Link to post
JGMS 2 Posted January 14 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