Jump to content

OpenCV v4 UDF


smbape
 Share

Recommended Posts

Thank you. It was a stupid mistake. 😀

24 minutes ago, smbape said:

You should also add the following condition after _cveBoundingRectangle

Yeah, I already added. But thanks anyway.
Does everything work now? 
It was long and interesting. 😉
I work with two more files.
If you weren't tired of me, will I ask questions later?

Link to comment
Share on other sites

25 minutes ago, Lion66 said:

Does everything work now? 

You tell me.

26 minutes ago, Lion66 said:

If you weren't tired of me, will I ask questions later?

I doubt I will ever be tired of someone interested in my project.
Feel free to ask any question

Link to comment
Share on other sites

Hi @smbape

I started a new example: draw a contour.
Depending on the Threshold, or the contour is not displayed, or the script fails.

Thanks in advance for your help.

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_UseX64=y
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
Opt("MustDeclareVars", 1)

#include "emgucv-autoit-bindings\cve_extra.au3"
_OpenCV_DLLOpen("libemgucv-windesktop-4.5.3.4721\libs\x64\cvextern.dll")

Local $img = _cveImreadAndCheck("Pic\king_club.JPG", $CV_IMREAD_COLOR)
Local $gray = _cveMatCreate()
_cveCvtColorMat($img, $gray, $CV_COLOR_BGR2GRAY)

Local $Gaus = _cveMatCreate()
_cveGaussianBlurMat($gray, $Gaus, _cvSize(3, 3), 0, 0)
_cveThresholdMat($Gaus, $Gaus, 180, 255, $CV_THRESH_BINARY)

_cveImshowMat("", $Gaus)
_cveWaitKey()
_cveDestroyAllWindows()

Local $contoursVect = _VectorOfVectorOfPointCreate()
Local $contours = _cveOutputArrayFromVectorOfVectorOfPoint($contoursVect)

Local $hierarchyMat = _cveMatCreate()
Local $hierarchy = _cveOutputArrayFromMat($hierarchyMat)

Local $io_arr_Gaus = _cveInputOutputArrayFromMat($Gaus)
_cveFindContours($io_arr_Gaus, $contours, $hierarchy, $CV_RETR_EXTERNAL, $CV_CHAIN_APPROX_SIMPLE)   ; $CV_RETR_LIST, $CV_RETR_EXTERNAL, $CV_RETR_TREE

Local $cnts = _VectorOfVectorOfPointGetSize($contoursVect)
ConsoleWrite("Found " & $cnts & " contours" & @CRLF & @CRLF)

_cveDrawContoursMat($img, $contours, -1, _cvRGB(0, 255, 0), 3)  ; -1 for all contours

_cveImshowMat("", $img)
_cveWaitKey()
_cveDestroyAllWindows()
_Opencv_DLLClose()

 

Link to comment
Share on other sites

Hi @Lion66

Following your advice, I came up with a way to pass the type to the opencv functions.
It simplifies the call of some function.
Grab the new UDF (there is no breaking changes with the previous version) at emgucv-autoit-bindings-v1.0.0-rc.1.zip

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_UseX64=y
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
Opt("MustDeclareVars", 1)

#include "emgucv-autoit-bindings\cve_extra.au3"
_OpenCV_DLLOpen("libemgucv-windesktop-4.5.3.4721\libs\x64\cvextern.dll")

Local $img = _cveImreadAndCheck("Pic\king_club.JPG", $CV_IMREAD_COLOR)
Local $gray = _cveMatCreate()
_cveCvtColorMat($img, $gray, $CV_COLOR_BGR2GRAY)

Local $Gaus = _cveMatCreate()
_cveGaussianBlurMat($gray, $Gaus, _cvSize(3, 3), 0, 0)
_cveThresholdMat($Gaus, $Gaus, 180, 255, $CV_THRESH_BINARY)

_cveImshowMat("", $Gaus)
_cveWaitKey()
_cveDestroyAllWindows()

Local $contours = _VectorOfVectorOfPointCreate()
Local $hierarchy = _cveMatCreate()

_cveFindContoursTyped("Mat", $Gaus, "VectorOfVectorOfPoint", $contours, "Mat", $hierarchy, $CV_RETR_EXTERNAL, $CV_CHAIN_APPROX_SIMPLE)   ; $CV_RETR_LIST, $CV_RETR_EXTERNAL, $CV_RETR_TREE

Local $cnts = _VectorOfVectorOfPointGetSize($contours)
ConsoleWrite("Found " & $cnts & " contours" & @CRLF & @CRLF)

_cveDrawContoursTyped("Mat", $img, "VectorOfVectorOfPoint", $contours, -1, _cvRGB(0, 255, 0), 3)  ; -1 for all contours

_cveImshowMat("", $img)
_cveWaitKey()
_cveDestroyAllWindows()
_Opencv_DLLClose()

 

Link to comment
Share on other sites

3 hours ago, smbape said:

Can you please have a look at How to translate python/c++ code to the UDF and tell me if it eases things for you?

I've read this before, but it's not my level.

If we drop the fact that the number of functions is incredibly large, as I would like to see these functions.

Example:

Local $contours = _VectorOfVectorOfPointCreate()
Local $hierarchy = _cveMatCreate()

_cveFindContoursTyped("Mat", $Gaus, "VectorOfVectorOfPoint", $contours, "Mat", $hierarchy, $CV_RETR_EXTERNAL, $CV_CHAIN_APPROX_SIMPLE)

The first two lines are service lines. They must be inside the UDF. Why would the user know this?

Maybe I'm wrong because I only see a specific case.

What about this parameters: "Mat", "VectorOfVectorOfPoint", $CV_CHAIN_APPROX_SIMPLE ?

Are the following parameters permanent? If so, why would the user list them? Let them be inside the UDF.

If these parameters can change, then in the description of the function I would like to see the enumeration and description.

And then instead of three lines of code, the user writes:

_cveFindContoursTyped($Gaus, $CV_RETR_EXTERNAL)

 

Thank you, the example works. Now I will try to supplement with a convex contour.

And I read that the object should be white on a black background. But that's not always the case.

How do I recognize and invert the image after GaussianBlur and before find contour?

 

Edited by Lion66
Link to comment
Share on other sites

57 minutes ago, Lion66 said:

The first two lines are service lines. They must be inside the UDF. Why would the user know this?

There are too much functions in opencv. Remember, this UDF is generated from the opencv c++ source files.
Going through all of them to make the necessary adjustements like it is done in python will be too much time consumming.
The fastest way is to use the c++ functions as they are by using a generator.
*Array are intermediate types. Meaning they are not used directly.
As a conclusion, a program cannot create an opencv binding that is user friendly.

No matter what typing purists say, typing is annoying.
As a human, type does not exist. Sure we can't sum potatoes and tomatoes when asking the number of potatoes, but we can when asking how many food items we have.
Type were made because there was no other way to make a compute understand what we, humans, want.
It also happens to make programs run way faster.

The annoyance of typing is one of the reason python is usally used to do alorigthm.
And opencv python made a good job making it easy to use.
With lot of time, the same could be done with AutoIt. But it will be slower that python, mostly because of loops to convert certain types into AutoIt Array.
Taking your SearchContureDraw-v5.au3 as an example, it takes 2 seconds to finish the top loop.
For one image it may be fine, but if you have thousands of images to compute, it is unusable.
As a conclusion, due to time consumption and the slowness of AutoIt, I don't think it worth the investment.
I suppose the AutoIt dev will not make things better because:

  • It involves choosing/creating/using a compiler
  • There is an alternative which are dlls.
    It is unfortunate for those without knowledge, but still an alternative.

If AutoIt was open source, may be there will have been someone willing to do it.

57 minutes ago, Lion66 said:

What about this parameters: "Mat", "VectorOfVectorOfPoint", $CV_CHAIN_APPROX_SIMPLE ?

Are the following parameters permanent? If so, why would the user list them? Let them be inside the UDF.

If these parameters can change, then in the description of the function I would like to see the enumeration and description.

I may not have expressed myself correctly, this is what I said about it

Types which are *Array like cv::_InputArray, are harder to translate because there is no automatic convertion in AutoIt like in c++.
For this reason, for functions which take those type of parameters, there will be 2 additionnal functions.
_cveFooTyped where you specified the type of the Array parameter and
_cveFooMat where you specified the type of all the Array parameter are Mat.

By *Array I mean types that ends with "Array". Only them need a conversion.
This is a consequence of letting the c++ functions as they are.
image is an InputArray, contours is an OutputArrayOfArrays and hierarchy is an OutputArray. Only them need a conversion.

Regarding $CV_CHAIN_APPROX_SIMPLE it is the translation of the python code. The documentation does not say that it is optional.

57 minutes ago, Lion66 said:

How do I recognize and invert the image before find contour?

Unfortunatly I don't know.
While I think you will find results for inverting colors, I don't understand what you mean by recognizing the image.
Can you elaborate please?

Edited by smbape
Link to comment
Share on other sites

It was my view, and I understand that because of the large number of functions, should not expect it! Forget it.

For me, acceptable speed is not particularly important, I do it as a hobby. It's like a puzzle to collect. Otherwise, I would probably start studying python.

Sometimes, due to the peculiarities of the image, after the Gaussian blur, we will receive an object of black color and a background of white color.

This will be a problem with contour recognition.

There must be a simple solution that makes black white and white black.

True, I have not yet sought a solution 😉

Link to comment
Share on other sites

I try to draw a convex frame to the previous script. The script crashes.

Local $hull = _VectorOfPointCreate()
Local $o_arr_hull = _cveOutputArrayFromVectorOfPoint($hull)

;_cveConvexHullTyped($typeOfPoints, $points, $typeOfHull, $hull, $clockwise = false, $returnPoints = true)
_cveConvexHullTyped("Mat", $contours, "VectorOfVectorOfPoint", $o_arr_hull)

;Func _cveDrawContoursTyped($typeOfImage, $image, $typeOfContours, $contours, $contourIdx, $color, ...
_cveDrawContoursTyped("Mat", $img, "VectorOfVectorOfPoint", $contours, -1, _cvRGB(0, 255, 0), 3)

 

Link to comment
Share on other sites

$contours is a VectorOfVectorOfPoint. Therefore, if used as a Mat, like shown in _cveConvexHullTyped, it will crash.
convexHull cannot but applied on a VectorOfVectorOfPoint. It can only be applied on a VectorOfPoint or on a Mat.

Local $hull = _VectorOfVectorOfPointCreate()
Local $hull_i = _VectorOfPointCreate()
Local $tVectorPointPtr = DllStructCreate("ptr value")
For $i = 0 To _VectorOfVectorOfPointGetSize($contours) - 1
    _VectorOfVectorOfPointGetItemPtr($contours, $i, $tVectorPointPtr)

    ;_cveConvexHullTyped($typeOfPoints, $points, $typeOfHull, $hull, $clockwise = false, $returnPoints = true)
    _cveConvexHullTyped("VectorOfPoint", $tVectorPointPtr.value, "VectorOfPoint", $hull_i)
    _VectorOfVectorOfPointPush($hull, $hull_i)
Next

;Func _cveDrawContoursTyped($typeOfImage, $image, $typeOfContours, $contours, $contourIdx, $color, ...
; _cveDrawContoursTyped("Mat", $img, "VectorOfVectorOfPoint", $contours, -1, _cvRGB(0, 255, 0), 3)
_cveDrawContoursTyped("Mat", $img, "VectorOfVectorOfPoint", $hull, -1, _cvRGB(255, 0, 0), 3)

 

Link to comment
Share on other sites

On 8/28/2021 at 3:34 PM, Lion66 said:

There must be a simple solution that makes black white and white black.

The solution will be to use $CV_THRESH_BINARY_INV instead of $CV_THRESH_BINARY (when it is needed).

_cveThresholdMat($Gaus, $Gaus, 120, 255, $CV_THRESH_BINARY_INV) ; $CV_THRESH_BINARY_INV, $CV_THRESH_BINARY

I began to understand functions little by little with TYPED at the end.

I even managed to filtering by area size and find the center for the convex contour.

;Find and draw Convex contour
Local $hull = _VectorOfVectorOfPointCreate()
Local $hull_i = _VectorOfPointCreate()
Local $tVectorPointPtr = DllStructCreate("ptr value")
For $i = 0 To _VectorOfVectorOfPointGetSize($contours) - 1
    _VectorOfVectorOfPointGetItemPtr($contours, $i, $tVectorPointPtr)

    ;_cveConvexHullTyped($typeOfPoints, $points, $typeOfHull, $hull, $clockwise = false, $returnPoints = true)
    _cveConvexHullTyped("VectorOfPoint", $tVectorPointPtr.value, "VectorOfPoint", $hull_i)

    ;Func _cveContourAreaTyped($typeOfContour, $contour, $oriented = false)
    Local $Area2 = _cveContourAreaTyped("VectorOfPoint", $tVectorPointPtr.value)
    ConsoleWrite($Area2 & @CRLF)
    If $Area2 < 1000 Then
        ContinueLoop
    Else
        _VectorOfVectorOfPointPush($hull, $hull_i)
    EndIf
Next

;Func _cveDrawContoursTyped($typeOfImage, $image, $typeOfContours, $contours, $contourIdx, $color, ...
_cveDrawContoursTyped("Mat", $img, "VectorOfVectorOfPoint", $hull, -1, _cvRGB(255, 0, 0), 3)

;Draw circle in center of Convex
Local $moments = _cveMomentsCreate()
;Func _cveMomentsTyped($typeOfArr, $arr, $binaryImage, $moments)
_cveMomentsTyped("VectorOfPoint", $hull_i, $Gaus, $moments)
Local $cx = _cveMomentsGetM10($moments) / _cveMomentsGetM00($moments)
Local $cy = _cveMomentsGetM01($moments) / _cveMomentsGetM00($moments)
_cveCircleMat($img, _cvPoint($cx, $cy), 25, _cvScalar(255, 0, 255), $CV_FILLED)
_cveMomentsRelease($moments)

But I could not cope with the concave contour.
After filter by area size, I draw a large contour correctly, but the structure remains unchanged.
And the script falls when calculating the moment.

;Find and draw Concave contour
Local $contours = _VectorOfVectorOfPointCreate()
Local $hierarchy = _cveMatCreate()

;Func _cveFindContoursTyped($typeOfImage, $image, $typeOfContours, $contours, $typeOfHierarchy, $hierarchy, $mode, $method,
_cveFindContoursTyped("Mat", $Gaus, "VectorOfVectorOfPoint", $contours, "Mat", $hierarchy, $CV_RETR_EXTERNAL, $CV_CHAIN_APPROX_SIMPLE)   ; $CV_RETR_LIST, $CV_RETR_EXTERNAL, $CV_RETR_TREE

Local $cnts = _VectorOfVectorOfPointGetSize($contours)
ConsoleWrite("Found " & $cnts & " contours" & @CRLF & @CRLF)

Local $tVectorPointPtr2 = DllStructCreate("ptr value")
For $i = 0 To _VectorOfVectorOfPointGetSize($contours) - 1
    _VectorOfVectorOfPointGetItemPtr($contours, $i, $tVectorPointPtr2)

    ;Func _cveContourAreaTyped($typeOfContour, $contour, $oriented = false)
    Local $Area1 = _cveContourAreaTyped("VectorOfPoint", $tVectorPointPtr2.value)
    ConsoleWrite($Area1 & @CRLF)

    If $Area1 < 1000 Then
        ContinueLoop
    Else
        ;Func _cveDrawContoursTyped($typeOfImage, $image, $typeOfContours, $contours, $contourIdx, $color
        _cveDrawContoursTyped("Mat", $img, "VectorOfVectorOfPoint", $contours, $i, _cvRGB(0, 255, 0), 3)  ; -1 for all contours
    EndIf
Next

;Draw circle in center of Concave
Local $moments2 = _cveMomentsCreate()
;Func _cveMomentsTyped($typeOfArr, $arr, $binaryImage, $moments)
_cveMomentsTyped("VectorOfVectorOfPoint", $contours, $Gaus, $moments2)

Local $cx1 = _cveMomentsGetM10($moments2) / _cveMomentsGetM00($moments2)
Local $cy1 = _cveMomentsGetM01($moments2) / _cveMomentsGetM00($moments2)
_cveCircleMat($img, _cvPoint($cx1, $cy1), 25, _cvScalar(0, 255, 255), $CV_FILLED)
_cveMomentsRelease($moments2)

 

Link to comment
Share on other sites

1 hour ago, Lion66 said:

I began to understand functions little by little with TYPED at the end.

I am really happy to read that.

Moments cannot be calculated on VectorOfVectorOfPoint.
It can be calculated on VectorOfPoint.
That means it should be done inside the loop.
More over, the binaryImage is a bool, meaning its value is either True or False.
The default value is False.

1 hour ago, Lion66 said:

_cveMomentsTyped("VectorOfPoint", $hull_i, $Gaus, $moments)

Works because $hull_i is a VectorOfPoint and $Gaus is a pointer treated as an integer, therefore equivalent to True.
By the way, keep in mind that $hull_i is the last calculated convexHull.
I am not sure this is what you want. It think it should be done inside the loop, as well as the center caculation.
It should be corrected to

_cveMomentsTyped("VectorOfPoint", $hull_i, False, $moments)

 

1 hour ago, Lion66 said:

_cveMomentsTyped("VectorOfVectorOfPoint", $contours, $Gaus, $moments2)

Doesn't work because $contours is a VectorOfVectorOfPoint.
It should be inside the loop in this form.

_cveMomentsTyped("VectorOfPoint", $tVectorPointPtr2.value, False, $moments2)

 

Link to comment
Share on other sites

1 hour ago, smbape said:

It think it should be done inside the loop, as well as the center caculation.

Yes, it will be correct if there is more than one contour left after the filter.

1 hour ago, smbape said:

Moments cannot be calculated on VectorOfVectorOfPoint.
It can be calculated on VectorOfPoint.

Did I have an opportunity to understand this from the UDF?
Will the type always be VectorOfPoint?
Then can specify it directly in the function _cveMomentsTyped.

Link to comment
Share on other sites

58 minutes ago, Lion66 said:

Did I have an opportunity to understand this from the UDF?
Will the type always be VectorOfPoint?
Then can specify it directly in the function _cveMomentsTyped.

The type a function is expecting is given by the documentation.
Because some functions can take multiple type of parameter, opencv has an intermediate type, *Array, which is derived from the real type.
*Array can be derived from Mat, Scalar, VectorOfPoint, VectorOfVectorOfPoint,VectorOfDMatch ...

There is no way to know, just by looking at the function, what is the expected real type.
For exemple, moments function can take at Mat, a VectorOfPoint, a VectorOfPointF.
You would have faced the same problem with python when calling a function with the wrong parameters.

In order to get a *Array from the real type, the function need to know the real type.
Every object on the c++ side is a pointer on the AutoIt side.
And because there is no way to know the real type of a variable, I came up with a way around which is: the user has to inform about the type.
In python, you can know the instance of a variable. This why you don't have to specify them.

Link to comment
Share on other sites

8 hours ago, Lion66 said:

How to create the correct condition to verify existence to fulfill release?

I am not sure of the question, if you mean a way to be sure that the object was created, then the condition is

$obj <> Ptr(0)

However, I don't see the relationship with release.
The rule is, everything you create must be destroyed when you are done with it.
Did you had errors trying to release an object that failed to be created?

Edited by smbape
Link to comment
Share on other sites

4 hours ago, Lion66 said:

Can all types be considered as an Object for destroy purposes?

No. An object is usually created with a _cve<type>Create or _VectorOf<type>Create. It is that variable that need to be released with _cve<type>Release or _VectorOf<type>Release.
For example

; create an object
Local $str = _cveStringCreate()
; ... use it
; release it
_cveStringRelease($str)

; create an object
Local $mat = _cveMatCreate()
; ... use it
; release it
_cveMatRelease($mat)

; create an object
Local $contours = _VectorOfVectorOfPointCreate()
; ... use it
; release it
_VectorOfVectorOfPointRelease($contours)

; create an object
; _cveImreadAndCheck returns a Mat. It is not documented, sorry about that
Local $mat = _cveImreadAndCheck('scan.jpg', $CV_IMREAD_COLOR)
; ... use it
; release it
_cveMatRelease($mat)

 

Edited by smbape
Link to comment
Share on other sites

The question was how to verify correctly that the variable was set. 🙂

 

I do examples from  here .
I did it 7a.

Local $x = 0, $y = 0, $w = 0, $h = 0
        Local $boundingRect = _cvRect()
        Local $tVectorPointPtr3 = DllStructCreate("ptr value")

        For $i = 0 To _VectorOfVectorOfPointGetSize($contours) - 1
            _VectorOfVectorOfPointGetItemPtr($contours, $i, $tVectorPointPtr3)

            Local $contour = _cveInputArrayFromVectorOfPoint($tVectorPointPtr3.value)
            _cveBoundingRectangle($contour, $boundingRect)
            Local $Area3 = _cveContourAreaTyped("VectorOfPoint", $tVectorPointPtr3.value)
            If $Area3 < 1000 Then
                ContinueLoop
            Else
                $x = $boundingRect.x
                $y = $boundingRect.y
                $w = $boundingRect.width
                $h = $boundingRect.height
                Local $tMatchRect = _cvRect($x, $y, $w, $h)
                _cveRectangleMat($img, $tMatchRect, _cvRGB(0, 0, 255), 3, $CV_LINE_8, 0)
            EndIf
        Next


But 7b (Rotated Rectangle )does not work.

Local $x2 = 0, $y2 = 0, $w2 = 0, $h2 = 0
        Local $tVectorPointPtr4 = DllStructCreate("ptr value")
        Local $tVectorPointPtr5 = DllStructCreate("ptr value")
        Local $tVectorPointPtr6 = DllStructCreate("ptr value")
        Local $boundingRect2 = _cvRect()
        For $i = 0 To _VectorOfVectorOfPointGetSize($contours) - 1
            _VectorOfVectorOfPointGetItemPtr($contours, $i, $tVectorPointPtr4)
            ;Func _cveMinAreaRectTyped($typeOfPoints, $points, $box)
            _cveMinAreaRectTyped("VectorOfPoint", $tVectorPointPtr4.value, $tVectorPointPtr5.value)
            ;_WinAPI_DisplayStruct($tVectorPointPtr5, $tagCvRect)

            ;Func _cveBoxPointsTyped($box, $typeOfPoints, $points)
            _cveBoxPointsTyped($tVectorPointPtr5, "VectorOfPoint", $boundingRect2)

            $x2 = $boundingRect2.x
            $y2 = $boundingRect2.y
            $w2 = $boundingRect2.width
            $h2 = $boundingRect2.height
            Local $tMatchRect = _cvRect($x2, $y2, $w2, $h2)
            _cveRectangleMat($img, $tMatchRect, _cvRGB(0, 0, 255), 3, $CV_LINE_8, 0)

        Next

 

Edited by Lion66
Link to comment
Share on other sites

9 hours ago, Lion66 said:

The question was how to verify correctly that the variable was set. 

When you assign a value to a variable, the variable is set.
However, your phrase makes me think that it is not what you mean.
Can you explain?

7b is hard to translate since it involves manipulation of vectors and matrix

Local $tVectorPointPtr = DllStructCreate("ptr value")
Local $countours2 = _VectorOfVectorOfPointCreate()
local $coutour = _VectorOfPointCreate()
Local $rect = DllStructCreate($tagCvBox2D)
Local $box = _cveMatCreate()

For $i = 0 To _VectorOfVectorOfPointGetSize($contours) - 1
    _VectorOfVectorOfPointGetItemPtr($contours, $i, $tVectorPointPtr)

    _cveMinAreaRectTyped("VectorOfPoint", $tVectorPointPtr.value, $rect)
    _cveBoxPointsTyped($rect, "Mat", $box)

    _VectorOfPointClear($coutour)
    For $xi = 0 To 3
        Local $x = _cveMatGetAt("float", $box, _cvPoint(0, $xi))
        Local $y = _cveMatGetAt("float", $box, _cvPoint(1, $xi))
        _VectorOfPointPush($coutour, _cvPoint($x, $y))
    Next

    _VectorOfVectorOfPointClear($countours2)
    _VectorOfVectorOfPointPush($countours2, $coutour)
    _cveDrawContoursTyped("Mat", $img, "VectorOfVectorOfPoint", $countours2, 0, _cvRGB(0, 0, 255), 3)
Next

 

Edited by smbape
Link to comment
Share on other sites

  • smbape changed the title to OpenCV v4 UDF

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
 Share

×
×
  • Create New...