void Arc3DModel::Laplacian2(FloatImage &depthImg, FloatImage &countImg, int minCount, CharImage &featureMask, float depthThr) { FloatImage Sum; int w=depthImg.w,h=depthImg.h; Sum.resize(w,h); for(int y=1;y<h-1;++y) for(int x=1;x<w-1;++x) { float curDepth=depthImg.Val(x,y); int cnt=0; for(int j=-1;j<=1;++j) for(int i=-1;i<=1;++i) { int q=countImg.Val(x+i,y+j)-minCount+1; if(q>0 && fabs(depthImg.Val(x+i,y+j)-curDepth) < depthThr) { Sum.Val(x,y)+=q*depthImg.Val(x+i,y+j); cnt+=q; } } if(cnt>0) { Sum.Val(x,y)/=cnt; } else Sum.Val(x,y)=depthImg.Val(x,y); } for(int y=1;y<h-1;++y) for(int x=1;x<w-1;++x) { float q=(featureMask.Val(x,y)/255.0); depthImg.Val(x,y) = depthImg.Val(x,y)*q + Sum.Val(x,y)*(1-q); } }
/// Apply the hand drawn mask image bool Arc3DModel::CombineHandMadeMaskAndCount(CharImage &CountImg, QString maskName ) { QImage maskImg(maskName); qDebug("Trying to read maskname %s",qPrintable(maskName)); if(maskImg.isNull()) return false; if( (maskImg.width()!= CountImg.w) || (maskImg.height()!= CountImg.h) ) { qDebug("Warning mask and images does not match! %i %i vs %i %i",maskImg.width(),CountImg.w,maskImg.height(),CountImg.h); return false; } for(int j=0;j<maskImg.height();++j) for(int i=0;i<maskImg.width();++i) if(qRed(maskImg.pixel(i,j))>128) CountImg.Val(i,j)=0; return true; }
void Arc3DModel::SmartSubSample(int factor, FloatImage &fli, CharImage &chi, FloatImage &subD, FloatImage &subQ, int minCount) { assert(fli.w==chi.w && fli.h==chi.h); int w=fli.w/factor; int h=fli.h/factor; subQ.resize(w,h); subD.resize(w,h); for(int i=0;i<w;++i) for(int j=0;j<h;++j) { float maxcount=0; int cnt=0; float bestVal=0; for(int ki=0;ki<factor;++ki) for(int kj=0;kj<factor;++kj) { float q= chi.Val(i*factor+ki,j*factor+kj) - minCount+1 ; if(q>0) { maxcount+= q; bestVal +=q*fli.Val(i*factor+ki,j*factor+kj); cnt++; } } if(cnt>0) { subD.Val(i,j)=float(bestVal)/maxcount; subQ.Val(i,j)=minCount-1 + float(maxcount)/cnt ; } else { subD.Val(i,j)=0; subQ.Val(i,j)=0; } } }
/* Main function that populate the dialog, loading all the images and eventually creating the thumbs. called directly by the open before invoking this dialog. */ void v3dImportDialog::setArc3DReconstruction(Arc3DReconstruction *_er) { // if the epoch reconstruction has not changed do nothing if(erCreated == _er->created) { er=_er; return; } er=_er; erCreated=er->created; ui.infoLabel->setText(er->name + " - " + er->author + " - " + er->created); ui.imageTableWidget->clear(); ui.imageTableWidget->setRowCount(er->modelList.size()); ui.imageTableWidget->setColumnCount(4); //imageTableWidget->setColumnWidth (1,64); ui.imageTableWidget->setSelectionBehavior (QAbstractItemView::SelectRows); ui.imageTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); ui.imageTableWidget->setMinimumWidth(64*8); ui.rangeLabel->setPixmap(generateColorRamp()); ui.rangeLabel->setMaximumHeight(10); ui.rangeLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); ui.rangeLabel->setScaledContents(true); ui.minCountSlider->setMaximumHeight(12); int i; for(i=0; i<er->modelList.size() ;++i) { QString ThumbImgName=Arc3DModel::ThumbName(er->modelList[i].textureName); QString ThumbCntName=Arc3DModel::ThumbName(er->modelList[i].countName); ui.imageTableWidget->setRowHeight (i,64); QTableWidgetItem *emptyHeaderItem = new QTableWidgetItem(i*2); QTableWidgetItem *texNameHeaderItem = new QTableWidgetItem(er->modelList[i].textureName,i); ui.imageTableWidget->setItem(i, 0, texNameHeaderItem); ui.imageTableWidget->setItem(i, 1, emptyHeaderItem); QLabel *imageLabel = new QLabel(ui.imageTableWidget); if(!QFile::exists(ThumbImgName)) { QPixmap(er->modelList[i].textureName).scaledToHeight(64).save(ThumbImgName,"jpg"); if(!QFile::exists(ThumbImgName)) QMessageBox::warning(this,"Error in Thumb creation", QString("Unable to create '%1' from '%2'").arg(ThumbImgName),er->modelList[i].textureName); } imageLabel->setPixmap(QPixmap(Arc3DModel::ThumbName(er->modelList[i].textureName))); ui.imageTableWidget->setCellWidget(i,1,imageLabel); if(QFile::exists(er->modelList[i].maskName)) { QTableWidgetItem *emptyHeaderItem = new QTableWidgetItem(i*4); ui.imageTableWidget->setItem(i, 2, emptyHeaderItem); QLabel *maskLabel = new QLabel(ui.imageTableWidget); maskLabel->setPixmap(QPixmap(er->modelList[i].maskName).scaledToHeight(64)); ui.imageTableWidget->setCellWidget(i,2,maskLabel); } else { QTableWidgetItem *maskHeaderItem = new QTableWidgetItem(QString("double click for\nediting the mask"),i*3); ui.imageTableWidget->setItem(i, 2, maskHeaderItem); } if(!QFile::exists(ThumbCntName)) { CharImage chi; bool ret=chi.Open(er->modelList[i].countName.toUtf8().data()); if(!ret) QMessageBox::warning(this,"Error in Thumb creation",QString("Unable to create '%1' from '%2'").arg(ThumbCntName,er->modelList[i].textureName)); CharImage::colorizedScaledToHeight(64,chi).save(ThumbCntName,"jpg"); if(!QFile::exists(ThumbCntName)) QMessageBox::warning(this,"Error in Thumb creation",QString("Unable to create '%1' from '%2'").arg(ThumbCntName,er->modelList[i].textureName)); } QLabel *countLabel = new QLabel(ui.imageTableWidget); countLabel->setPixmap(QPixmap(ThumbCntName)); QPixmap tmp(ThumbCntName); if(tmp.isNull()) QMessageBox::warning(this,"Error in Thumb creation",QString("Null Pixmap '%1'").arg(ThumbCntName)); ui.imageTableWidget->setCellWidget(i,3,countLabel); } show(); // necessary to make the size of the image preview correct. ui.imageTableWidget->setItemSelected(ui.imageTableWidget->item(0,0),true); ui.imageTableWidget->setItemSelected(ui.imageTableWidget->item(0,1),true); ui.imageTableWidget->setItemSelected(ui.imageTableWidget->item(0,2),true); }
Point3m Arc3DModel::TraCorrection(CMeshO &m, int subsampleFactor, int minCount, int smoothSteps) { FloatImage depthImgf; CharImage countImgc; depthImgf.Open(depthName.toUtf8().data()); countImgc.Open(countName.toUtf8().data()); QImage TextureImg; TextureImg.load(textureName); CombineHandMadeMaskAndCount(countImgc,maskName); // set count to zero for all masked points FloatImage depthSubf; // the subsampled depth image FloatImage countSubf; // the subsampled quality image (quality == count) SmartSubSample(subsampleFactor,depthImgf,countImgc,depthSubf,countSubf,minCount); CharImage FeatureMask; // the subsampled image with (quality == features) GenerateGradientSmoothingMask(subsampleFactor, TextureImg, FeatureMask); depthSubf.convertToQImage().save("tmp_depth.jpg", "jpg"); float depthThr = ComputeDepthJumpThr(depthSubf,0.8f); for(int ii=0;ii<smoothSteps;++ii) Laplacian2(depthSubf,countSubf,minCount,FeatureMask,depthThr); vcg::tri::Grid<CMeshO>(m,depthSubf.w,depthSubf.h,depthImgf.w,depthImgf.h,&*depthSubf.v.begin()); // The depth is filtered and the minimum count mask is update accordingly. // To be more specific the border of the depth map are identified by erosion // and the relative vertex removed (by setting mincount equal to 0). ComputeDepthJumpThr(depthSubf,0.95f); int vn = m.vn; for(int i=0;i<vn;++i) if(countSubf.v[i]<minCount) { m.vert[i].SetD(); m.vn--; } cam.Open(cameraName.toUtf8().data()); CMeshO::VertexIterator vi; Matrix33d Rinv= Inverse(cam.R); Point3m correction(0.0,0.0,0.0); int numSamp=0; for(vi=m.vert.begin();vi!=m.vert.end();++vi)if(!(*vi).IsD()) { Point3m in=(*vi).P(); Point3d out; correction+=cam.DepthTo3DPoint(in[0], in[1], in[2], out); numSamp++; } if (numSamp!=0) correction/=(double)numSamp; return correction; }
bool Arc3DModel::BuildMesh(CMeshO &m, int subsampleFactor, int minCount, float minAngleCos, int smoothSteps, bool dilation, int dilationPasses, int dilationSize, bool erosion, int erosionPasses, int erosionSize,float scalingFactor) { FloatImage depthImgf; CharImage countImgc; clock(); depthImgf.Open(depthName.toUtf8().data()); countImgc.Open(countName.toUtf8().data()); QImage TextureImg; TextureImg.load(textureName); clock(); CombineHandMadeMaskAndCount(countImgc,maskName); // set count to zero for all masked points FloatImage depthSubf; // the subsampled depth image FloatImage countSubf; // the subsampled quality image (quality == count) SmartSubSample(subsampleFactor,depthImgf,countImgc,depthSubf,countSubf,minCount); CharImage FeatureMask; // the subsampled image with (quality == features) GenerateGradientSmoothingMask(subsampleFactor, TextureImg, FeatureMask); depthSubf.convertToQImage().save("tmp_depth.jpg", "jpg"); clock(); float depthThr = ComputeDepthJumpThr(depthSubf,0.8f); for(int ii=0;ii<smoothSteps;++ii) Laplacian2(depthSubf,countSubf,minCount,FeatureMask,depthThr); clock(); vcg::tri::Grid<CMeshO>(m,depthSubf.w,depthSubf.h,depthImgf.w,depthImgf.h,&*depthSubf.v.begin()); clock(); // The depth is filtered and the minimum count mask is update accordingly. // To be more specific the border of the depth map are identified by erosion // and the relative vertex removed (by setting mincount equal to 0). float depthThr2 = ComputeDepthJumpThr(depthSubf,0.95f); depthFilter(depthSubf, countSubf, depthThr2, dilation, dilationPasses, dilationSize, erosion, erosionPasses, erosionSize); int vn = m.vn; for(int i=0;i<vn;++i) if(countSubf.v[i]<minCount) { m.vert[i].SetD(); m.vn--; } cam.Open(cameraName.toUtf8().data()); CMeshO::VertexIterator vi; Matrix33d Rinv= Inverse(cam.R); for(vi=m.vert.begin();vi!=m.vert.end();++vi)if(!(*vi).IsD()) { Point3m in=(*vi).P(); Point3d out; cam.DepthTo3DPoint(in[0], in[1], in[2], out); (*vi).P().Import(out); QRgb c = TextureImg.pixel(int(in[0]), int(in[1])); vcg::Color4b tmpcol(qRed(c),qGreen(c),qBlue(c),0); (*vi).C().Import(tmpcol); if(FeatureMask.Val(int(in[0]/subsampleFactor), int(in[1]/subsampleFactor))<200) (*vi).Q()=0; else (*vi).Q()=1; (*vi).Q()=float(FeatureMask.Val(in[0]/subsampleFactor, in[1]/subsampleFactor))/255.0; } clock(); CMeshO::FaceIterator fi; Point3m CameraPos = Point3m::Construct(cam.t); for(fi=m.face.begin();fi!=m.face.end();++fi) { if((*fi).V(0)->IsD() ||(*fi).V(1)->IsD() ||(*fi).V(2)->IsD() ) { (*fi).SetD(); --m.fn; } else { Point3m n=vcg::TriangleNormal(*fi); n.Normalize(); Point3m dir=CameraPos-vcg::Barycenter(*fi); dir.Normalize(); if(dir.dot(n) < minAngleCos) { (*fi).SetD(); --m.fn; } } } tri::Clean<CMeshO>::RemoveUnreferencedVertex(m); clock(); Matrix44m scaleMat; scaleMat.SetScale(scalingFactor,scalingFactor,scalingFactor); vcg::tri::UpdatePosition<CMeshO>::Matrix(m, scaleMat); return true; }
void Arc3DModel::GenerateGradientSmoothingMask(int subsampleFactor, QImage &OriginalTexture, CharImage &mask) { CharImage gray(OriginalTexture); CharImage grad; grad.resize(gray.w,gray.h); int w=gray.w,h=gray.h; for(int x=1;x<w-1;++x) for(int y=1;y<h-1;++y) { int dx=abs(int(gray.Val(x,y))-int(gray.Val(x-1,y))) + abs(int(gray.Val(x,y))-int(gray.Val(x+1,y))); int dy=abs(int(gray.Val(x,y))-int(gray.Val(x,y-1))) + abs(int(gray.Val(x,y))-int(gray.Val(x,y+1))); grad.Val(x,y)=min(255,16*dx+dy); } // create subsampled mask int ws=gray.w/subsampleFactor, hs=gray.h/subsampleFactor; mask.resize(ws,hs); for(int x=0;x<ws;++x) for(int y=0;y<hs;++y) { unsigned char maxGrad=0; for(int si=0;si<subsampleFactor;++si) for(int sj=0;sj<subsampleFactor;++sj) maxGrad = max(maxGrad, grad.Val(x*subsampleFactor+sj,y*subsampleFactor+si)); mask.Val(x,y) = maxGrad; } CharImage mask2; mask2.resize(ws, hs); // average filter (11 x 11) int avg; int wsize = 5; for (int y = wsize; y < hs-wsize; y++) for (int x = wsize; x < ws-wsize; x++) { avg = 0; for (int yy = y - wsize; yy <= y + wsize; yy++) for (int xx = x - wsize; xx <= x + wsize; xx++) avg += mask.Val(xx, yy); mask2.Val(x, y) = min(255, avg / ((2 * wsize + 1)* (2 * wsize +1))); } mask.convertToQImage().save("tmp_testmask.jpg","jpg"); mask2.convertToQImage().save("tmp_testmaskSmooth.jpg","jpg"); // erosion filter (7 x 7) int minimum; wsize = 3; for (int y = wsize; y < hs-wsize; y++) for (int x = wsize; x < ws-wsize; x++) { minimum = mask2.Val(x, y); for (int yy = y - wsize; yy <= y + wsize; yy++) for (int xx = x - wsize; xx <= x + wsize; xx++) if (mask2.Val(xx, yy) < minimum) minimum = mask2.Val(xx, yy); mask.Val(x, y) = minimum; } grad.convertToQImage().save("tmp_test.jpg","jpg"); mask.convertToQImage().save("tmp_testmaskeroded.jpg","jpg"); }