Jump to content

shineworld

Members
  • Content Count

    283
  • Joined

  • Last visited

  • Days Won

    3

Posts posted by shineworld


  1. To communicate between Delphi pure UI process and Python process I've implemented an API Server, based on TCP/IP + JSON encrypted messages.

    A Python package (cnc-api-client-core in PyPi) implements the same protocol (API Client).
    CNC Board covers any type of CNC (plasma, laser, mill, etc, till 9-Axis in EtherCAT Drivers or 6-Axis Step/DIR Drivers).

    api-server-client-system-overview.png

     

    With Python API Client interface I can do anything on CNC Control Software and CNC Board,
    exposing CNC features to any Python UI interface.

    Here an old example of Python process which use API Client and PySimpleGUI UI to interact with CNC:


    Now UI was moved to DelphiFMX to be used in Windows and Linux OS.


  2. I have a lot, pretty much everything, to learn about Python, importing and building modules and packages.
     

    I had NEVER used Python or had never been interested in Python before the appearance and knowledge of Python4Delphi, some months ago.
     

    Since Embarcadero made DelphiVCL and Python4Delphi available, I have thrown myself headlong into bringing code that normally runs on native Delphi programs

    but standalone on Delphi modules and extensions for Python so that customers can make their own customizations via Python.

     

    Now, looking at how DelphiVCL initializes the Python engine I found that I have misunderstood a lot of things.

     

    Solve the problem is based on changing:
        ExtensionManager.FEngine.UseLastKnownVersion := True;
        ExtensionManager.FEngine.OpenDll('');

    to
        ExtensionManager.FEngine.UseLastKnownVersion := True;
        ExtensionManager.FEngine.LoadDllInExtensionModule();

    Obviously, in the end, I will collect any PYD (actually 32 Pyds) in one PyPi wheel install package so all Pyds will be in the Lib\ite-packages folder like Embarcadero does with DelphiVCL.

    The resulting framework will use opencv to capture, filter, and evaluate images to find specific objects, measure sizes, etc.
    At moment I don't use any form of AI to do that to remain fast and simple.

    • Like 1

  3. To simplify Python + DelphiVCL + owner Delphi PYD modules for our customer I would like to use the embedded version of Python.
    When the customer installs our software it installs also an embedded version of Python in the known path so do not touch customer versions or virtual environments.

    So I can have a customer with a full install of Python 3.10.1 in: 
    C:\Users\silverio.di.QEMSRL\AppData\Local\Programs\Python\Python310\

    and our embedded Python 3.10.4 with pre-installed custom modules at:
    D:\x\develop\qem\cnc_vision_1\python\

    The Embedded Python version has PIP and all related modules as well as DelphiVCL, Skia, OpenCV, etc.
    When I run the embedded Python version I can import delphivcl, skia, cv2, etc without issue BUT not our PYD made with Delphi.
    However, the same module, called cnc_vision_ext.cp310-win_amd64.pyd, is loaded without issue with the full Python version...

    Could be that the embedded python distro is missing some lib or basic module required to load it?

    I've captured the CMD window reported below.

    Any suggestion?
     

    D:\x\develop\qem\cnc_vision_1\python>dir
     Il volume nell'unità D è Volume
     Numero di serie del volume: 48BE-3E0C
    
     Directory di D:\x\develop\qem\cnc_vision_1\python
    
    04/05/2022  18:09    <DIR>          .
    04/05/2022  18:09    <DIR>          ..
    03/05/2022  16:58         5.347.328 cnc_vision_ext.cp310-win_amd64.pyd
    22/10/2021  18:30         2.159.352 get-pip.py
    04/05/2022  18:05    <DIR>          Lib
    23/03/2022  23:22         3.439.512 libcrypto-1_1.dll
    23/03/2022  23:22            32.792 libffi-7.dll
    23/03/2022  23:22           698.784 libssl-1_1.dll
    23/03/2022  23:22            32.763 LICENSE.txt
    23/03/2022  23:22           194.000 pyexpat.pyd
    23/03/2022  23:22           589.053 python.cat
    23/03/2022  23:22            99.280 python.exe
    23/03/2022  23:22            62.416 python3.dll
    23/03/2022  23:22         4.445.648 python310.dll
    23/03/2022  23:23         2.638.493 python310.zip
    04/05/2022  18:04                79 python310._pth
    23/03/2022  23:22            97.744 pythonw.exe
    04/05/2022  18:06               302 requirements.txt
    04/05/2022  18:06    <DIR>          Scripts
    23/03/2022  23:22            26.064 select.pyd
    23/03/2022  23:22         1.476.048 sqlite3.dll
    23/03/2022  23:22         1.118.672 unicodedata.pyd
    23/03/2022  23:22            98.224 vcruntime140.dll
    23/03/2022  23:22            37.256 vcruntime140_1.dll
    23/03/2022  23:22            27.088 winsound.pyd
    23/03/2022  23:22            61.392 _asyncio.pyd
    23/03/2022  23:22            79.824 _bz2.pyd
    23/03/2022  23:22           119.760 _ctypes.pyd
    23/03/2022  23:22           248.272 _decimal.pyd
    23/03/2022  23:22           124.368 _elementtree.pyd
    23/03/2022  23:22            60.880 _hashlib.pyd
    23/03/2022  23:22           154.064 _lzma.pyd
    23/03/2022  23:22            40.912 _msi.pyd
    23/03/2022  23:22            30.672 _multiprocessing.pyd
    23/03/2022  23:22            46.032 _overlapped.pyd
    23/03/2022  23:22            27.600 _queue.pyd
    23/03/2022  23:22            75.216 _socket.pyd
    23/03/2022  23:22            94.672 _sqlite3.pyd
    23/03/2022  23:22           156.624 _ssl.pyd
    23/03/2022  23:22            21.456 _uuid.pyd
    23/03/2022  23:22            40.912 _zoneinfo.pyd
    04/05/2022  18:04    <DIR>          __archive__
                  37 File     24.003.554 byte
                   5 Directory  206.815.965.184 byte disponibili
    
    D:\x\develop\qem\cnc_vision_1\python>C:\Users\silverio.di.QEMSRL\AppData\Local\Programs\Python\Python310\Python.exe
    Python 3.10.4 (tags/v3.10.4:9d38120, Mar 23 2022, 23:13:41) [MSC v.1929 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import delphivcl as vcl
    >>> import cnc_vision_ext as ext
    >>> quit()
    
    D:\x\develop\qem\cnc_vision_1\python>.\python.exe
    Python 3.10.4 (tags/v3.10.4:9d38120, Mar 23 2022, 23:13:41) [MSC v.1929 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import delphivcl as vcl
    >>> import cnc_vision_ext as ext
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    SystemError: initialization of cnc_vision_ext did not return an extension module
    >>> quit()
    
    D:\x\develop\qem\cnc_vision_1\python>

     


  4. Following your suggestion, I've solved using PyBytes_FromStringAndSize... very very fast!!!

    1] I've created a VideoCapture as native Delphi component TCncVisionCapturePxc.

        This can be used for standard Delphi programs or wrapped in an expansion module pyd to be used in Python.
        In this class, I can't use python objects, obviously as return values but the dynamic array of bytes for a frame.
    2] I wrapped the class to export in pyd adding an extra method to get fastly the frame with PyBytes_FromStringAndSize:

    uses
      ...
      osCncVideoCapturePxc;
    
    type
      TPyCncVisionVideoCapturePxc_Wrapper = class(TPyDelphiObject)
      private
        constructor Create(APythonType: TPythonType); override;
        constructor CreateWith(PythonType: TPythonType; args: PPyObject); override;
        function Repr: PPyObject; override;
        class function DelphiObjectClass: TClass; override;
        class procedure RegisterMethods(PythonType: TPythonType); override;
        function GrabbedFrameEx(pself, args: PPyObject): PPyObject; cdecl;
      end;
    
    ...
    
    { TPyCncVisionVideoCapturePxc_Wrapper }
    
    constructor TPyCncVisionVideoCapturePxc_Wrapper.Create(APythonType: TPythonType);
    begin
      inherited;
    
      DelphiObject := TCncVisionVideoCapturePxc.Create;
      Owned := False;
    end;
    
    constructor TPyCncVisionVideoCapturePxc_Wrapper.CreateWith(PythonType: TPythonType; args: PPyObject);
    begin
      inherited;
    
      //###
    end;
    
    class function TPyCncVisionVideoCapturePxc_Wrapper.DelphiObjectClass: TClass;
    begin
      Result := TCncVisionVideoCapturePxc;
    end;
    
    function TPyCncVisionVideoCapturePxc_Wrapper.Repr: PPyObject;
    begin
      Result := GetPythonEngine.PyUnicodeFromString('none to show at moment');
    end;
    
    class procedure TPyCncVisionVideoCapturePxc_Wrapper.RegisterMethods(PythonType: TPythonType);
    begin
      inherited;
      with PythonType do
      begin
        AddMethod('grabbed_frame_ex', @TPyCncVisionVideoCapturePxc_Wrapper.GrabbedFrameEx, 'get grabbed frame as string' );
      end;
    end;
    
    function TPyCncVisionVideoCapturePxc_Wrapper.GrabbedFrameEx(pself, args: PPyObject): PPyObject; cdecl;
    var
      S: AnsiString;
      FrameSize: Integer;
      Frame: TByteDynArray;
      VideoCapture: TCncVisionVideoCapturePxc;
    begin
      Adjust(@Self);
      VideoCapture := TCncVisionVideoCapturePxc(DelphiObject);
    
      Frame := VideoCapture.grabbed_frame;
        FrameSize := Length(Frame);
      if FrameSize = 0 then
        Exit(GetPythonEngine.ReturnNone);
    
      SetLength(S, FrameSize);
      CopyMemory(@S[1], Frame, FrameSize);
      Result := GetPythonEngine.PyBytes_FromStringAndSize(@S[1], FrameSize);
    end;


    Adjust(@Self) was a nightmare because I don't know about that and without the self access to DelphiObject is always invalid.

    Now from 80ms to transfer an image from Delphi to Python is moved to 128uS.

    I'm happy.
     


  5. When I can I move thread things to Delphi Thread in an expansion module but this has a limited range of operations, you have to use only Delphi libraries, no Python code there.

     

    For example, I'm banging my head on a very odd problem without a solution.

     

    1] In a Delphi extension module I get frames from a proprietary camera that can't be accessed with OpenCV VideoCapture, for a lot of reasons.

    2] A Python thread gets the frame data from the Delphi extension module as a property to TByteDynArray data.

     

    TByteDynArray is converted to List by PythonWrapper and the inner system copies any byte of the array in a VariantArray which takes a big amount of time.

    To get back from the Delphi module 48000 image bytes take 0.080 (80ms) which is too much, slowing down a lot the FPS.

    A classical bottleneck.....


    I've tried to change the FrameData returned from TByteDynArray to AnsiString and data movement is very very fast (0.001ms) but AnsiString with Frame Data

    is encoded to Unicode before sending it back to python and str.encode(frame_data) in Python is not applicable....

     

    So my question?

    What is the best way to get an array of bytes from the Delphi extension module (pyd) back to python?
    Have I to enter in the HELL of array pointers shared between Delphi to Python ?

     

    Delphi extension module is able to capture till 60fps, but the bottleneck of frame retrieve from PYD to Python fall down my FPS to 6FPS...
    image.thumb.png.d6573a5c9de35e2d0f9562cd4ba2db92.png


  6. I'm not a Win guru but Windows has owner rules on how much time is left to a process and related task.

    Usually, it does not permit to use of more than 40/50% of the time to a process/thread depending on many factors.


    GIL Lock system however limits a lot of threading performances in Python, is not permit a true Threading programming
    as like as in other languages .

    When possibile I move python math code in delphi extension module native threads or I use Cython to improve moudule performances.

    To improve a little the performances of time.Sleep() and timers and so of GIL I've tried to change the precision

    timer of Windows with:

    from os_utils import os_improve_timings
    ...
    ..
    .
    
    def main():
        """Main entry point."""
        ...
        ..
        .
    
    # MAIN entry point when module is called
    if __name__ == '__main__':
    
        # improve os timings and run main
        with os_improve_timings():
            main()

     

     

    os_utils.py


  7. There are many ways to "define" what Pythonxxx.DLL will be used by PythonEngine used in the Delphi application or in a PYD extension module made with Delphi.

     

    You can set "MyEngine.UseLaskKnownVersion := True" so the Engine search for 3.10, then 3.9, ... till the oldest version is supported.

    You can also "fix" a version leaving it to PythonEngine to search for the path.

    This is done thanks to fact that Python "stores" any installed Python version path in Windows Registry.

    {**
     *  TAKE CARE
     *  =========
     *  PYD modules need to be build for a specific Python version using below defines.
     *  The defines MUST be mutually exclusive.
     *  If not defined the "Use Last Known Version" will be used.
     *
     **}
    
    {$DEFINE USE_PYTHON_39}
    {.DEFINE USE_PYTHON_310}
    
    {$IF Defined(USE_PYTHON_39) and Defined(USE_PYTHON_310)}
      {$Message Fatal 'Only one Python version can be set.'}
    {$IFEND}
    
    { module import functions }
    
    function PyInit_cnc_vision_ext: PPyObject;
    begin
      Result := nil;
      try
        ExtensionManager.FEngine := TPythonEngine.Create(nil);
        ExtensionManager.FEngine.AutoFinalize := False;
    {$IF Defined(USE_PYTHON_39)}
        ExtensionManager.FEngine.DLLName := 'python39.dll';
        ExtensionManager.FEngine.UseLastKnownVersion := False;
        ExtensionManager.FEngine.OpenDll(ExtensionManager.FEngine.DLLName);
    {$ELSEIF Defined(USE_PYTHON_310)}
        ExtensionManager.FEngine.DLLName := 'python310.dll';
        ExtensionManager.FEngine.UseLastKnownVersion := False;
        ExtensionManager.FEngine.OpenDll(ExtensionManager.FEngine.DLLName);
    {$ELSE}
        ExtensionManager.FEngine.UseLastKnownVersion := True;
        ExtensionManager.FEngine.OpenDll('');
    {$IFEND}
        ExtensionManager.FModule := TPythonModule.Create(nil);
        ExtensionManager.FModule.Engine := ExtensionManager.FEngine;
        ExtensionManager.FModule.ModuleName := 'cnc_vision_ext';
    
        ExtensionManager.FWrapper := TPyDelphiWrapper.Create(nil);
        ExtensionManager.FWrapper.Engine := ExtensionManager.FEngine;
        ExtensionManager.FWrapper.Module := ExtensionManager.FModule;
    
        ExtensionManager.FModule.Initialize;
        ExtensionManager.FWrapper.OnInitialization := ExtensionManager.WrapperInitializationEvent;
        ExtensionManager.FWrapper.Initialize;
    
        Result := ExtensionManager.FModule.Module;
      except
      end;
    end;

    This is a part of my extension module in which I can define if the module is built for 3.9, 3.10, or looking for the last known version.
    OpenDLL internally searches in Windows Registry where the requested python engine path is.

    Take a look at file: PythonVersions.pas GetRegisteredPythonVersion() function there is also a good description of system.
    https://github.com/pyscripter/python4delphi/wiki/FindingPython

    I don't know how works with Anaconda envs or embedded python distros.


     


  8. The way to manage FPU exceptions in DELPHI is not the same on Python so you have to add this in your Delphi unit:

    initialization
      MaskFPUExceptions(True);

     

    PS: Actually I've moved from PIL to SKIA in image management.
    Skia offers more features than Pillow.

    import skia
    		
    ...
    .
    ...
    
    # frame_inp is a numpy containing RGB image
    
    # convert numpy image from RGB to RGBA (add AlphaBlend layer)
    skia_frame = cv2.cvtColor(frame_inp, cv2.COLOR_RGB2RGBA)
            
    # create a skia image from numpy RGBA array
    skia_image = skia.Image.fromarray(skia_frame)
    
    # sample of image resize with skia
    image_ratio = 1.0
    image_width = skia_image.width()
    image_height = skia_image.height()
    camera_image_width = self.CameraImage.Width
    camera_image_height = self.CameraImage.Height
    
    r_w = image_width / camera_image_width
    r_h = image_height /  camera_image_height
    if r_w > 1.0 or r_h > 1.0:
    	image_ratio = max(r_w, r_h)
        image_width = int(image_width / image_ratio)
        image_height = int(image_height / image_ratio)
        skia_image = skia_image.resize(image_width, image_height, skia.FilterQuality.kLow_FilterQuality)
        skia_frame = skia_image.toarray()
    
    # sample of antialiasing drawings on skia
    paint = skia.Paint(AntiAlias=True, Color=skia.ColorRED)
    canvas = skia.Canvas(skia_frame)
    canvas.drawLine(0, int(image_height / 2), image_width, int(image_height / 2), paint)
    canvas.drawLine(int(image_width / 2), 0, int(image_width / 2), image_height, paint)
    
    # back skia image to numpy array RGB
    skia_frame = cv2.cvtColor(skia_frame, cv2.COLOR_RGBA2RGB)
    
    # draw of numpy array RGB to delphi Image
    ext.update_image_from_bytes(
    	self.CameraImage,
    	skia_frame.tobytes(),
    	skia_frame.shape[1],
    	skia_frame.shape[0],
    	3 if len(skia_frame.shape) == 3 else 1
    )
    self.CameraImage.Repaint()

     


  9. Hi all,

    there is a way to inform PyLint about delphivcl types ?
    I've tried to add it to .pylintrc without results:

    [MASTER]
    
    # A comma-separated list of package or module names from where C extensions may
    # be loaded. Extensions are loading into the active Python interpreter and may
    # run arbitrary code.
    extension-pkg-allow-list=
    
    # A comma-separated list of package or module names from where C extensions may
    # be loaded. Extensions are loading into the active Python interpreter and may
    # run arbitrary code. (This is an alternative name to extension-pkg-allow-list
    # for backward compatibility.)
    extension-pkg-whitelist=cv2,delphivcl
    
    # Return non-zero exit code if any of these messages/categories are detected,
    # even if score is above --fail-under value. Syntax same as enable. Messages
    # specified are enabled, while categories only check already-enabled messages.
    fail-on=
    ...
    ..
    .

     


  10. Delphi Extension way vs Pillow using time.time_perf:

            # delphi module extension way
            t1 = time.perf_counter()
            ext.update_image_from_bytes(
                self.CameraImage,
                frame_inp.tobytes(),
                frame_inp.shape[1],
                frame_inp.shape[0],
                3 if len(frame_inp.shape) == 3 else 1)
            self.CameraImage.Repaint()
            t2 = time.perf_counter()
    
            # pillow way
            t3 = time.perf_counter()
            rgb_im: PIL_Image.Image = PIL_Image.fromarray(np.uint8(frame_inp[...,[2,1,0]].copy())).convert('RGB')
            self.CameraImage.Picture.Bitmap.SetSize(rgb_im.width, rgb_im.height)
            dib = PIL_ImageWin.Dib(rgb_im)
            dib.expose(self.CameraImage.Picture.Bitmap.Canvas.Handle)
            self.CameraImage.Repaint()
            t4 = time.perf_counter()
    
            # timings report
            self.Caption = 'EXT = {:7.3f} ms - PILLOW {:7.3f}'.format(t2 - t1, t4 - t3)

    Delphi way take half time in this case 🙂

    image.thumb.png.0b8bebf0957a8e8821a15b8125155d9c.png


  11. To fastly send an image in NumPy array to a delphivcl.Image component, without use Pillow and DIB way,  you can create a new python extension module with Delphi

    where you wrap a update_image_from_bytes(image: Image(), data: bytes, width: int, height: int, channels: int) -> bool

    { generic functions wrappers }
    
    function CncVisionUpdateImageFromBytesArray_Wrapper(pself, args: PPyObject): PPyObject; cdecl;
    var
      Image: TImage;
      Width: Integer;
      Height: Integer;
      Channels: Integer;
      Bytes: TByteArray;
      BytesPyObj: PPyObject;
      ImagePyObj: PPyObject;
    
      function PyBytesAsBytes(Obj: PPyObject): TByteArray;
      var
        Size: NativeInt;
        Buffer: PAnsiChar;
      begin
        Result := nil;
        with GetPythonEngine do
        begin
          if not PyBytes_Check(Obj) then Exit;
          PyBytes_AsStringAndSize(Obj, Buffer, Size);
          if Size = 0 then Exit;
          SetLength(Result, Size);
          CopyMemory(Result, Buffer, Size);
        end;
      end;
    
    begin
      with GetPythonEngine do
      begin
        try
          if PyArg_ParseTuple(args, 'OOiii:update_image_from_bytes', @ImagePyObj, @BytesPyObj, @Width, @Height, @Channels) <> 0 then
          begin
            if ImagePyObj.ob_type.tp_name <> 'Image' then AbortFast;
            Image := TPyDelphiImage(TPyObject(PAnsiChar(ImagePyObj) + SizeOf(PyObject))).DelphiObject;
            if Image = nil then AbortFast;
    
            if BytesPyObj.ob_type.tp_name <> 'bytes' then AbortFast;
            Bytes := PyBytesAsBytes(BytesPyObj);
            if Bytes = nil then AbortFast;
            if Length(Bytes) <> (Width * Height * Channels) then AbortFast;
    
            if not CncVisionUpdateImageFromBytesArray(Image, Bytes, Width, Height, Channels) then AbortFast;
            Result := ReturnTrue;
          end
          else
            Result := ReturnFalse;
        except
          Result := ReturnFalse;
        end;
      end;
    end;
    
    ...
    .
    ...
    
    procedure TPyExtensionManager.WrapperInitializationEvent(Sender: TObject);
    begin
      FWrapper.RegisterFunction
      (
        PAnsiChar('update_image_from_bytes'),
        CncVisionUpdateImageFromBytesArray_Wrapper,
        PAnsiChar('update delphivcl Image object from bytes array with width, height & channels')
      );
    end;

    Then the pascal byte array to Image is:

    function CncVisionUpdateImageFromBytesArray(Image: TImage; Bytes: osCncVisionTypes.TByteArray; Width, Height, Channels: Integer): Boolean;
    type
      TRGBBitmapInfoHeader = record
        Header: TBitmapInfoHeader;
        ColorTable: array[0..255] of TRGBQuad;
      end;
      PBitmapInfoHeader = ^TBitmapInfoHeader;
    var
      I: Integer;
      Bitmap: TBitmap;
      IsGray: Boolean;
      Buffer: TRGBBitmapInfoHeader;
      BmpInfoHeader: PBitmapInfoHeader;
    var
      BmpInfoBuffer: TBitmapInfo absolute Buffer;
    begin
      try
        if Length(Bytes) = 0 then
          Exit(False);
        if Length(Bytes) <> (Width * Height * Channels) then
          Exit(False);
        if not Channels in [1, 3] then
          Exit(False);
    
        if Image.Picture.Bitmap = nil then
          Image.Picture.Bitmap := TBitmap.Create;
        Bitmap := Image.Picture.Bitmap;
        Bitmap.Width := Width;
        Bitmap.Height := Height;
    
        BmpInfoHeader := PBitmapInfoHeader(@Buffer);
        BmpInfoHeader^.biSize          := SizeOf(TBitmapInfoHeader);
        BmpInfoHeader^.biWidth         := Width;
        BmpInfoHeader^.biHeight        := -Height;
        BmpInfoHeader^.biPlanes        := 1;
        BmpInfoHeader^.biBitCount      := 8 * Channels;
        BmpInfoHeader^.biCompression   := BI_RGB;
        BmpInfoHeader^.biSizeImage     := 0;
        BmpInfoHeader^.biXPelsPerMeter := 0;
        BmpInfoHeader^.biYPelsPerMeter := 0;
        BmpInfoHeader^.biClrUsed       := 0;
        BmpInfoHeader^.biClrImportant  := 0;
    
        // if Bytes array is for monochrome image (channels = 1) normalizes bitmap color table
        // https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
        //
        // The BITMAPINFOHEADER structure may be followed by an array of palette entries or color masks.
        // The rules depend on the value of biCompression.
        //
        // - If biCompression equals BI_RGB and the bitmap uses 8 bpp or less, the bitmap has a color table immediately
        //   following the BITMAPINFOHEADER structure. The color table consists of an array of RGBQUAD values. The size
        //   of the array is given by the biClrUsed member. If biClrUsed is zero, the array contains the maximum number
        //   of colors for the given bitdepth; that is, 2^biBitCount colors.
        if Channels = 1 then
        begin
          for I := 0 to 255 do
          begin
            Buffer.ColorTable[I].rgbBlue := I;
            Buffer.ColorTable[I].rgbGreen := I;
            Buffer.ColorTable[I].rgbRed := I;
          end;
        end;
    
        LockWindowUpdate(Bitmap.Canvas.Handle);
        try
          Result := SetDIBitsToDevice
          (
            Bitmap.Canvas.Handle, // handle to device context
            0,                    // x-coordinate of upper-left corner of
            0,                    // y-coordinate of upper-left corner of
            Width,                // source rectangle width
            Height,               // source rectangle height
            0,                    // x-coordinate of Lower-left corner of
            0,                    // y-coordinate of Lower-left corner of
            0,                    // first scan line in array
            Height,               // number of scan lines
            Bytes,                // address of array with DIB bits
            BmpInfoBuffer,        // address of structure with bitmap info
            DIB_RGB_COLORS        // RGB or palette indexes
          ) > 0;
        finally
          LockWindowUpdate(0)
        end;
      except
        Result := False;
      end;
    end;

    CncVisionUpdateImageFromBytesArray permits RGB or Monochrome Bytes array.

    In python the code to update image is simple:

    import cnc_vision_ext as ext # this is only my delphi extension module for python where extra features for delphivcl are placed
    
    # for monochrome frames to CameraImage (delphivcl.Image object)
            frame = get_frame_image_from_somewhere()
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            ext.update_image_from_bytes(
                self.CameraImage,
                frame_inp.tobytes(),
                frame_inp.shape[1],
                frame_inp.shape[0],
                3 if len(frame_inp.shape) == 3 else 1)
            self.CameraImage.Invalidate()
    
    # for color frames to CameraImage (delphivcl.Image object)
            frame = get_frame_image_from_somewhere()
            ext.update_image_from_bytes(
                self.CameraImage,
                frame_inp.tobytes(),
                frame_inp.shape[1],
                frame_inp.shape[0],
                3 if len(frame_inp.shape) == 3 else 1)
            self.CameraImage.Invalidate()

     

    • Thanks 1

  12. Seems this works but is hard to say OK, at moment, for me:

     

    {
            A                    B                                                      C
            +-------------------++------------------------------------------------------+
            | PyObject header   ||             TPyObject class                          |
            +----------+--------++-----------------+------------+----------+------------+
            |ob_refcnt |ob_type ||hidden Class Ptr |PythonType  |IsSubType |PythonAlloc |
            |integer   |pointer ||pointer          |TPythonType |Boolean   |Boolean     |
            |4 bytes   |4 bytes ||4 bytes          |4 bytes     |1 byte    |1 byte      |
            +----------+--------++-----------------+------------+----------+------------+
    
            ^                    ^
            |                    |
            ptr returned         ptr returned by Adjust
            by GetSelf
    
            - a Python object must start at A.
            - a Delphi class class must start at B
            - TPyObject.InstanceSize will return C-B
            - Sizeof(TPyObject) will return C-B
            - The total memory allocated for a TPyObject instance will be C-A,
              even if its InstanceSize is C-B.
            - When turning a Python object pointer into a Delphi instance pointer, PythonToDelphi
              will offset the pointer from A to B.
            - When turning a Delphi instance into a Python object pointer, GetSelf will offset
              Self from B to A.
            - Properties ob_refcnt and ob_type will call GetSelf to access their data.
    }
    function CncVisionUpdateImageFromBytesArray_Wrapper(pself, args: PPyObject): PPyObject; cdecl;
    var
      Image: TImage;
      Width: Integer;
      Height: Integer;
      Channels: Integer;
      Bytes: TByteArray;
      ImagePyObj: PPyObject;
    begin
      with GetPythonEngine do
      begin
        if PyArg_ParseTuple(args, 'Oiii:update_image_from_bytes', @ImagePyObj, @Width, @Height, @Channels) <> 0 then
        begin
          Image := TPyDelphiImage(TPyObject(PAnsiChar(ImagePyObj)+Sizeof(PyObject))).DelphiObject;
          Result := PyUnicodeFromString
          (
            Format('%s %d %d %d', [Image.Name, Width, Height, Channels])
          );
        end
        else
          Result := nil;
      end;
    end;

     


  13. Hi all,
     

    I'm using DelphiVCL + PyScripter to develop a Python windows application.I've created and "pyd" extension module in which perform some critical time operations or add support functions missing in DelphiVCL.

    - In python script I create an Image
    - now I would like to send a bytes array to cnc_vision_ext module (made with Delphi):

    import delphivcl
    import cnc_vision_ext as ext
    
    ....
    my_Image = delphivcl.Image(Form1)
    ext.update_image_from_bytes(my_image, 1024, 768, 3)

    Now in the wrapper CncVisionUpdateImageFromBytesArray_Wrapper I need to extract the TImage instance contained in PPyObject of args.
    I was not able to find the right way....

     

    { generic functions wrappers }
    
    function CncVisionUpdateImageFromBytesArray_Wrapper(pself, args: PPyObject): PPyObject; cdecl;
    var
      Image: TImage;
      Width: Integer;
      Height: Integer;
      Channels: Integer;
      Bytes: TByteArray;
      ImagePyObj: PPyObject;
    begin
      with GetPythonEngine do
      begin
        if PyArg_ParseTuple(args, 'Oiii:update_image_from_bytes', @ImagePyObj, @Width, @Height, @Channels) <> 0 then
        begin
          // ??? HOW TO GET BACK wrapped TImage from Image() object created with my_image = delphivcl.Image(owner) ???
          Image := TImage((PythonToDelphi(ImagePyObj) as TPyDelphiObject).DelphiObject);
          Result := PyUnicodeFromString
          (
            Format('%s %d %d %d', [Image.Name, Width, Height, Channels])
          );
        end
        else
          Result := nil;
      end;
    end;
    { TPyExtensionManager }
    
    procedure TPyExtensionManager.WrapperInitializationEvent(Sender: TObject);
    begin
      FWrapper.RegisterDelphiWrapper(TPyCncVisionVideoCapturePxc_Wrapper);
      FWrapper.RegisterFunction
      (
        PAnsiChar('update_image_from_bytes'),
        CncVisionUpdateImageFromBytesArray_Wrapper,
        PAnsiChar('update delphivcl Image object from bytes array with width, height & channels')
      );
      FWrapper.RegisterFunction
      (
        PAnsiChar('get_usb_camera_device_list'),
        CncVisionGetUSBCameraDeviceList_Wrapper,
        PAnsiChar('get list of available usb camera devices')
      );
    end;


    Thanks in advance for the suggestions....
    Best Regards
    Silverio


  14. TVideoCapture is managed as a record, not a class, with class members so do not requires a TVideoCapture.Create

    It is defined in videoio.inc.

     

    Never tried if this structure permits more than one instance.

    I've checked the demos and copied/pasted the minimal code to try it.

    My actual implementation is yet on Python + delphivcl + opencv.
    It's a very big project and I'm yet in the writting phase.

    When finished I will try to recreate my vision python framework directly in Delphi code + DelphiPythonVCL.

     


  15. In the dropbox link, I can't attach files bigger than 4.8Mb here,

    you can find a very simple Delphi Sydney 10.4.1 project where

    the OpenCV get frames from a USB camera (VideoCapture device 0)

    and with the trackbar, you can apply Canny filter thresholds 1 & 2.


    The required DLLs are in Win64\Debug & Release folders BUT

    can also be placed one time for always in Windows OS folders.
     

    Just my first test with Delphi-OpenCV.

    ONLY 64 bit applications are supported by Delphi-OpenCV class.

     

    https://www.dropbox.com/s/iqzlnfwttj20pnr/cnc_vision_2.7z?dl=0

    PS: I don't know if there are other MS dependencies because I've Microsoft Visual Studio installed
    and some DLL could be already available for MSVC packages.

    During run-in IDE these are modules loaded:

    Module Load: cnc_vision_2. No Debug Info. Base Address: $0000000000400000. Process cnc_vision_2.exe (14340)
    Module Load: ntdll.dll. No Debug Info. Base Address: $00007FFD371B0000. Process cnc_vision_2.exe (14340)
    Module Load: KERNEL32.dll. No Debug Info. Base Address: $00007FFD36870000. Process cnc_vision_2.exe (14340)
    Module Load: KERNELBASE.dll. No Debug Info. Base Address: $00007FFD34BA0000. Process cnc_vision_2.exe (14340)
    Module Load: WINSPOOL.DRV. No Debug Info. Base Address: $00007FFD2AF90000. Process cnc_vision_2.exe (14340)
    Module Load: SHELL32.dll. No Debug Info. Base Address: $00007FFD36930000. Process cnc_vision_2.exe (14340)
    Module Load: WINMM.dll. No Debug Info. Base Address: $00007FFD2B2A0000. Process cnc_vision_2.exe (14340)
    Module Load: msvcp_win.dll. No Debug Info. Base Address: $00007FFD34FC0000. Process cnc_vision_2.exe (14340)
    Module Load: COMCTL32.dll. No Debug Info. Base Address: $00007FFD26F50000. Process cnc_vision_2.exe (14340)
    Module Load: msvcrt.dll. No Debug Info. Base Address: $00007FFD35B40000. Process cnc_vision_2.exe (14340)
    Module Load: ucrtbase.dll. No Debug Info. Base Address: $00007FFD348D0000. Process cnc_vision_2.exe (14340)
    Module Load: msvcrt.dll. No Debug Info. Base Address: $0000000000D50000. Process cnc_vision_2.exe (14340)
    Module Unload: msvcrt.dll. Process cnc_vision_2.exe (14340)
    Module Load: msvcrt.dll. No Debug Info. Base Address: $0000000000DF0000. Process cnc_vision_2.exe (14340)
    Module Unload: msvcrt.dll. Process cnc_vision_2.exe (14340)
    Module Load: USER32.dll. No Debug Info. Base Address: $00007FFD35BE0000. Process cnc_vision_2.exe (14340)
    Module Load: GDI32.dll. No Debug Info. Base Address: $00007FFD370E0000. Process cnc_vision_2.exe (14340)
    Module Load: win32u.dll. No Debug Info. Base Address: $00007FFD34F20000. Process cnc_vision_2.exe (14340)
    Module Load: win32u.dll. No Debug Info. Base Address: $00000000001A0000. Process cnc_vision_2.exe (14340)
    Module Unload: win32u.dll. Process cnc_vision_2.exe (14340)
    Module Load: OLEAUT32.dll. No Debug Info. Base Address: $00007FFD35390000. Process cnc_vision_2.exe (14340)
    Module Load: gdi32full.dll. No Debug Info. Base Address: $00007FFD34A00000. Process cnc_vision_2.exe (14340)
    Module Load: VERSION.dll. No Debug Info. Base Address: $00007FFD2DB50000. Process cnc_vision_2.exe (14340)
    Module Load: combase.dll. No Debug Info. Base Address: $00007FFD36460000. Process cnc_vision_2.exe (14340)
    Module Load: RPCRT4.dll. No Debug Info. Base Address: $00007FFD35540000. Process cnc_vision_2.exe (14340)
    Module Load: ADVAPI32.dll. No Debug Info. Base Address: $00007FFD36140000. Process cnc_vision_2.exe (14340)
    Module Load: SECHOST.dll. No Debug Info. Base Address: $00007FFD36290000. Process cnc_vision_2.exe (14340)
    Module Load: ole32.dll. No Debug Info. Base Address: $00007FFD36330000. Process cnc_vision_2.exe (14340)
    Module Load: NETAPI32.dll. No Debug Info. Base Address: $00007FFD21B00000. Process cnc_vision_2.exe (14340)
    Module Load: netutils.dll. No Debug Info. Base Address: $00007FFD33E40000. Process cnc_vision_2.exe (14340)
    Module Load: IMM32.dll. No Debug Info. Base Address: $00007FFD356A0000. Process cnc_vision_2.exe (14340)
    Module Load: MSCTF.dll. No Debug Info. Base Address: $00007FFD35210000. Process cnc_vision_2.exe (14340)
    Module Load: UxTheme.dll. No Debug Info. Base Address: $00007FFD32280000. Process cnc_vision_2.exe (14340)
    Module Load: AppCore.dll. No Debug Info. Base Address: $00007FFD32800000. Process cnc_vision_2.exe (14340)
    Module Load: bcryptPrimitives.dll. No Debug Info. Base Address: $00007FFD34B10000. Process cnc_vision_2.exe (14340)
    Module Load: WTSAPI32.dll. No Debug Info. Base Address: $00007FFD2F7C0000. Process cnc_vision_2.exe (14340)
    Module Load: WINSTA.dll. No Debug Info. Base Address: $00007FFD33640000. Process cnc_vision_2.exe (14340)
    Module Load: opencv_world455.dll. No Debug Info. Base Address: $00007FFCE7660000. Process cnc_vision_2.exe (14340)
    Module Load: WS2_32.dll. No Debug Info. Base Address: $00007FFD35D80000. Process cnc_vision_2.exe (14340)
    Module Load: COMDLG32.dll. No Debug Info. Base Address: $00007FFD35460000. Process cnc_vision_2.exe (14340)
    Module Load: SHCORE.dll. No Debug Info. Base Address: $00007FFD367C0000. Process cnc_vision_2.exe (14340)
    Module Load: SHLWAPI.dll. No Debug Info. Base Address: $00007FFD35330000. Process cnc_vision_2.exe (14340)
    Module Load: VCRUNTIME140.dll. No Debug Info. Base Address: $00007FFD21D40000. Process cnc_vision_2.exe (14340)
    Module Load: CONCRT140.dll. No Debug Info. Base Address: $00007FFD231C0000. Process cnc_vision_2.exe (14340)
    Module Load: MSVCP140.dll. No Debug Info. Base Address: $00007FFD14770000. Process cnc_vision_2.exe (14340)
    Module Load: VCRUNTIME140_1.dll. No Debug Info. Base Address: $0000000000830000. Process cnc_vision_2.exe (14340)
    Module Unload: VCRUNTIME140_1.dll. Process cnc_vision_2.exe (14340)
    Module Load: VCRUNTIME140_1.dll. No Debug Info. Base Address: $00007FFD2F5D0000. Process cnc_vision_2.exe (14340)
    Module Load: opencv_videoio_msmf455_64.dll. No Debug Info. Base Address: $00007FFD22EB0000. Process cnc_vision_2.exe (14340)
    Module Load: MF.dll. No Debug Info. Base Address: $00007FFD20830000. Process cnc_vision_2.exe (14340)
    Module Load: MFReadWrite.dll. No Debug Info. Base Address: $00007FFD1F890000. Process cnc_vision_2.exe (14340)
    Module Load: dxgi.dll. No Debug Info. Base Address: $00007FFD33210000. Process cnc_vision_2.exe (14340)
    Module Load: MFPlat.DLL. No Debug Info. Base Address: $00007FFD060C0000. Process cnc_vision_2.exe (14340)
    Module Load: CFGMGR32.dll. No Debug Info. Base Address: $00007FFD351C0000. Process cnc_vision_2.exe (14340)
    Module Load: d3d11.dll. No Debug Info. Base Address: $00007FFD300B0000. Process cnc_vision_2.exe (14340)
    Module Load: MFCORE.dll. No Debug Info. Base Address: $00007FFCE71D0000. Process cnc_vision_2.exe (14340)
    Module Load: CRYPT32.dll. No Debug Info. Base Address: $00007FFD35060000. Process cnc_vision_2.exe (14340)
    Module Load: bcrypt.dll. No Debug Info. Base Address: $00007FFD349D0000. Process cnc_vision_2.exe (14340)
    Module Load: POWRPROF.dll. No Debug Info. Base Address: $00007FFD33EA0000. Process cnc_vision_2.exe (14340)
    Module Load: ksuser.dll. No Debug Info. Base Address: $00007FFD32120000. Process cnc_vision_2.exe (14340)
    Module Load: CRYPTBASE.dll. No Debug Info. Base Address: $00007FFD342A0000. Process cnc_vision_2.exe (14340)
    Module Load: RTWorkQ.DLL. No Debug Info. Base Address: $00007FFD1D060000. Process cnc_vision_2.exe (14340)
    Module Load: UMPDC.dll. No Debug Info. Base Address: $00007FFD33D10000. Process cnc_vision_2.exe (14340)
    Module Load: CLBCatQ.DLL. No Debug Info. Base Address: $00007FFD36010000. Process cnc_vision_2.exe (14340)
    Module Load: DEVENUM.DLL. No Debug Info. Base Address: $00007FFD252A0000. Process cnc_vision_2.exe (14340)
    Module Load: SETUPAPI.dll. No Debug Info. Base Address: $00007FFD356D0000. Process cnc_vision_2.exe (14340)
    Module Load: NTMARTA.dll. No Debug Info. Base Address: $00007FFD335A0000. Process cnc_vision_2.exe (14340)
    Module Load: DEVOBJ.dll. No Debug Info. Base Address: $00007FFD34670000. Process cnc_vision_2.exe (14340)
    Module Load: WINTRUST.dll. No Debug Info. Base Address: $00007FFD34F50000. Process cnc_vision_2.exe (14340)
    Module Load: MSASN1.dll. No Debug Info. Base Address: $00007FFD344B0000. Process cnc_vision_2.exe (14340)
    Module Load: msdmo.dll. No Debug Info. Base Address: $00007FFD2C5B0000. Process cnc_vision_2.exe (14340)
    Module Load: QCap.dll. No Debug Info. Base Address: $00007FFD16770000. Process cnc_vision_2.exe (14340)
    Module Load: QUARTZ.dll. No Debug Info. Base Address: $00007FFD145A0000. Process cnc_vision_2.exe (14340)
    Module Load: Windows.Storage.dll. No Debug Info. Base Address: $00007FFD32A00000. Process cnc_vision_2.exe (14340)
    Module Load: Wldp.dll. No Debug Info. Base Address: $00007FFD34330000. Process cnc_vision_2.exe (14340)
    Module Load: Source.dll. No Debug Info. Base Address: $00007FFD166B0000. Process cnc_vision_2.exe (14340)
    Module Load: ATL.DLL. No Debug Info. Base Address: $00007FFD24C60000. Process cnc_vision_2.exe (14340)
    Module Load: MFSENSORGROUP.dll. No Debug Info. Base Address: $00007FFD15110000. Process cnc_vision_2.exe (14340)
    Module Load: ksproxy.ax. No Debug Info. Base Address: $00007FFD14550000. Process cnc_vision_2.exe (14340)
    Module Load: d3d9.dll. No Debug Info. Base Address: $00007FFD2CF10000. Process cnc_vision_2.exe (14340)
    Module Load: dwmapi.dll. No Debug Info. Base Address: $00007FFD32510000. Process cnc_vision_2.exe (14340)
    Module Load: policymanager.dll. No Debug Info. Base Address: $00007FFD2F4A0000. Process cnc_vision_2.exe (14340)
    Module Load: msvcp110_win.dll. No Debug Info. Base Address: $00007FFD33A30000. Process cnc_vision_2.exe (14340)
    Module Load: vidcap.dll. No Debug Info. Base Address: $00007FFD24C50000. Process cnc_vision_2.exe (14340)
    Module Load: kswdmcap.dll. No Debug Info. Base Address: $00007FFD23190000. Process cnc_vision_2.exe (14340)
    Module Load: MFC42.dll. No Debug Info. Base Address: $00007FFCF43E0000. Process cnc_vision_2.exe (14340)
    Module Load: QEdit.dll. No Debug Info. Base Address: $00007FFD144A0000. Process cnc_vision_2.exe (14340)
    Module Load: gdiplus.dll. No Debug Info. Base Address: $00007FFD2B040000. Process cnc_vision_2.exe (14340)
    Module Load: MSVFW32.dll. No Debug Info. Base Address: $00007FFD1F210000. Process cnc_vision_2.exe (14340)
    Module Load: DDRAW.dll. No Debug Info. Base Address: $00007FFCFC5D0000. Process cnc_vision_2.exe (14340)
    Module Load: DCIMAN32.dll. No Debug Info. Base Address: $00007FFD238F0000. Process cnc_vision_2.exe (14340)
    Module Load: nvldumdx.dll. No Debug Info. Base Address: $00007FFD2ADB0000. Process cnc_vision_2.exe (14340)
    Module Load: imagehlp.dll. No Debug Info. Base Address: $00007FFD35670000. Process cnc_vision_2.exe (14340)
    Module Load: CRYPTSP.dll. No Debug Info. Base Address: $00007FFD34280000. Process cnc_vision_2.exe (14340)
    Module Load: RSAENH.dll. No Debug Info. Base Address: $00007FFD339A0000. Process cnc_vision_2.exe (14340)
    Module Load: NVD3DUMX.dll. No Debug Info. Base Address: $00007FFCC5A50000. Process cnc_vision_2.exe (14340)
    Module Load: nvspcap64.dll. No Debug Info. Base Address: $00007FFD09D00000. Process cnc_vision_2.exe (14340)
    Module Load: profapi.dll. No Debug Info. Base Address: $00007FFD34810000. Process cnc_vision_2.exe (14340)
    Module Load: NvCameraWhitelisting64.dll. No Debug Info. Base Address: $00007FFD14410000. Process cnc_vision_2.exe (14340)
    Module Unload: NvCameraWhitelisting64.dll. Process cnc_vision_2.exe (14340)
    Module Load: dxcore.dll. No Debug Info. Base Address: $00007FFD2B680000. Process cnc_vision_2.exe (14340)
    Module Unload: NVD3DUMX.dll. Process cnc_vision_2.exe (14340)
    Module Unload: nvldumdx.dll. Process cnc_vision_2.exe (14340)
    Module Load: nvldumdx.dll. No Debug Info. Base Address: $00007FFD2ADB0000. Process cnc_vision_2.exe (14340)
    Module Load: NVD3DUMX.dll. No Debug Info. Base Address: $00007FFCC5A50000. Process cnc_vision_2.exe (14340)
    Module Load: NvCameraWhitelisting64.dll. No Debug Info. Base Address: $00007FFD14410000. Process cnc_vision_2.exe (14340)
    Module Unload: NvCameraWhitelisting64.dll. Process cnc_vision_2.exe (14340)
    Module Unload: NVD3DUMX.dll. Process cnc_vision_2.exe (14340)
    Module Unload: nvldumdx.dll. Process cnc_vision_2.exe (14340)
    Module Load: nvldumdx.dll. No Debug Info. Base Address: $00007FFD2ADB0000. Process cnc_vision_2.exe (14340)
    Module Load: NVD3DUMX.dll. No Debug Info. Base Address: $00007FFCC5A50000. Process cnc_vision_2.exe (14340)
    Module Load: NvCameraWhitelisting64.dll. No Debug Info. Base Address: $00007FFD14410000. Process cnc_vision_2.exe (14340)
    Module Unload: NvCameraWhitelisting64.dll. Process cnc_vision_2.exe (14340)
    Module Unload: NVD3DUMX.dll. Process cnc_vision_2.exe (14340)
    Module Unload: nvldumdx.dll. Process cnc_vision_2.exe (14340)
    Module Load: WINMMBASE.dll. No Debug Info. Base Address: $00007FFD22CE0000. Process cnc_vision_2.exe (14340)
    Module Load: MSYUV.dll. No Debug Info. Base Address: $00007FFD238E0000. Process cnc_vision_2.exe (14340)
    Module Unload: MSYUV.dll. Process cnc_vision_2.exe (14340)
    Module Load: TextInputFramework.dll. No Debug Info. Base Address: $00007FFD2B430000. Process cnc_vision_2.exe (14340)
    Module Load: CoreUIComponents.dll. No Debug Info. Base Address: $00007FFD31640000. Process cnc_vision_2.exe (14340)
    Module Load: CoreMessaging.dll. No Debug Info. Base Address: $00007FFD319A0000. Process cnc_vision_2.exe (14340)
    Module Load: WinTypes.dll. No Debug Info. Base Address: $0000000007A20000. Process cnc_vision_2.exe (14340)
    Module Load: WinTypes.dll. No Debug Info. Base Address: $0000000007B80000. Process cnc_vision_2.exe (14340)
    Module Unload: WinTypes.dll. Process cnc_vision_2.exe (14340)
    Module Unload: WinTypes.dll. Process cnc_vision_2.exe (14340)
    Module Load: WinTypes.dll. No Debug Info. Base Address: $00007FFD30ED0000. Process cnc_vision_2.exe (14340)
    Module Load: OLEACC.dll. No Debug Info. Base Address: $00007FFD2AF20000. Process cnc_vision_2.exe (14340)

     


  16. IMO, you have two solutions:

    a) Use the new, but experimental, Delphi-OpenCV-Class (opencv 4.5.5) for Delphi 10.4 & 11.0 package:
    https://github.com/Laex/Delphi-OpenCV-Class

    Laex is doing a very very good job to import opencv 4.5.5 C++ based DLLs to Delphi.
    I don't know how much of opencv is already ported but I've cloned the project only yesterday and tried a basic Canny function:
    image.thumb.png.509bf80eb1ea69d75387a8e89e5b6c66.png
    Works very very fastly, with native Delphi (in the snapshot I've got frames from a USB camera and applied canny filter).
    During run-in IDE environment there are some delays (also in Release) because uses debug versions of DLLs,
    but when you close the IDE and start the program from Explorer BOOM is a rocket.
    I will move, only in the next future, my Python delphivcl application to be a native Delphi set of classes.
    This ONLY to fully integrate into Forms of native VCL program.

    b) To back processed image to Delphi just pass the opencv NumPy array content which can be o monodimensional array

    for gray images or a tridimensional array for images with RGB.
    Pass also Width and Height info.
    Then you have only to use a Bitmap, and related canvas HDC Handle with the same width and height and use

    DIB windows functions to recreate bitmap image from a byte array.

    This is an extract of yesterday's experiments for upon Image.
    TMat is the Laex generics implementation of OpenCV mat so array of bytes of image:

     

    function MatDraw(DC: HDC; Image: TMat; const Rect: TRect; const Stretch: Boolean = True): Boolean;
    type
      pCOLORREF         = ^COLORREF;
      pBITMAPINFOHEADER = ^BITMAPINFOHEADER;
    var
      // isrgb: Boolean;
      IsGray: Boolean;
      buf: array [1 .. SizeOf(BITMAPINFOHEADER) + SizeOf(RGBQUAD) * 256] of byte;
      dibhdr: pBITMAPINFOHEADER;
      _dibhdr: TBitmapInfo ABSOLUTE buf;
      _rgb: pCOLORREF;
      i: Integer;
      iResult: Integer;
    begin
      if Image.empty then
        Exit(False);
    
      // isrgb := ('R' = upcase(img^.colorModel[0])) and ('G' = upcase(img^.colorModel[1])) and ('B' = upcase(img^.colorModel[2]));
      // isgray := 'G' = upcase(img^.colorModel[0]);
      IsGray := Image.channels = 1;
      // if (not isgray) and (not isrgb) then
      // Exit(false);
      // if (1 = img^.nChannels) and (not isgray) then
      // Exit(false);
    
      dibhdr := pBITMAPINFOHEADER(@buf);
      _rgb := pCOLORREF(Integer(dibhdr) + SizeOf(BITMAPINFOHEADER));
    
      if (IsGray) then
        for i := 0 to 255 do
          _rgb[i] := rgb(i, i, i);
    
      dibhdr^.biSize := SizeOf(BITMAPINFOHEADER);
      dibhdr^.biWidth := Image.cols;
      // Check origin for display
      // if img^.Origin = 0 then
      dibhdr^.biHeight := -Image.rows;
      // else
      // dibhdr^.biHeight := img^.Height;
    
      dibhdr^.biPlanes := 1;
      dibhdr^.biBitCount := 8 * Image.channels;
      dibhdr^.biCompression := BI_RGB;
      dibhdr^.biSizeImage := 0; // img^.imageSize;
      dibhdr^.biXPelsPerMeter := 0;
      dibhdr^.biYPelsPerMeter := 0;
      dibhdr^.biClrUsed := 0;
      dibhdr^.biClrImportant := 0;
    
      if Stretch then
      begin
        SetStretchBltMode(DC, COLORONCOLOR);
        SetMapMode(DC, MM_TEXT);
        // Stretch the image to fit the rectangle
        iResult := StretchDIBits(DC, Rect.Left, Rect.Top, Rect.Width, Rect.Height, 0, 0, Image.cols, Image.rows, Image.Data, _dibhdr, DIB_RGB_COLORS, SRCCOPY);
        Result := (iResult > 0); // and (iResult <> GDI_ERROR);
      end
      else
      begin
        // Draw without scaling
        iResult := SetDIBitsToDevice(DC, Rect.Left, Rect.Top, Image.cols, Image.rows, 0, 0, 0, Image.rows, Image.Data, _dibhdr, DIB_RGB_COLORS);
        Result := (iResult > 0); // and (iResult <> GDI_ERROR);
      end;
    end;

    Code went from Laex library sources so try to read them.


     


  17. Hi all.

    There is a way to detect if compile phase is in Delphi 10.4.1 or 10.4.2 ?
    At Embarcadero documentation they notice only VER340 and compiler 34.0 for either:

    https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Compiler_Versions

     

    but VCL from 10.4.1 and 10.4.2 is different and in 10.4.1 NumberBox is not available.

     

    This block 10.4.1 users to compile PythonVCL design package or force them, at any pull, to manually remove

    any reference to NumberBox where present in check:  {$IFDEF DELPHI10_4_OR_HIGHER}  ... {$ENDIF DELPHI10_4_OR_HIGHER}.


  18. On 2/1/2022 at 7:32 PM, pyscripter said:

    Demo 29 might be of help.

    Initially failed to understand how in Demo29 the use of PIL allowed to place an image in a TImage object.
    But slowly I succeeded:

            rgb_im = PIL_Image.fromarray(np.uint8(frame)).convert('RGB')
            self.imgFrameI.Picture.Bitmap.SetSize(rgb_im.width, rgb_im.height)
            dib = PIL_ImageWin.Dib(rgb_im)
            dib.expose(self.imgFrameI.Picture.Bitmap.Canvas.Handle)
            self.imgFrameI.Repaint()
    
            rgb_im = PIL_Image.fromarray(np.uint8(frame_out)).convert('RGB')
            self.imgFrameO.Picture.Bitmap.SetSize(rgb_im.width, rgb_im.height)
            dib = PIL_ImageWin.Dib(rgb_im)
            dib.expose(self.imgFrameO.Picture.Bitmap.Canvas.Handle)
            self.imgFrameO.Repaint()

    Now perfectly works and from 30FPS of PySimpleGUI version, I've reached 89 stable FPS with delphivcl version.
    AMAZING how fast delphi vcl is"

     

    • Thanks 2

  19. Thanks for the info.

    I remember your suggestion to report the request in github in the issue department.

    I haven't done it yet because first I would like to try to do something myself, both because I started studying P4D sources and making some modules, and to actually understand how many things are necessary to cover my needs and that they are then useful to everyone. .

    The project I'm converting from PySimpleGUI is a real-time object recognition system that uses OpenCV and other AI technologies with complicated UIs that need to be really fast, hence the choice of switching to DelphiVCL and / or DelphiFMX.

    Thanks again for the support.

×