Make Image!
ldci12-May-2015/17:47:44+2:00
Voici une fonction qui fait son boulot: elle prend en paramètre une image OpenCV et la transforme en image RGB Rebol

cvtoRebol2: func [img /local src rgb] [
"Transforms OpenCV image to REBOL image"
    src: getIPLImage img 							; get values of IplImage
	rgb: copy #{}									; make a binary 
	for y 0 src/height - 1 1 [
		index: y * src/widthStep 					; go to line y and get data line by line
		line: get-memory (src/imageData + index) (src/width * src/nChannels)  
		append tail rgb reverse line
	]
	; mow makes the REBOL image
	RebolImage: make image! reduce [as-pair (src/width) (src/height) rgb] 
] 


Le seule petit problème est qu'à chaque appel, la fonction crée une nouvelle image, générant ainsi une surconsommation de la mémoire et bien évidement des risques de plantage. Comment pourrait-on faire pour éviter ce problème?
A +
GreG15-May-2015/10:49:28+2:00
RebolImage n'est pas local à la fonction, est-ce volontaire? Du coup ça devient une définition globale.
Pourquoi ne pas remplacer directement par:
make image! reduce [as-pair (src/width) (src/height) rgb]


Si tu souhaites concerver RebolImage, as-tu essayer un clear RebolImage ou un RebolImage: none ?
ldci16-May-2015/18:37:21+2:00
Bien vu pour la variable en local
Mais tous les tests ne changent rien: le make image! reste consommateur de mémoire .
A +
DideC18-May-2015/17:00:29+2:00
Pour moi, il y a 4 cas possibles :
- soit l'image Rebol n'existe pas encore : il faut la créer.
- soit l'image a la même taille que précédemment.
- soit le nombre de pixels est plus petit.
- soit le nombre de pixels est plus grand.

Dans le 2ème cas, on peut réutiliser l'image précédente en "pokant" les pixels 1 par 1.

Dans le 3ème cas, la doc indique qu'on peut changer la taille (/size) et qu'il peut rester des pixels inutilisés à la fin. Le buffer binaire reste attribué, mais pas utilisé. Donc là, il suffit d'indiquer la nouvelle taille, sans recréer l'image.

Dans le 4ème cas, il faut recréer l'image, car le buffer est trop petit.

Cela donnerait quelque chose comme ça (que je n'ai pas testé, donc à peaufiner) :
cvtoRebol2: func [
	"Transforms OpenCV image to a new or existing REBOL image"
	cvimg "OpenCV image to transform"     ; quel est le type de cette donnée ?
	img "Rebol image destination of the copy (warning : recreate if too small or not an image)."
	/local src rgb
] [
	src: getIPLImage cvimg 		; get values of IplImage
	; si l'image rebol n'en est pas une
	; ou que le nombre de pixels est supérieur au nombre de pixels actuel de l'image (indépendamment de ses dimensions), on la recréé.
	if any ['images! <> type?/word img  (src/height * src/width) > length? img] [img: make image! as-pair src/width src/height]
	; Si les dimensions sont différentes : on modifie la dimension de l'image
	if any [src/height <> img/size/y   src/width <> img/size/x] [img/size: as-pair src/width src/height]
	
	for y 0 src/height - 1 1 [
		index: y * src/widthStep 					; go to line y and get data line by line
		line: get-memory (src/imageData + index) (src/width * src/nChannels)  
		; Attention : il faut poker des tuple!
		poke img index to tuple! reverse line
	]
	img
]
ldci18-May-2015/22:00:07+2:00
Merci Didier
Je vais regarder ça demain
ldci22-May-2015/12:38:42+2:00
DideC est toujours aussi élégant dans son code et ses solutions
La solution de Didier marche très bien pour ne créer l'image REBOL uniquement quand elle n'existe pas ou qu'elle a changé de taille. Très utile pour les fuites mémoires liées à la création multiple d'images. En revanche le poke qu'il propose n'est pas adapté, car le poke permet un accès individuel à un élément n d'une série (ici un pixel). Pour utiliser le poke, il faut travailler pixel par pixel, c'est-dire, se positionner au début de chaque ligne de l'image et ensuite de faire un poke sur les n valeurs correspondant à chaque colonne de l'image. Cela consomme du temps. L'autre problème est que le type tuple! est limité à 10 valeurs et on ne peut donc pas transformer toute la ligne en une seule instruction: on ne récupère que les 10 premières valeurs de la ligne, c'est-à-dire 3.33333 pixels! Heureusement pour nous comme Carl a très bien conçu le type image!, on peut s'en sortir aisément avec les raffinements. Mais avant de présenter cette solution, quelques informations sur les images qui pourront être utiles pour tous ceux qui s'intéressent aux images avec REBOL.

Les types images OpenCV et REBOL sont assez proches mais également différents. Les deux types d'image peuvent se définir comme une structure. Pour REBOL, c'est comme en C, très simple.

image: make struct! [
   width    [integer!] ; Image width in pixels
   height    [integer!]   ; Image height in pixels
   data    [binary!]    ; binary string, i.e. a pointer to aligned image data
] none

la structure de l'image OpenCV est plus complexe car elle intègre des informations supplémentaires comme les régions d'intérêt (ROI), la profondeur, le nombre de canaux et d'autres informations non utilisées. C'est lié au type image initialement créé par Intel qui avait développé en C les premières versions d'OpenCV. Mais fondamentalement on retrouvera les éléments qui nous intéressent: la hauteur et la largeur de l'image ainsi que l'adresse de son contenu binaire. Dans la structure REBOL ci-dessous, qui calque la structure initiale en C, on utilise un type int pour désigner un pointeur.

IplImage!: make struct! [
nSize             [integer!]       ; sizeof(IplImage)
ID                [integer!]       ; version (=0)
nChannels          [integer!]       ; functions support 1,2,3 or 4 channels
   alphaChannel       [integer!]       ; Ignored by OpenCV */
depth             [integer!]       ; Pixel depth in bits:
cm0             [char!]         ; colorModel Ignored by OpenCV char [4] RGB
cm1               [char!]
cm2               [char!]
cm3               [char!]
cs0                [char!]         ; channelSeq Ignored by OpenCV char[4] BGR
cs1               [char!]
cs2               [char!]
cs3               [char!]
dataOrder          [integer!]      ; 0 - interleaved , 1 - separate color channels.
origin             [integer!]      ; 0 - top-left origin, 1 - bottom-left origin
align             [integer!]      ; Alignment of image rows (4 or 8); uses widthStep
width             [integer!]      ; Image width in pixels
height             [integer!]      ; Image height in pixels. `
roi               [int];         ; Image ROI. If NULL, the whole image is selected
maskROI          [int]         ; Must be NULL
imageId          [int]         ; NULL
tileInfo          [int]         ; NULL
imageSize          [integer!]      ; Image data size in bytes
imageData          [int]         ; Pointer to aligned image data [char*].
widthStep          [integer!]      ; Size of aligned image row in bytes.
bm0                [integer!]      ; BorderMode int BorderMode[4]
bm1                [integer!]
bm2                [integer!]
bm3                [integer!]
bc0                [integer!]      ; BorderConst Ditto.
bc1                [integer!]
bc2                [integer!]
bc3                [integer!]
imageDataOrigin      [int]         ; Pointer to very origin of image data
] none




Une seconde différence réside dans le codage de la couleur. Pour REBOL, le format est unique et de type RGBA, c'est-à dire une image 8 bits à 4 canaux dont les trois premiers codent la couleur RGB et le quatrième la transparence alpha. REBOL code donc les pixels sous la forme d'un tuple r.g.b.a : 255.255.225.0

Pour OpenCV, les images peuvent être de formats différents. On peut avoir des images à 1, 2, 3 ou 4 canaux en 8, 16 ou 32 bits. Dans la pratique, celles qui sont très utilisées sont les images à 1 canal pour la binarisation en noir et blanc et les images 3 canaux pour gérer les images couleurs. OpenCV utilise le format BGR pour les images en couleur et ne gère pas la transparence.

Un autre différence est dans l'alignement des données en mémoire et dans la façon d'accéder individuellement aux pixels de l'image. Pour REBOL comme pour OpenCV, les images sont considérées comme une matrice 2D et les valeurs binaires sont contenues dans un vecteur (une matrice à 1 seule dimension). OpenCV et REBOL utilisent tous les deux un modèle row-major order.Pour construire l'image, on doit donc calculer la position du pixel dans le vecteur afin de pouvoir l'afficher dans la matrice 2D.
Pour REBOL c'est très simple, comme les pixels sont toujours un tuple r.g.b.a, il suffit d'utiliser les coordonnées xy du pixel pour y accéder en lecture ou en écriture. Si x est la colonne et y la ligne dans la matrice 2D, la position du pixel dans le vecteur est égale à x + (y * image/width).

Pour des raisons d'optimisation de la mémoire OpenCV gère différemment, via la variable widthStep, la façon dont il aligne en mémoire les pixels. Pour OpenCV, comme le format et la valeur des pixels peut varier selon le type d'image, on doit faire un calcul qui tient compte de l'alignement des données en mémoire. La position du pixel dans le vecteur est égale à x + (y * image/widthStep). Dans les cas les plus simple image/widthStep est égal à image/width. Dans d'autres cas, widthStep est > à width et dans ce cas OpenCV rajoute des pixels supplémentaire pour aligner ses données. Si on ne prend pas en compte ce facteur on se retrouve avec une image zébrée car le calcule du décalage de début de ligne n'est pas bon.

Comment faire pour transformer une image OpenCV en image REBOL?
Personnellement, je fais faire tous les traitements sur les images par OpenCV et ensuite
je copie le résultat du traitement dans une image OpenCV (8 bits et 3 canaux) compatible avec REBOL.

Après c'est très simple

On utilise la fonction getIPLImage (incluse dans le binding OpenCV pour REBOL) qui permet de connaitre le contenu de la structure de l'image OpenCV notamment la taille de l'image et l'adresse du pointeur sur les données binaires. Ensuite il nous suffit de créer un buffer (une chaine binaire) qui récupérera les pixels contenus dans l'image OpenCV. En crée également une image REBOL en fonction de la taille de l'image OpenCV. Après ligne par ligne, on récupère avec un pointeur les valeurs OpenCV qui sont dans zone mémoire sur laquelle pointe le pointeur. Pour chaque ligne de l'image OpenCV on récupère le contenu de la ligne en décalant l'adresse du pointeur selon l'alignement des données en mémoire. On ajoute le contenu de la ligne dans le buffer binaire après avoir inversé la ligne pour passer de BGR à RGB. Comme Carl a très bien conçu le type image, lorsque nous avons lu toutes les lignes, il nous suffit d'utiliser les raffinements /size /rgb et /alpha pour mettre à jour notre image REBOL.

src: getIPLImage cvimg
rgb: make binary! #{}
img: make image! as-pair src/width src/height]

for y 0 src/height - 1 1 [
		index: y * src/widthStep 
		line: get-memory (src/imageData + index) (src/width * src/nChannels)  
		append tail rgb reverse line
]
img/size: as-pair src/width src/height 
img/rgb: rgb 
img/alpha: 0
img


C'est simple et efficace car on utilise des pointeurs (avec get-memory) vers des zones mémoires et on traite les colonnes de l'image en une seule fois. Il y a certainement d'autres solutions, comme utiliser un change directement dans l'image REBOL pour remplacer le poke que propose DideC, qui seraient toutes aussi rapides. A vos claviers!
DideC1-Jun-2015/15:55:49+2:00
Merci pour toutes ces explications (ah, un peu de technique !).

Je n'avais pas compris que le 'get-memory récupérait toute la ligne de pixels.

Ce qui me chiffonne, c'est l'histoire du BGR/RGB.
Je pensais que cela signifiait que chaque pixel, avait ces trois octets dans l'ordre inverse (R est le premier octet des 3 en RGB, le dernier en BGR...), mais les pixels, eux étaient dans le même ordre.
Si c'est le cas, alors le 'reverse doit aussi inverser le sens de la ligne, pas seulement celui des couleurs (la gauche devient la droite et inversement) !?!?

Sinon, pour en revenir à l'optimisation mémoire, on doit pouvoir utiliser 'change/part pour modifier directement le img/rgb.

Est-ce que ça ça marche ?
cvtoRebol2: func [
	"Transforms OpenCV image to a new or existing REBOL image"
	cvimg "OpenCV image to transform"     ; quel est le type de cette donnée ?
	img "Rebol image destination of the copy (warning : recreate if too small or not an image)."
	/local src rgb
] [
	src: getIPLImage cvimg 		; get values of IplImage
	; si l'image rebol n'en est pas une
	; ou que le nombre de pixels est supérieur au nombre de pixels actuel de l'image (indépendamment de ses dimensions), on la recréé.
	if any ['images! <> type?/word img  (src/height * src/width) > length? img] [img: make image! as-pair src/width src/height]
	; Si les dimensions sont différentes : on modifie la dimension de l'image
	if any [src/height <> img/size/y   src/width <> img/size/x] [img/size: as-pair src/width src/height]
	
	for y 0 src/height - 1 1 [
		index: y * src/widthStep 					; go to line y and get data line by line
		line: get-memory (src/imageData + index) (src/width * src/nChannels)  
		; On change directement dans la chaine binaire
		change/part at img/rgb img/size/y * y + 1 reverse line
	]
	img
]
ldci2-Jun-2015/10:47:35+2:00
Salut Didec
Bonne observation pour le BGR/RGB
Le reverse inverse bien le sens de la ligne (Ah, les petits et les grands indiens!) , mais ne touche pas à l'ordre des pixels. Il faudrait lire pixel par pixel et inverser le tuple à chaque fois. Pas terrible! Une façon simple de s'en sortir est de faire un flip avec REBOL
srcimage/image: cvtoRebol2 image srcimage/image
srcimage/effect: [fit flip 1x0]


Pour le change/part, il faut que je regarde en détail car tel que j'ai une erreur "Error: change expected range argument of type: number series port pair"
A suivre ...
DideC3-Jun-2015/10:19:02+2:00
Error : normal, j'ai oublié d'indiquer la longueur du change/part en fin de ligne !
Ca doit être img/size/x, je pense :
change/part at img/rgb img/size/y * y + 1 reverse line img/size/x
DocKimbel6-Jun-2015/13:25:02+2:00
@ldci Merci pour l'étude comparative entre les deux formats d'images, très intéressant.

Login required to Post.


Powered by RebelBB and REBOL 2.7.8.4.2