La combinación no elimina las costuras en OpenCV

Intento mezclar 2 imágenes para que las costuras entre ellas desaparezcan.

Primera imagen: enter image description here

2da imagen: enter image description here

si no se aplica blending: enter image description here

si se aplica la mezcla: enter image description here

Usé ALPHA BLENDING ; NINGUNA costura eliminada; de hecho, imagen AÚN MISMO PERO MÁS OSCURA

Esta es la parte donde hago la mezcla

Mat warped1; warpPerspective(left,warped1,perspectiveTransform,front.size());// Warping may be used for correcting image distortion imshow("combined1",warped1/2+front/2); vector imgs; imgs.push_back(warped1/2); imgs.push_back(front/2); double alpha = 0.5; int min_x = ( imgs[0].cols - imgs[1].cols)/2 ; int min_y = ( imgs[0].rows -imgs[1].rows)/2 ; int width, height; if(min_x < 0) { min_x = 0; width = (imgs).at(0).cols; } else width = (imgs).at(1).cols; if(min_y < 0) { min_y = 0; height = (imgs).at(0).rows - 1; } else height = (imgs).at(1).rows - 1; Rect roi = cv::Rect(min_x, min_y, imgs[1].cols, imgs[1].rows); Mat out_image = imgs[0].clone(); Mat A_roi= imgs[0](roi); Mat out_image_roi = out_image(roi); addWeighted(A_roi,alpha,imgs[1],1-alpha,0.0,out_image_roi); imshow("foo",imgs[0](roi)); 

Elijo definir el valor alfa en función de la distancia al “centro del objeto”, cuanto más lejos esté del centro del objeto, menor será el valor alfa. El “objeto” está definido por una máscara.

He alineado las imágenes con GIMP (similar a su warpPerspective). Deben estar en el mismo sistema de coordenadas y ambas imágenes deben tener el mismo tamaño.

Mis imágenes de entrada se ven así:

enter image description here

enter image description here

 int main() { cv::Mat i1 = cv::imread("blending/i1_2.png"); cv::Mat i2 = cv::imread("blending/i2_2.png"); cv::Mat m1 = cv::imread("blending/i1_2.png",CV_LOAD_IMAGE_GRAYSCALE); cv::Mat m2 = cv::imread("blending/i2_2.png",CV_LOAD_IMAGE_GRAYSCALE); // works too, for background near white // m1 = m1 < 220; // m2 = m2 < 220; // edited: using OTSU thresholding. If not working you have to create your own masks with a better technique cv::threshold(m1,m1,255,255,cv::THRESH_BINARY_INV|cv::THRESH_OTSU); cv::threshold(m2,m2,255,255,cv::THRESH_BINARY_INV|cv::THRESH_OTSU); cv::Mat out = computeAlphaBlending(i1,m1,i2,m2); cv::waitKey(-1); return 0; } 

con la función de mezcla: necesita algunos comentarios y optimizaciones, supongo, los agregaré más adelante.

 cv::Mat computeAlphaBlending(cv::Mat image1, cv::Mat mask1, cv::Mat image2, cv::Mat mask2) { // edited: find regions where no mask is set // compute the region where no mask is set at all, to use those color values unblended cv::Mat bothMasks = mask1 | mask2; cv::imshow("maskOR",bothMasks); cv::Mat noMask = 255-bothMasks; // ------------------------------------------ // create an image with equal alpha values: cv::Mat rawAlpha = cv::Mat(noMask.rows, noMask.cols, CV_32FC1); rawAlpha = 1.0f; // invert the border, so that border values are 0 ... this is needed for the distance transform cv::Mat border1 = 255-border(mask1); cv::Mat border2 = 255-border(mask2); // show the immediate results for debugging and verification, should be an image where the border of the face is black, rest is white cv::imshow("b1", border1); cv::imshow("b2", border2); // compute the distance to the object center cv::Mat dist1; cv::distanceTransform(border1,dist1,CV_DIST_L2, 3); // scale distances to values between 0 and 1 double min, max; cv::Point minLoc, maxLoc; // find min/max vals cv::minMaxLoc(dist1,&min,&max, &minLoc, &maxLoc, mask1&(dist1>0)); // edited: find min values > 0 dist1 = dist1* 1.0/max; // values between 0 and 1 since min val should alwaysbe 0 // same for the 2nd image cv::Mat dist2; cv::distanceTransform(border2,dist2,CV_DIST_L2, 3); cv::minMaxLoc(dist2,&min,&max, &minLoc, &maxLoc, mask2&(dist2>0)); // edited: find min values > 0 dist2 = dist2*1.0/max; // values between 0 and 1 //TODO: now, the exact border has value 0 too... to fix that, enter very small values wherever border pixel is set... // mask the distance values to reduce information to masked regions cv::Mat dist1Masked; rawAlpha.copyTo(dist1Masked,noMask); // edited: where no mask is set, blend with equal values dist1.copyTo(dist1Masked,mask1); rawAlpha.copyTo(dist1Masked,mask1&(255-mask2)); //edited cv::Mat dist2Masked; rawAlpha.copyTo(dist2Masked,noMask); // edited: where no mask is set, blend with equal values dist2.copyTo(dist2Masked,mask2); rawAlpha.copyTo(dist2Masked,mask2&(255-mask1)); //edited cv::imshow("d1", dist1Masked); cv::imshow("d2", dist2Masked); // dist1Masked and dist2Masked now hold the "quality" of the pixel of the image, so the higher the value, the more of that pixels information should be kept after blending // problem: these quality weights don't build a linear combination yet // you want a linear combination of both image's pixel values, so at the end you have to divide by the sum of both weights cv::Mat blendMaskSum = dist1Masked+dist2Masked; //cv::imshow("blendmask==0",(blendMaskSum==0)); // you have to convert the images to float to multiply with the weight cv::Mat im1Float; image1.convertTo(im1Float,dist1Masked.type()); cv::imshow("im1Float", im1Float/255.0); // TODO: you could replace those splitting and merging if you just duplicate the channel of dist1Masked and dist2Masked // the splitting is just used here to use .mul later... which needs same number of channels std::vector channels1; cv::split(im1Float,channels1); // multiply pixel value with the quality weights for image 1 cv::Mat im1AlphaB = dist1Masked.mul(channels1[0]); cv::Mat im1AlphaG = dist1Masked.mul(channels1[1]); cv::Mat im1AlphaR = dist1Masked.mul(channels1[2]); std::vector alpha1; alpha1.push_back(im1AlphaB); alpha1.push_back(im1AlphaG); alpha1.push_back(im1AlphaR); cv::Mat im1Alpha; cv::merge(alpha1,im1Alpha); cv::imshow("alpha1", im1Alpha/255.0); cv::Mat im2Float; image2.convertTo(im2Float,dist2Masked.type()); std::vector channels2; cv::split(im2Float,channels2); // multiply pixel value with the quality weights for image 2 cv::Mat im2AlphaB = dist2Masked.mul(channels2[0]); cv::Mat im2AlphaG = dist2Masked.mul(channels2[1]); cv::Mat im2AlphaR = dist2Masked.mul(channels2[2]); std::vector alpha2; alpha2.push_back(im2AlphaB); alpha2.push_back(im2AlphaG); alpha2.push_back(im2AlphaR); cv::Mat im2Alpha; cv::merge(alpha2,im2Alpha); cv::imshow("alpha2", im2Alpha/255.0); // now sum both weighted images and divide by the sum of the weights (linear combination) cv::Mat imBlendedB = (im1AlphaB + im2AlphaB)/blendMaskSum; cv::Mat imBlendedG = (im1AlphaG + im2AlphaG)/blendMaskSum; cv::Mat imBlendedR = (im1AlphaR + im2AlphaR)/blendMaskSum; std::vector channelsBlended; channelsBlended.push_back(imBlendedB); channelsBlended.push_back(imBlendedG); channelsBlended.push_back(imBlendedR); // merge back to 3 channel image cv::Mat merged; cv::merge(channelsBlended,merged); // convert to 8UC3 cv::Mat merged8U; merged.convertTo(merged8U,CV_8UC3); return merged8U; } 

y función auxiliar:

 cv::Mat border(cv::Mat mask) { cv::Mat gx; cv::Mat gy; cv::Sobel(mask,gx,CV_32F,1,0,3); cv::Sobel(mask,gy,CV_32F,0,1,3); cv::Mat border; cv::magnitude(gx,gy,border); return border > 100; } 

con resultado:

enter image description here

editar: se olvidó una función;) editar: ahora manteniendo el fondo original

  1. Primero crea una imagen de Máscara a partir de tu imagen de entrada, esto se puede hacer al ponerle un umbral a la imagen de origen y realizar en modo bit_y entre ellos.

  2. Ahora copie el resultado sumdo a una nueva alfombra usando la máscara anterior.

enter image description hereenter image description here

enter image description hereenter image description here
enter image description here

En el siguiente código, no he utilizado warpPerspective. En cambio, utilicé ROI en ambas imágenes para alinearlas correctamente.

 Mat left=imread("left.jpg"); Mat front=imread("front.jpg"); int x=30, y=10, w=240, h=200, offset_x=20, offset_y=6; Mat leftROI=left(Rect(x,y,w,h)); Mat frontROI=front(Rect(x-offset_x,y+offset_y,w,h)); //create mask Mat gray1,thr1; cvtColor(leftROI,gray1,CV_BGR2GRAY); threshold( gray1, thr1,190, 255,CV_THRESH_BINARY_INV ); Mat gray2,thr2; cvtColor(frontROI,gray2,CV_BGR2GRAY); threshold( gray2, thr2,190, 255,CV_THRESH_BINARY_INV ); Mat mask; bitwise_and(thr1,thr2,mask); //perform add weighted and copy using mask Mat add; double alpha=.5; double beta=.5; addWeighted(frontROI,alpha,leftROI,beta,0.0,add,-1); Mat dst(add.rows,add.cols,add.type(),Scalar::all(255)); add.copyTo(dst,mask); imshow("dst",dst); 

De acuerdo. Aquí hay una nueva prueba que podría funcionar solo para su tarea específica para combinar exactamente 3 imágenes de esas caras, frontal, izquierda, derecha.

Yo uso estas entradas:

frente (i1):

enter image description here

izquierda (i2):

enter image description here

derecha (i3):

enter image description here

máscara frontal (m1): (opcional):

enter image description here

El problema con estas imágenes es que la imagen frontal solo cubre una pequeña parte, mientras que la izquierda y la derecha se superponen a toda la imagen frontal, lo que conduce a una mala combinación en la otra solución. Además, la alineación de las imágenes no es tan grande (principalmente debido a los efectos de perspectiva), por lo que pueden producirse artefactos de fusión.

Ahora la idea de este nuevo método es, que definitivamente desea mantener las partes de la imagen frontal, que se encuentran dentro del área abarcada por sus “puntos de marcador” coloreados, estos no deben mezclarse. Cuanto más se aleje de esa área de marcador, más información de las imágenes izquierda y derecha se debe utilizar, por lo que creamos una máscara con valores alfa, que disminuye linealmente de 1 (dentro del área de marcador) a 0 (a una distancia definida de la región del marcador).

Entonces, la región abarcada por los marcadores es esta:

enter image description here

ya que sabemos que la imagen de la izquierda se usa básicamente en la región que queda del triángulo marcador izquierdo, podemos crear máscaras para la imagen izquierda y derecha, que se utilizan para encontrar la región que además debe estar cubierta por la imagen frontal:

izquierda:

enter image description here

derecho:

enter image description here

región del marcador frontal y todo lo que no está a la izquierda y no está en la máscara derecha:

enter image description here

esto se puede enmascarar con la entrada de máscara frontal opcional, esto es mejor porque este ejemplo de imagen frontal no cubre toda la imagen, pero lamentablemente solo una parte de la imagen.

enter image description here

ahora esta es la máscara de fusión, con un valor alfa decreciente lineal hasta que la distancia a la máscara sea 10 o más píxeles:

enter image description here

ahora primero creamos la imagen que cubre solo la imagen izquierda y derecha, copiando la mayoría de las partes sin mezclar, pero mezclamos las partes descubiertas por las máscaras izquierda / derecha con 0.5*left + 0.5*right

blendLR :

enter image description here

finalmente blendLR imagen front en esa blendLR por cálculo:

blended = alpha*front + (1-alpha)*blendLR

enter image description here

algunas mejoras pueden incluir para calcular el valor de maxDist partir de información más alta (como el tamaño de la superposición o el tamaño de los triangularjs marcadores hasta el borde de la cara).

Otra mejora sería no calcular 0.5*left + 0.5*right pero para hacer algunas mezclas alfa aquí también, tomando más información de la imagen de la izquierda cuanto más a la izquierda estamos en el espacio. Esto reduciría las costuras en el medio de la imagen (en la parte superior e inferior de la parte frontal de la imagen).

 // idea: keep all the pixels from front image that are inside your 6 points area always unblended: cv::Mat blendFrontAlpha(cv::Mat front, cv::Mat left, cv::Mat right, std::vector sixPoints, cv::Mat frontForeground = cv::Mat()) { // define some maximum distance. No information of the front image is used if it's further away than that maxDist. // if you have some real masks, you can easily set the maxDist according to the dimension of that mask - dimension of the 6-point-mask float maxDist = 10; // to use the cv function to draw contours we must order it like this: std::vector > contours; contours.push_back(sixPoints); // create the mask cv::Mat frontMask = cv::Mat::zeros(front.rows, front.cols, CV_8UC1); // draw those 6 points connected as a filled contour cv::drawContours(frontMask,contours,0,cv::Scalar(255),-1); // add "lines": everything left from the points 3-4-5 might be used from left image, everything from the points 0-1-2 might be used from the right image: cv::Mat leftMask = cv::Mat::zeros(front.rows, front.cols, CV_8UC1); { cv::Point2f center = cv::Point2f(sixPoints[3].x, sixPoints[3].y); float steigung = ((float)sixPoints[5].y - (float)sixPoints[3].y)/((float)sixPoints[5].x - (float)sixPoints[3].x); if(sixPoints[5].x - sixPoints[3].x == 0) steigung = 2*front.rows; float n = center.y - steigung*center.x; cv::Point2f top = cv::Point2f( (0-n)/steigung , 0); cv::Point2f bottom = cv::Point2f( (front.rows-1-n)/steigung , front.rows-1); // now create the contour of the left image: std::vector leftMaskContour; leftMaskContour.push_back(top); leftMaskContour.push_back(bottom); leftMaskContour.push_back(cv::Point(0,front.rows-1)); leftMaskContour.push_back(cv::Point(0,0)); std::vector > leftMaskContours; leftMaskContours.push_back(leftMaskContour); cv::drawContours(leftMask,leftMaskContours,0,cv::Scalar(255),-1); cv::imshow("leftMask", leftMask); cv::imwrite("x_leftMask.png", leftMask); } // add "lines": everything left from the points 3-4-5 might be used from left image, everything from the points 0-1-2 might be used from the right image: cv::Mat rightMask = cv::Mat::zeros(front.rows, front.cols, CV_8UC1); { // add "lines": everything left from the points 3-4-5 might be used from left image, everything from the points 0-1-2 might be used from the right image: cv::Point2f center = cv::Point2f(sixPoints[2].x, sixPoints[2].y); float steigung = ((float)sixPoints[0].y - (float)sixPoints[2].y)/((float)sixPoints[0].x - (float)sixPoints[2].x); if(sixPoints[0].x - sixPoints[2].x == 0) steigung = 2*front.rows; float n = center.y - steigung*center.x; cv::Point2f top = cv::Point2f( (0-n)/steigung , 0); cv::Point2f bottom = cv::Point2f( (front.rows-1-n)/steigung , front.rows-1); std::cout < < top << " - " << bottom << std::endl; // now create the contour of the left image: std::vector rightMaskContour; rightMaskContour.push_back(cv::Point(front.cols-1,0)); rightMaskContour.push_back(cv::Point(front.cols-1,front.rows-1)); rightMaskContour.push_back(bottom); rightMaskContour.push_back(top); std::vector > rightMaskContours; rightMaskContours.push_back(rightMaskContour); cv::drawContours(rightMask,rightMaskContours,0,cv::Scalar(255),-1); cv::imshow("rightMask", rightMask); cv::imwrite("x_rightMask.png", rightMask); } // add everything that's not in the side masks to the front mask: cv::Mat additionalFrontMask = (255-leftMask) & (255-rightMask); // if we know more about the front face, use that information: cv::imwrite("x_frontMaskIncreased1.png", frontMask + additionalFrontMask); if(frontForeground.cols) { // since the blending mask is blended for maxDist distance, we have to erode this mask here. cv::Mat tmp; cv::erode(frontForeground,tmp,cv::Mat(),cv::Point(),maxDist); // idea is to only use the additional front mask in those areas where the front image contains of face and not those background parts. additionalFrontMask = additionalFrontMask & tmp; } frontMask = frontMask + additionalFrontMask; cv::imwrite("x_frontMaskIncreased2.png", frontMask); //todo: add lines cv::imshow("frontMask", frontMask); // for visualization only: cv::Mat frontMasked; front.copyTo(frontMasked, frontMask); cv::imshow("frontMasked", frontMasked); cv::imwrite("x_frontMasked.png", frontMasked); // compute inverse of mask to take it as input for distance transform: cv::Mat inverseFrontMask = 255-frontMask; // compute the distance to the mask, the further away from the mask, the less information from the front image should be used: cv::Mat dist; cv::distanceTransform(inverseFrontMask,dist,CV_DIST_L2, 3); // scale wanted values between 0 and 1: dist /= maxDist; // remove all values > 1; those values are further away than maxDist pixel from the 6-point-mask dist.setTo(cv::Scalar(1.0f), dist>1.0f); // now invert the values so that they are == 1 inside the 6-point-area and go to 0 outside: dist = 1.0f-dist; cv::Mat alphaValues = dist; //cv::Mat alphaNonZero = alphaValues > 0; // now alphaValues contains your general blendingMask. // but to use it on colored images, we need to duplicate the channels: std::vector singleChannels; singleChannels.push_back(alphaValues); singleChannels.push_back(alphaValues); singleChannels.push_back(alphaValues); // merge all the channels: cv::merge(singleChannels, alphaValues); cv::imshow("alpha mask",alphaValues); cv::imwrite("x_alpha_mask.png", alphaValues*255); // convert all input mats to floating point mats: front.convertTo(front,CV_32FC3); left.convertTo(left,CV_32FC3); right.convertTo(right,CV_32FC3); cv::Mat result; // first: blend left and right both with 0.5 to the result, this gives the correct results for the intersection of left and right equally weighted. // TODO: these values could be blended from left to right, giving some finer results cv::addWeighted(left,0.5,right,0.5,0, result); // now copy all the elements that are included in only one of the masks (not blended, just 100% information) left.copyTo(result,leftMask & (255-rightMask)); right.copyTo(result,rightMask & (255-leftMask)); cv::imshow("left+right", result/255.0f); cv::imwrite("x_left_right.png", result); // now blend the front image with it's alpha blending mask: cv::Mat result2 = front.mul(alphaValues) + result.mul(cv::Scalar(1.0f,1.0f,1.0f)-alphaValues); cv::imwrite("x_front_blend.png", front.mul(alphaValues)); cv::imshow("inv", cv::Scalar(1.0f,1.0f,1.0f)-alphaValues); cv::imshow("fa", front.mul(alphaValues)/255.0f); cv::imshow("fr", (result.mul(cv::Scalar(1.0f,1.0f,1.0f)-alphaValues))/255.0f); result2.convertTo(result2, CV_8UC3); return result2; } int main() { // front image cv::Mat i1 = cv::imread("blending/new/front.jpg"); // left image cv::Mat i2 = cv::imread("blending/new/left.jpg"); // right image cv::Mat i3 = cv::imread("blending/new/right.jpg"); // optional: mask of front image cv::Mat m1 = cv::imread("blending/new/mask_front.png",CV_LOAD_IMAGE_GRAYSCALE); cv::imwrite("x_M1.png", m1); // these are the marker points you detect in the front image. // the order is important. the first three pushed points are the right points (left part of the face) in order from top to bottom // the second three points are the ones from the left image half, in order from bottom to top // check coordinates for those input images to understand the ordering! std::vector frontArea; frontArea.push_back(cv::Point(169,92)); frontArea.push_back(cv::Point(198,112)); frontArea.push_back(cv::Point(169,162)); frontArea.push_back(cv::Point(147,162)); frontArea.push_back(cv::Point(122,112)); frontArea.push_back(cv::Point(147,91)); // first parameter is the front image, then left (right face half), then right (left half of face), then the image polygon and optional the front image mask (which contains all facial parts of the front image) cv::Mat result = blendFrontAlpha(i1,i2,i3, frontArea, m1); cv::imshow("RESULT", result); cv::imwrite("x_Result.png", result); cv::waitKey(-1); return 0; } 

Para evitar que las caras sean transparentes fuera de su intersección, no puede usar un solo valor alpha para toda la imagen .

Por ejemplo, necesita usar alpha=0.5 en la intersección de img[0] e img[1] , alpha=1 en la región donde img[1]=0 y alpha=0 en la región donde img[0]=0 .

Este ejemplo es el enfoque fácil, pero no eliminará por completo las costuras. Si lo desea, debe adaptar alpha más inteligente en función del contenido de la imagen. Puede echar un vistazo a los numerosos artículos de investigación sobre ese tema, pero esta no es una tarea trivial:

  • “Costura de imágenes sin costuras en el dominio de gradiente”, de Levin, Zomet Peleg & Weiss, ECCV 2004 ( enlace )

  • “Costura sin costuras con barrido de planos de múltiples perspectivas”, por Kang, Szeliski y Uyttendaele, 2004 ( enlace )