From afc88ff99838d8ca6137badcc3362f19134cb467 Mon Sep 17 00:00:00 2001 From: deseilligny Date: Fri, 30 Aug 2024 16:07:11 +0200 Subject: [PATCH] Refactor --- MMVII/Doc/Methods/CodedTarget-Theory.tex | 85 +- .../CodedTarget/cCdts_CheckBoardTarget.cpp | 830 +++++++++++ .../CodedTarget/cCheckBoardTargetExtract.cpp | 1218 +---------------- .../CodedTarget/cCheckBoardTargetExtract.h | 386 ++++++ 4 files changed, 1309 insertions(+), 1210 deletions(-) create mode 100755 MMVII/src/CodedTarget/cCdts_CheckBoardTarget.cpp create mode 100755 MMVII/src/CodedTarget/cCheckBoardTargetExtract.h diff --git a/MMVII/Doc/Methods/CodedTarget-Theory.tex b/MMVII/Doc/Methods/CodedTarget-Theory.tex index 6cabd8752..1fe7b208d 100644 --- a/MMVII/Doc/Methods/CodedTarget-Theory.tex +++ b/MMVII/Doc/Methods/CodedTarget-Theory.tex @@ -462,10 +462,93 @@ \subsection{Code extraction and validation} \end{figure} - % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%----------------------------------------------------------------------- +%----------------------------------------------------------------------- +%----------------------------------------------------------------------- + +\section{Checkboard target detection} + +This section mixes methologicall aspects and reference to the implementation +(names {\tt files}, names of {\tt classes, methods} \dots). + + %----------------------------------------------------------------------- + +\subsection{Global architecture} + +The main steps of the methods are : + +\begin{enumerate} + \item find potential center of checkboard , this is done on neigboorhing small enough not + to include the limit of ellipse ; + + \item estimate the direction of $2$ axes (still on neigboorhing small enough) , by sweeping direction arround + the center; + + \item estimate the local value of black and white on small neighbourhood; + + \item estimate the black points of checkboard by a component analyis (of point under gray threshold) ; + then estimate point of ellipse frontier caracterised as frontier point of the connected component that are not + close to the axes; + + \item estimate the ellipse (with center fix) from the frontier point by least square, + then estimate the corner of the checkboard by intersection of the ellipse and the axes, + then estimate the affinity between image and reference target; + + \item finaly decode \dots + +\end{enumerate} + + + %----------------------------------------------------------------------- + +\subsection{Find centers} +The computation of potential centers is made by application of successive criteria with +progressive elimination of candidate by some thresholdings : + + +\begin{enumerate} + \item compute the points that are "tologically" saddle points (see~\ref{TopoSadlePoint}); + + \item refine the position of these points by fitting a quadratic function on image, + and extract the null gradient point; + compute the curvature; + + \item use the curvature criteria to select a subset of point on global and local criteria; + + \item compute a symetry criteria, use-it to refine the position and select point on a thresholds on + this criteria. +\end{enumerate} + +Note the implementation use a series of classes included like \emph{matriochka} (russian dolls) ; + + +\begin{enumerate} + + \item the smallest \emph{doll} is class {\tt cCdSadle} that represent candidate selected + at first level as saddle point criteria; + + \item the we have {\tt cCdSym} that represent a candidate selected on symetry criteria, + a {\tt cCdSym} is also a {\tt cCdSadle}, + and we have in the implementation + "{\tt cCdSym} \emph{inherits} of {\tt cCdSadle}" + \footnote{what we write {\tt cCdSadle} $\prec$ {\tt cCdSym} } + + \item the scheme is followed all allong the progression; + + \item globally we have {\tt cCdSadle} $\prec$ {\tt cCdSym} $\prec$ {\tt cCdRadiom} + $\prec$ {\tt cCdEllipse } $\prec$ {\tt cCdMerged } . + + + +\end{enumerate} + + + % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +\subsubsection{tological saddle points} +\label{TopoSadlePoint} diff --git a/MMVII/src/CodedTarget/cCdts_CheckBoardTarget.cpp b/MMVII/src/CodedTarget/cCdts_CheckBoardTarget.cpp new file mode 100755 index 000000000..bebe59340 --- /dev/null +++ b/MMVII/src/CodedTarget/cCdts_CheckBoardTarget.cpp @@ -0,0 +1,830 @@ +#include "cCheckBoardTargetExtract.h" + +namespace MMVII +{ + +namespace NS_CHKBRD_TARGET_EXTR { + + +/* ================================================================= */ + + +/* **************************************************** */ +/* cCdSadle */ +/* **************************************************** */ + +int cCdSadle::TheCptNum=0; +int cCdSadle::TheNum2Debug=-2; + +cCdSadle::cCdSadle (const cPt2dr & aC,tREAL8 aCrit,bool isPTest) : + mC (aC) , + mSadCrit (aCrit) , + mIsPTest (isPTest), + mNum (TheCptNum++) +{ +} +cCdSadle::cCdSadle () : + mNum (-1) +{ +} + +bool cCdSadle::Is4Debug() const {return mNum == TheNum2Debug;} + + + + +/* ***************************************************** */ +/* */ +/* cCdRadiom */ +/* */ +/* ***************************************************** */ + +cCdRadiom::cCdRadiom +( + const cAppliCheckBoardTargetExtract * anAppli, + const cCdSym & aCdSym, + const cDataIm2D & aDIm, + tREAL8 aTeta0, + tREAL8 aTeta1, + tREAL8 aLength, + tREAL8 aThickness +) : + cCdSym (aCdSym), + mAppli (anAppli), + mIsOk (false), + mDIm (&aDIm), + mTetas {aTeta0,aTeta1}, + mLength (aLength), + mThickness (aThickness), + mCostCorrel (2.001), // over maximal theoreticall value + mRatioBW (0) +{ + static int aCpt=0 ; aCpt++; + + int aNbIn0=0,aNbIn1=0; + + cMatIner2Var aCorGrayAll; + cMatIner2Var aCorGrayInside; + + cTmpCdRadiomPos aCRC(*this,aThickness); + + std::vector aVPixEllipse; + ComputePtsOfEllipse(aVPixEllipse); + for (const auto & aPImI : aVPixEllipse) + { + tREAL8 aValIm = aDIm.GetV(aPImI); + cPt2dr aPImR = ToR(aPImI); + + auto [aState,aGrayTh] = aCRC.TheorRadiom(aPImR); + + if (IsInside(aState)) + { + aCorGrayInside.Add(aGrayTh,aValIm); + aNbIn0 += (aState == eTPosCB::eInsideBlack); + aNbIn1 += (aState == eTPosCB::eInsideWhite); + } + if (IsOk(aState)) + { + aCorGrayAll.Add(aGrayTh,aValIm); + } + } + + if ((aNbIn0==0) && (aNbIn1==0)) + return; + + mRatioBW = std::min(aNbIn0,aNbIn1) / (tREAL8) std::max(aNbIn0,aNbIn1); + if (mRatioBW <0.05) + { + return ; + } + + mIsOk = true; + + mCostCorrel = 1-aCorGrayAll.Correl(); + auto [a,b] = aCorGrayInside.FitLineDirect(); + mBlack = b ; + mWhite = a+b; +} + +tREAL8 cCdRadiom::Threshold(tREAL8 aWW) const +{ + return mBlack*(1-aWW) + mWhite *aWW; +} + +void cCdRadiom::OptimSegIm(const cDataIm2D & aDIm,tREAL8 aLength) +{ + // StdOut() << "TttTT=" << Threshold() << " " << mBlack << " " << mWhite << " " << mRatioBW << "\n"; + + std::vector> aVSegOpt; + for (int aKTeta=0 ; aKTeta<2 ; aKTeta++) + { + cPt2dr aTgt = FromPolar(aLength,mTetas[aKTeta]); + tSeg2dr aSegInit(mC-aTgt,mC+aTgt); + cOptimSeg_ValueIm aOSVI(aSegInit,0.5,aDIm,Threshold()); + tSeg2dr aSegOpt = aOSVI.OptimizeSeg(0.5,0.01,true,2.0); + + aVSegOpt.push_back(aSegOpt); + mTetas[aKTeta] = Teta(aSegOpt.V12()); + // mTetas[aKTeta] = aSegOpt.I// + } + + cPt2dr aC = aVSegOpt.at(0).InterSeg(aVSegOpt.at(1)); + + mC = aC; + cScoreTetaLine::NormalizeTetaCheckBoard(mTetas); +} + +void cCdRadiom::ComputePtsOfEllipse(std::vector & aRes) const +{ + ComputePtsOfEllipse(aRes,mLength); +} + + +void cCdRadiom::ComputePtsOfEllipse(std::vector & aRes,tREAL8 aLength) const +{ + aRes.clear(); + // [1] Compute the affinity that goes from unity circle to ellipse + // ---- x,y -> mC + x V0 + y V1 ------ + cPt2dr aV0 = FromPolar(aLength,mTetas[0]); + cPt2dr aV1 = FromPolar(aLength,mTetas[1]); + + cAff2D_r aMapEll2Ori(mC,aV0,aV1); + cAff2D_r aMapOri2Ell = aMapEll2Ori.MapInverse(); + + // [2] Compute the bounding box containing the ellipse + cTplBoxOfPts aBox; + int aNbTeta = 100; + for (int aKTeta=0 ; aKTeta aSeg(mC,mC+FromPolar(1.0,aTeta)); + + cPt2dr aPLoc = aSeg.ToCoordLoc(aPAbs); + + if (std::abs(aPLoc.y()) <= 1.0 + std::abs(aPLoc.x()) /30.0) + return true; + + return false; +} + +bool cCdRadiom::PtIsOnEllAndRefine(cPt2dr & aPtAbs) const +{ + // point must be far enough of center (because close to, it's not easily separable) + if (Norm2(aPtAbs - mC)<3.0) + return false; + + // point canot be a point of the line + for (const auto & aTeta : mTetas ) + if (PtIsOnLine(aPtAbs,aTeta)) + return false; + + // extract the point that has the gray threshold (assuming gray starting point is bellow) + cGetPts_ImInterp_FromValue aGIFV(*mDIm,Threshold(),0.1,aPtAbs, VUnit(aPtAbs - mC)); + cPt2dr aNewP = aPtAbs; + if (aGIFV.Ok()) + { + // if interpoleted point is to far from initial : suscpicious + aNewP = aGIFV.PRes(); + if (Norm2(aPtAbs-aNewP)>2.0) + return false; + + cPt2dr aPGr = Proj(mDIm->GetGradAndVBL(aNewP)); + // StdOut() << "PGR=== " << aPGr << "\n"; + tREAL8 aSc = std::abs(CosWDef(aPGr,aNewP-mC,1.0)); + if (aSc<0.5) + return false; + + aPtAbs = aNewP; + } + else + return false; + + + return true; +} + +void cCdRadiom::SelEllAndRefineFront(std::vector & aRes,const std::vector & aFrontI) const +{ + aRes.clear(); + for (const auto & aPix : aFrontI) + { + cPt2dr aRPix = ToR(aPix); + if (PtIsOnEllAndRefine(aRPix)) + aRes.push_back(aRPix); + } +} + +bool cCdRadiom::FrontBlackCC(std::vector & aVFront,cDataIm2D & aDMarq,int aNbMax) const +{ + std::vector aRes; + aVFront.clear(); + + std::vector aVPtsEll; + ComputePtsOfEllipse(aVPtsEll,std::min(mLength,5.0)); + + tREAL8 aThrs = Threshold(); + for (const auto & aPix : aVPtsEll) + { + if (mDIm->GetV(aPix) & aV4 = Alloc4Neighbourhood(); + + cRect2 aImOk(aDMarq.Dilate(-10)); + bool isOk = true; + + while ((aIndBot != aRes.size()) && isOk) + { + for (const auto & aDelta : aV4) + { + cPt2di aPix = aRes.at(aIndBot) + aDelta; + if ((aDMarq.GetV(aPix)==0) && (mDIm->GetV(aPix) & aV8 = Alloc8Neighbourhood(); + // compute frontier points + for (const auto & aPix : aRes) + { + bool has8NeighWhite = false; + for (const auto & aDelta : aV8) + { + if (aDMarq.GetV(aPix+aDelta)==0) + has8NeighWhite = true; + } + + if (has8NeighWhite) + { + aVFront.push_back(aPix); + } + } + if (false && Is4Debug()) + { + StdOut() << "--xxx--HASH " << HashValue(aVFront,true) << " SZCC=" << aRes.size() + << " HELLL=" << HashValue(aVPtsEll,true) + << " HELLL=" << HashValue(aVPtsEll,true) + << " C=" << mC << " Thr=" << aThrs + << " L=" << mLength << "\n"; + } + // StdOut() << "FFFF=" << aVFront << "\n"; + + for (const auto & aPix : aRes) + aDMarq.SetV(aPix,0); + + return isOk; +} + + + + // if (has8NeighWhite && PtIsOnEll(aRPix)) + +void cCdRadiom::ShowDetail + ( + int aCptMarq, + const cScoreTetaLine & aSTL, + const std::string & aNameIm, + cDataIm2D & aMarq, + cFullSpecifTarget *aSpec + ) const +{ + cCdEllipse aCDE(*this,aMarq,-1,false); + if (! aCDE.IsOk()) + { + StdOut() << " @@@@@@@@@@@@@@@@@@@@@@@@@@@@ " << aCptMarq << "\n"; + return; + } + + std::pair aPairTeta(mTetas[0],mTetas[1]); + + aCDE.DecodeByL2CP(2.0/3.0); + StdOut() << " CptMarq=" << aCptMarq + << " NUM=" << mNum + << " Corrrr=" << mCostCorrel + << " Ratio=" << mRatioBW + << " V0="<< mBlack << " V1=" << mWhite + << " ScTeta=" << aSTL.Score2Teta(aPairTeta,2.0) + << " ScSym=" << mSymCrit + << " LLL=" << mLength + << " ThickN=" << mThickness + << " CODE=[" << aCDE.Code() << "]" + << " C=" << mC + << " DELL=" << aCDE.MaxEllD() + << " OK=" << aCDE.mIsOk + << " OUTCB=" << aCDE.BOutCB() + << "\n"; +} + +/* ***************************************************** */ +/* */ +/* cCdEllipse */ +/* */ +/* ***************************************************** */ + +void cCdEllipse::GenImageFail(const std::string & aWhyFail) +{ + static int aCpt=0; + cRGBImage aIm = mAppli->GenImaRadiom(*this); + StdOut() << "Fail for Num=" << mNum << " Cpt=" << aCpt << " reason=" << aWhyFail << "\n"; + aIm.ToJpgFileDeZoom(mAppli->NameVisu("Failed"+ aWhyFail,ToStr(aCpt++)),1); +} + + +cCdEllipse::cCdEllipse(const cCdRadiom & aCdR,cDataIm2D & aMarq,int aNbMax,bool isCircle) : + cCdRadiom (aCdR), + mSpec (mAppli->Specif()), + mEll (cPt2dr(0,0),0,1,1), + mMaxEllD (0.0), + mCode (nullptr), + mBOutCB (false), + mIsCircle (isCircle) +{ + + if (! mIsOk) + { + if(mIsPTest) StdOut() << "Ref cCdEllipse at L=" << __LINE__ << "\n" ; + return; + } + mIsOk = true; + std::vector aIFront; + mIsOk = FrontBlackCC(aIFront,aMarq,aNbMax); + + if (! mIsOk) + { + if(mIsPTest) StdOut() << "Ref cCdEllipse at L=" << __LINE__ << "\n" ; + return; + } + + { + cTmpCdRadiomPos aCRP(*this,1.0); + for (const auto & aPix : aIFront) + { + auto [aPos,aGray] = aCRP.TheorRadiom(ToR(aPix),2.0,1/20.0); + if (aPos == eTPosCB::eInsideWhite) + { + mBOutCB = true; + } + } + if (mBOutCB) + { + if(mIsPTest) StdOut() << "Ref cCdEllipse at L=" << __LINE__ << "\n" ; + mIsOk = false; + return; + } + } + + std::vector aEllFr; + SelEllAndRefineFront(aEllFr,aIFront); + + if ((int) aEllFr.size() < (mIsCircle ? 2 : mAppli->NbMinPtEllipse()) ) + { + if (mIsPTest) + GenImageFail("NbEllipse"); + mIsOk = false; + return; + } + + mIsOk = true; + + cEllipse_Estimate anEE(mC,false,mIsCircle); + for (const auto & aPixFr : aEllFr) + { + anEE.AddPt(aPixFr); + } + + mEll = anEE.Compute(); + + if (!mEll.Ok()) + { + if(mIsPTest) + { + GenImageFail("BadEll"); + } + mIsOk = false; + return; + } + + tREAL8 aThrs = 0.6+ mEll.LGa()/40.0; + for (const auto & aPixFr : aEllFr) + { + tREAL8 aD = mEll.NonEuclidDist(aPixFr); + UpdateMax(mMaxEllD,aD); + if (aD>aThrs) + { + if(mIsPTest) StdOut() << "Ref cCdEllipse at L=" << __LINE__ << "\n" ; + mIsOk = false; + return; + } + } + + + // In specif the corner are for a white sector, with name of transition (B->W or W->B) + // coming with trigonometric convention; here the angle have been comouted for a black sector + // The correction for angle have been made experimentally ... + mCornerlEl_WB = mEll.InterSemiLine(mTetas[0]); + mCornerlEl_BW = mEll.InterSemiLine(mTetas[1]+M_PI); + + + + cAff2D_r::tTabMin aTabIm{mC,mCornerlEl_WB,mCornerlEl_BW}; + cAff2D_r::tTabMin aTabMod{mSpec->Center(),mSpec->CornerlEl_WB(),mSpec->CornerlEl_BW()}; + + mAffIm2Mod = cAff2D_r::FromMinimalSamples(aTabIm,aTabMod); + // mAffIm2Mod +} + +bool cCdEllipse::BOutCB() const {return mBOutCB;} +bool cCdEllipse::IsCircle() const {return mIsCircle;} + +bool cCdEllipse::IsOk() const {return mIsOk;} +void cCdEllipse::AssertOk() const +{ + MMVII_INTERNAL_ASSERT_tiny(mIsOk,"No ellipse Ok in cCdEllipse"); +} + +cPt2dr cCdEllipse::M2I(const cPt2dr & aPMod) const +{ + AssertOk(); + return mAffIm2Mod.Inverse(aPMod); +} + +cPt2dr cCdEllipse::I2M(const cPt2dr & aPMod) const +{ + AssertOk(); + return mAffIm2Mod.Value(aPMod); +} + + +const cOneEncoding * cCdEllipse::Code() const {return mCode;} +tREAL8 cCdEllipse::MaxEllD() const {return mMaxEllD;} + +const cEllipse & cCdEllipse::Ell() const {AssertOk(); return mEll;} +const cPt2dr & cCdEllipse::CornerlEl_WB() const {AssertOk(); return mCornerlEl_WB;} +const cPt2dr & cCdEllipse::CornerlEl_BW() const {AssertOk(); return mCornerlEl_BW;} + +// tREAL8 cCdEllipse::ScoreCodeBlack(const cPt2dr& aDir,tREAL8 aRho, + +std::pair cCdEllipse::Length2CodingPart(tREAL8 aWeighWhite,const cPt2dr & aModCenterBit) const +{ + // Rho end search, 1.5 theoretical end of code , highly over estimated (no risk ?) + tREAL8 aRhoMaxRel = mSpec->Rho_2_EndCode() * 1.5; + std::pair aNoValue(-1,cPt2dr(0,0)); + + // Not sur meaningful with other mode + MMVII_INTERNAL_ASSERT_tiny(mSpec->Type()==eTyCodeTarget::eIGNIndoor,"Bad code in Length2CodingPart"); + + + cPt2dr aDirModel = VUnit(mSpec->Pix2Norm(aModCenterBit)); // -> normalize coord -> unitary vect + // distance , ratio on the line mC-Pt, between a normalized model pixel, and it corresponding image pixel + tREAL8 aMulN2I = Norm2(M2I(mSpec->Norm2Pix(aDirModel))-mC); + // + cPt2dr aDirIm = VUnit(M2I(aModCenterBit)-mC); // direction of line in image + + // Rho begin search in image, at mid position between check board and begining of code + tREAL8 aRho0 = aMulN2I * ((mSpec->Rho_0_EndCCB()+mSpec->Rho_1_BeginCode()) / 2.0); + + // Rho end search, 1.5 theoretical end of code , highly over estimated (no risk ?) + tREAL8 aRho1 = aMulN2I * aRhoMaxRel; + // step of research, overly small (no risk ?) + tREAL8 aStepRho = 0.2; + int aNbRho = round_up((aRho1-aRho0) / aStepRho); + aStepRho = (aRho1-aRho0) / aNbRho; + + // threshold for value computing the black code + tREAL8 aThresh = aWeighWhite * mWhite + (1-aWeighWhite) * mBlack; + + for (int aKRho = 0 ; aKRho<=aNbRho ; aKRho++) + { + tREAL8 aRho = aRho0 + aKRho * aStepRho; + cPt2dr aPt = mC + aDirIm * aRho; + + if (!mDIm->InsideBL(aPt)) // we are too far, end of game + { + return aNoValue; + } + else + { + auto [aVal,aGrad] = mDIm->GetPairGradAndVBL(aPt); + // we test also orientation of gradient, because with very small target, there is no clear separation , + // and we dont want to accept the checkboard as a + if ((aVal < aThresh) && (Scal(aGrad,aDirIm)<0)) + { + return std::pair (aRho/aMulN2I,aPt); + } + } + } + std::pair aDef(aRhoMaxRel,mC + aDirIm*aRhoMaxRel*aMulN2I); + + return aDef; +} + +tREAL8 cCdEllipse::ComputeThresholdsByRLE(const std::vector> & aVBR) const +{ + // cPt2di MaxRunLength(tU_INT4 aVal,size_t aPow2); + + + int aMaxWL = mSpec->Specs().mMaxRunL.x(); // Max run lenght for 0 (= white in general, 2 adapt ...) + aMaxWL = std::min(aMaxWL,(int)mSpec->NbBits()); + + size_t aFlag = 0; + int aRunLE = mSpec->NbBits(); + size_t aKBit = 0; + while ((aMaxWLNbBits()).x(); + } + + return aVBR.at(aKBit-1).second * 1.2; + +} + +tREAL8 cCdEllipse::ComputeThresholdsMin(const std::vector> & aVBR) const +{ + tREAL8 aV0 = aVBR[0].second; + + return aV0 * 1.25; +} + + +void cCdEllipse::DecodeByL2CP(tREAL8 aWeighWhite) +{ + std::vector > aVBR; // Vector Bits/Rho + const auto & aVC = mSpec->BitsCenters(); // Vector centers + for (size_t aKBit=0 ; aKBit(aKBit,aRho)); + } + + SortOnCriteria(aVBR,[](const auto & aPair) {return aPair.second;}); + tREAL8 aRhoMin = aVBR.at(0).second; + + + if (0) + { + std::vector > aDup = aVBR; + SortOnCriteria(aDup,[](const auto & aPair) {return aPair.first;}); + for (const auto & [aBit,aRho] : aDup) + StdOut() << " RRR=" << aRho/aRhoMin << "\n"; + } + + // tREAL8 aTreshold = ComputeThresholdsMin(aVBR); + tREAL8 aTreshold = ComputeThresholdsByRLE(aVBR); + + cDecodeFromCoulBits aDec(mSpec); + for (const auto & [aBit,aRho] : aVBR) + { + aDec.SetColBit(aRhoBitsCenters(); + tREAL8 aThrs = Threshold(aWW); + + for (size_t aKBit=0 ; aKBitInsideBL(aPIm)) + { + aDec.SetColBit(mDIm->GetVBL(aPIm) < aThrs , aKBit); + } + else + return nullptr; + } + return aDec.Encoding(); +} + + +/* ***************************************************** */ +/* */ +/* cTmpCdRadiomPos */ +/* */ +/* ***************************************************** */ + + +cTmpCdRadiomPos::cTmpCdRadiomPos(const cCdRadiom & aCDR,tREAL8 aThickness) : + cCdRadiom (aCDR), + mThickness (aThickness), + mSeg0 (mC,mC+FromPolar(1.0,mTetas[0])), + mSeg1 (mC,mC+FromPolar(1.0,mTetas[1])) +{ +} + +std::pair cTmpCdRadiomPos::TheorRadiom(const cPt2dr &aPt,tREAL8 aThickInit,tREAL8 aSteep) const +{ + eTPosCB aPos = eTPosCB::eUndef; + tREAL8 aGrayTh = -1; + + // we compute locacl coordinates because the sign of y indicate if we are left/right of the oriented segment + // and sign of x indicate if we are before/after the centre + cPt2dr aLoc0 = mSeg0.ToCoordLoc(aPt); + tREAL8 aY0 = aLoc0.y(); + tREAL8 aThick0 = aThickInit + aSteep * std::abs(aLoc0.x()); + + cPt2dr aLoc1 = mSeg1.ToCoordLoc(aPt); + tREAL8 aY1 = aLoc1.y(); + tREAL8 aThick1 = aThickInit + aSteep * std::abs(aLoc1.x()); + + // compute if we are far enough of S0/S1 because the computation of gray will change + // black/white if far enough, else interpolation + bool FarS0 = std::abs(aY0)> aThick0; + bool FarS1 = std::abs(aY1)> aThick1; + + if ( FarS0 && FarS1) + { + if ((aY0>0)!=(aY1>0)) + { + aPos = eTPosCB::eInsideBlack; + aGrayTh = 0.0; + } + else + { + aPos = eTPosCB::eInsideWhite; + aGrayTh = 1.0; + } + } + else if ((!FarS0) && FarS1) + { + // (! FarS0) => teta1 + // Red = teta1 , black on left on image, right on left in coord oriented + aPos = eTPosCB::eBorderRight; + int aSignX = (aLoc0.x() >0) ? -1 : 1; + aGrayTh = (aThick0+aSignX*aY0) / (2.0*aThick0); + } + else if (FarS0 && (!FarS1)) + { + aPos = eTPosCB::eBorderLeft; + int aSignX = (aLoc1.x() <0) ? -1 : 1; + aGrayTh = (aThick1+aSignX*aY1) / (2.0 * aThick1); + } + + return std::pair(aPos,aGrayTh); +} + +std::pair cTmpCdRadiomPos::TheorRadiom(const cPt2dr &aPt) const +{ + return TheorRadiom(aPt,mThickness,0.0); +} + +/* ********************************************* */ +/* */ +/* cOptimPosCdM */ +/* */ +/* ********************************************* */ + +class cOptimPosCdM : public cDataMapping +{ + public : + cOptimPosCdM(const cCdMerged & aCdM,const cInterpolator1D & ); + + cPt1dr Value(const cPt2dr & ) const override; + typedef cSegment2DCompiled tSeg; + + private : + void AddPts(const cPt2dr & aMaster, const cPt2dr & aSecond,bool toAvoid2); + + const cCdMerged& mCdM; + const cInterpolator1D & mCurInt; + std::vector mPtsOpt; +}; + + + + +cPt1dr cOptimPosCdM::Value(const cPt2dr & aDelta ) const +{ + cSymMeasure aSymM; // + cPt2dr a2NewC = (mCdM.mC0 + aDelta) * 2.0; + + const cDataIm2D & aDIm = *(mCdM.mDIm0); + for (const auto & aP1 : mPtsOpt) + { + cPt2dr aP2 = a2NewC - aP1; + if (aDIm.InsideInterpolator(mCurInt,aP1) && aDIm.InsideInterpolator(mCurInt,aP2)) + aSymM.Add(aDIm.GetValueInterpol(mCurInt,aP1),aDIm.GetValueInterpol(mCurInt,aP2)); + } + + return cPt1dr(aSymM.Sym(1e-5)); +} + + +cOptimPosCdM::cOptimPosCdM(const cCdMerged & aCdM,const cInterpolator1D & aInt) : + mCdM (aCdM), + mCurInt (aInt) +{ + AddPts(mCdM.CornerlEl_WB(), mCdM.CornerlEl_BW(),true); + AddPts(mCdM.CornerlEl_BW(), mCdM.CornerlEl_WB(),false); +} + +void cOptimPosCdM::AddPts(const cPt2dr & aSCorn1, const cPt2dr & aSCorn2,bool toAvoid2) +{ + cPt2dr aCorn1 = aSCorn1 * mCdM.mScale; + cPt2dr aCorn2 = aSCorn2 * mCdM.mScale; + + tREAL8 aStep = 0.25; + tREAL8 aWidth = 1.0; + tREAL8 aL1 = std::min(10.0,Norm2(aCorn1-mCdM.mC0)-1.0); + // cPt2dr aCorn2 = aSCorn2 * mScale; + + int aNbX = round_up(aL1/aStep); + tREAL8 aStepX = aL1 / aNbX; + + int aNbY = round_up(aWidth/aStep); + tREAL8 aStepY = aWidth / aNbY; + + tSeg aSeg1(mCdM.mC0,aCorn1); + tSeg aSeg2(mCdM.mC0,aCorn2); + + for (int aKX=-aNbX ; aKX<=aNbX ; aKX++) + { + for (int aKY=0 ; aKY<=aNbY ; aKY++) // KY=0 : we take only one point /2 + { + if ((aKY>0) || (aKX>0)) + { + cPt2dr aPLoc(aKX*aStepX,aKY*aStepY); + cPt2dr aPAbs = aSeg1.FromCoordLoc(aPLoc); + if ((!toAvoid2) || (aSeg2.DistLine(aPAbs) >aWidth)) + { + mPtsOpt.push_back(aPAbs); + // StdOut() << "PAAAA " << aPAbs - mCdM.mC0 << "\n"; + } + } + } + } +} + +/* ********************************************* */ +/* */ +/* cCdMerged */ +/* */ +/* ********************************************* */ + +cCdMerged::cCdMerged(const cDataIm2D * aDIm0,const cCdEllipse & aCDE,tREAL8 aScale) : + cCdEllipse (aCDE), + mScale (aScale), + mC0 (mC * mScale), + mDIm0 (aDIm0) +{ +} + +void cCdMerged::OptimizePosition(const cInterpolator1D & anInt,tREAL8 aStepEnd) +{ + cOptimPosCdM aCdtOpt(*this,anInt); + cOptimByStep anOpt(aCdtOpt,true,1.0); + auto [aVal,aDelta] = anOpt.Optim(cPt2dr(0,0),0.02,aStepEnd); + + mC0 = mC0 + aDelta; +} + + + +}; // =================== NS_CHKBRD_TARGET_EXTR +}; // =================== MMVII diff --git a/MMVII/src/CodedTarget/cCheckBoardTargetExtract.cpp b/MMVII/src/CodedTarget/cCheckBoardTargetExtract.cpp index 63a098ba6..9bd6acb4a 100755 --- a/MMVII/src/CodedTarget/cCheckBoardTargetExtract.cpp +++ b/MMVII/src/CodedTarget/cCheckBoardTargetExtract.cpp @@ -1,3 +1,4 @@ +/* #include "CodedTarget.h" #include "MMVII_2Include_Serial_Tpl.h" #include "MMVII_Tpl_Images.h" @@ -9,1218 +10,17 @@ #include "MMVII_HeuristikOpt.h" #include "MMVII_ExtractLines.h" #include "MMVII_TplImage_PtsFromValue.h" - - -namespace MMVII -{ - -extern bool DebugEll; -bool DebugCB = false; - -namespace NS_CHKBRD_TARGET_EXTR { - -class cAppliCheckBoardTargetExtract ; - -class cCdSadle; -class cCdSym; -class cCdRadiom; -class cTmpCdRadiomPos ; -class cCdEllipse ; - -static constexpr tU_INT1 eNone = 0 ; -static constexpr tU_INT1 eTopo0 = 1 ; -static constexpr tU_INT1 eTopoTmpCC = 2 ; -static constexpr tU_INT1 eTopoMaxOfCC = 3 ; -static constexpr tU_INT1 eTopoMaxLoc = 4 ; -static constexpr tU_INT1 eFilterSym = 5 ; -static constexpr tU_INT1 eFilterRadiom = 6 ; -static constexpr tU_INT1 eFilterEllipse = 7 ; -static constexpr tU_INT1 eFilterCodedTarget = 8 ; - - -/* **************************************************** */ -/* cCdSadle */ -/* **************************************************** */ - -/// candidate that are pre-selected on the sadle-point criteria -class cCdSadle -{ - public : - cCdSadle (const cPt2dr & aC,tREAL8 aCrit,bool isPTest) ; - cCdSadle (); - - bool Is4Debug() const; - - /// Center - cPt2dr mC; - - /// Criterion of sadle point, obtain by fiting a quadric on gray level - tREAL8 mSadCrit; - - /// Is it a point marked - bool mIsPTest; - - /// Num : 4 debug - int mNum; - /// Use 2 compute mNum - static int TheCptNum; - /// Use to have breakpoint at creation - static int TheNum2Debug; -}; -// -/// candidate that are pre-selected after symetry criterion -class cCdSym : public cCdSadle -{ - public : - cCdSym(const cCdSadle & aCdSad,tREAL8 aCrit) : cCdSadle (aCdSad), mSymCrit (aCrit) { } - /// Criterion of symetry, obtain after optimisation of center - tREAL8 mSymCrit; - -}; - -/// candidate obtain after radiometric modelization, - -class cCdRadiom : public cCdSym -{ - public : - /// Cstr use the 2 direction + Thickness of transition between black & white - cCdRadiom(const cAppliCheckBoardTargetExtract *,const cCdSym &,const cDataIm2D & aDIm,tREAL8 aTeta1,tREAL8 aTeta2,tREAL8 aLength,tREAL8 aThickness); - - /// Theoretical threshold - tREAL8 Threshold(tREAL8 aWhite= 0.5 ) const ; - /// Once black/white & teta are computed, refine seg using - void OptimSegIm(const cDataIm2D & aDIm,tREAL8 aLength); - - /// compute an ellipse that "safely" contains the points of checkboard - void ComputePtsOfEllipse(std::vector & aRes,tREAL8 aLength) const; - /// call previous with length - void ComputePtsOfEllipse(std::vector & aRes) const; - - /// compute an ellipse that contain - bool FrontBlackCC(std::vector & aRes,cDataIm2D & aMarq,int aNbMax) const; - /// Select point of front that are on ellipse - void SelEllAndRefineFront(std::vector & aRes,const std::vector &) const; - - - /// Is the possibly the point on arc of black ellipse - bool PtIsOnEll(cPt2dr &) const; - /// Is the point on the line for one of angles - bool PtIsOnLine(const cPt2dr &,tREAL8 aTeta) const; - - /// Make a visualisation of geometry - void ShowDetail(int aCptMarq,const cScoreTetaLine & aSTL,const std::string &,cDataIm2D & aMarq, cFullSpecifTarget *) const; - - const cAppliCheckBoardTargetExtract * mAppli; - bool mIsOk; - const cDataIm2D * mDIm; - - tREAL8 mTetas[2]; - tREAL8 mLength; ///< length of the lines - tREAL8 mThickness; ///< thickness of the transition B/W - - tREAL8 mCostCorrel; // 1-Correlation of model - tREAL8 mRatioBW; // ratio min/max of BW - tREAL8 mScoreTeta; // ratio min/max of BW - tREAL8 mBlack; - tREAL8 mWhite; - cPt2di mDec; ///< Shit for visu -}; - - -class cCdEllipse : public cCdRadiom -{ - public : - cCdEllipse(const cCdRadiom &,cDataIm2D & aMarq,int aNbMax,bool isCircle); - bool IsOk() const; - const cEllipse & Ell() const; - const cPt2dr & CornerlEl_WB() const; - const cPt2dr & CornerlEl_BW() const; - cPt2dr M2I(const cPt2dr & aPMod) const; - cPt2dr I2M(const cPt2dr & aPIM) const; - bool IsCircle() const; - - - /** compute the "normalized" length to encoding part*/ - std::pair Length2CodingPart(tREAL8 aPropGray,const cPt2dr & aModCenterBit) const; - - /** adapted to case where the geometric prediction - * is good on direction but relatively bad on distance - */ - void DecodeByL2CP(tREAL8 aPropGray) ; - - /// Decode just by reading the value of predicted position of bits - const cOneEncoding * BasicDecode(tREAL8 aWeightWhite); - - /// Do the decoding, switch to one of the specialized method - const cOneEncoding * Decode(cFullSpecifTarget *,tREAL8 aPropGray); - - - const cOneEncoding * Code() const; ///< Accessor - bool BOutCB() const; ///< Accessor - tREAL8 MaxEllD() const; - tREAL8 ThrsEllD() const; - - - void GenImageFail(const std::string & aWhyFail); - - private : - - /// Most basic method, return minimal of all lenght - tREAL8 ComputeThresholdsMin(const std::vector> & aVBR) const; - /** "prefered" method, compute the minimal lenght under the constraint to have run length of white - over the value given specificiation */ - tREAL8 ComputeThresholdsByRLE(const std::vector> & aVBR) const; - - - void AssertOk() const; - - const cFullSpecifTarget * mSpec; - cEllipse mEll; - cPt2dr mCornerlEl_WB; - cPt2dr mCornerlEl_BW; - cAff2D_r mAffIm2Mod; - tREAL8 mMaxEllD; /// Maximal distance 2 ellipse (for frontier point, not on lines) - const cOneEncoding * mCode; - bool mBOutCB; /// Is there black point outside the check board - bool mIsCircle; ///< Was it obtained enforcing a circle -}; - -class cCdMerged : public cCdEllipse -{ - public : - cCdMerged(const cDataIm2D *,const cCdEllipse & aCDE,tREAL8 aScale) ; - - tREAL8 mScale; - cPt2dr mC0; // center at initial image scale - void OptimizePosition(const cInterpolator1D &); - - const cDataIm2D * mDIm0; -}; - - -class cOptimPosCdM : public cDataMapping -{ - public : - cOptimPosCdM(const cCdMerged & aCdM,const cInterpolator1D & ); - - cPt1dr Value(const cPt2dr & ) const override; - typedef cSegment2DCompiled tSeg; - - private : - void AddPts(const cPt2dr & aMaster, const cPt2dr & aSecond,bool toAvoid2); - - const cCdMerged& mCdM; - const cInterpolator1D & mCurInt; - std::vector mPtsOpt; -}; - - - -void cCdMerged::OptimizePosition(const cInterpolator1D & anInt) -{ - cOptimPosCdM aCdtOpt(*this,anInt); - cOptimByStep anOpt(aCdtOpt,true,1.0); - auto [aVal,aDelta] = anOpt.Optim(cPt2dr(0,0),0.02,0.001); - - mC0 = mC0 + aDelta; -} - -cPt1dr cOptimPosCdM::Value(const cPt2dr & aDelta ) const -{ - cSymMeasure aSymM; // - cPt2dr a2NewC = (mCdM.mC0 + aDelta) * 2.0; - - const cDataIm2D & aDIm = *(mCdM.mDIm0); - for (const auto & aP1 : mPtsOpt) - { - cPt2dr aP2 = a2NewC - aP1; - if (aDIm.InsideInterpolator(mCurInt,aP1) && aDIm.InsideInterpolator(mCurInt,aP2)) - aSymM.Add(aDIm.GetValueInterpol(mCurInt,aP1),aDIm.GetValueInterpol(mCurInt,aP2)); - } - - return cPt1dr(aSymM.Sym(1e-5)); -} - - -cOptimPosCdM::cOptimPosCdM(const cCdMerged & aCdM,const cInterpolator1D & aInt) : - mCdM (aCdM), - mCurInt (aInt) -{ - AddPts(mCdM.CornerlEl_WB(), mCdM.CornerlEl_BW(),true); - AddPts(mCdM.CornerlEl_BW(), mCdM.CornerlEl_WB(),false); -} - -void cOptimPosCdM::AddPts(const cPt2dr & aSCorn1, const cPt2dr & aSCorn2,bool toAvoid2) -{ - cPt2dr aCorn1 = aSCorn1 * mCdM.mScale; - cPt2dr aCorn2 = aSCorn2 * mCdM.mScale; - - tREAL8 aStep = 0.25; - tREAL8 aWidth = 1.0; - tREAL8 aL1 = std::min(10.0,Norm2(aCorn1-mCdM.mC0)-1.0); - // cPt2dr aCorn2 = aSCorn2 * mScale; - - int aNbX = round_up(aL1/aStep); - tREAL8 aStepX = aL1 / aNbX; - - int aNbY = round_up(aWidth/aStep); - tREAL8 aStepY = aWidth / aNbY; - - tSeg aSeg1(mCdM.mC0,aCorn1); - tSeg aSeg2(mCdM.mC0,aCorn2); - - for (int aKX=-aNbX ; aKX<=aNbX ; aKX++) - { - for (int aKY=0 ; aKY<=aNbY ; aKY++) // KY=0 : we take only one point /2 - { - if ((aKY>0) || (aKX>0)) - { - cPt2dr aPLoc(aKX*aStepX,aKY*aStepY); - cPt2dr aPAbs = aSeg1.FromCoordLoc(aPLoc); - if ((!toAvoid2) || (aSeg2.DistLine(aPAbs) >aWidth)) - { - mPtsOpt.push_back(aPAbs); - // StdOut() << "PAAAA " << aPAbs - mCdM.mC0 << "\n"; - } - } - } - } -} - -/* */ -/* - private : - std::vector mPtsOpt; - */ - -//void cCdMerged:: - - +#include "cCheckBoardTargetExtract.h" -enum class eTPosCB -{ - eUndef, - eInsideBlack, - eInsideWhite, - eBorderLeft, - eBorderRight - -}; - -inline bool IsInside(eTPosCB aState) {return (aState==eTPosCB::eInsideBlack) || (aState==eTPosCB::eInsideWhite) ;} -inline bool IsOk(eTPosCB aState) {return aState!=eTPosCB::eUndef;} - -/// Used temporary for theoreticall radiometric model compilation of radiom -class cTmpCdRadiomPos : public cCdRadiom -{ - public : - cTmpCdRadiomPos(const cCdRadiom &,tREAL8 aThickness); - /// Theoreticall radiom of modelize checkboard + bool if was computed - std::pair TheorRadiom(const cPt2dr &,tREAL8 aThick,tREAL8 aSteep) const; - std::pair TheorRadiom(const cPt2dr &) const; - - tREAL8 mThickness; - cSegment2DCompiled mSeg0 ; - cSegment2DCompiled mSeg1 ; -}; - - -/* *************************************************** */ -/* */ -/* cAppliCheckBoardTargetExtract */ -/* */ -/* *************************************************** */ - -class cAppliCheckBoardTargetExtract : public cMMVII_Appli -{ - public : - typedef tREAL4 tElem; - typedef cIm2D tIm; - typedef cDataIm2D tDIm; - typedef cAffin2D tAffMap; - - - cAppliCheckBoardTargetExtract(const std::vector & aVArgs,const cSpecMMVII_Appli & aSpec); - - - /// Generate a small image, centred on decteted "cCdRadiom", showing the main carateristic detecte - cRGBImage GenImaRadiom(cCdRadiom &,int aSz) const; - /// Call "GenImaRadiom" with default size - cRGBImage GenImaRadiom(cCdRadiom &) const; - /// For generating visualizatio, - std::string NameVisu(const std::string & aPref,const std::string aPost="") const; - - /// Add the information specfic to a "cCdEllipse" - void ComplImaEllipse(cRGBImage &,const cCdEllipse &) const; - - - const cFullSpecifTarget * Specif() const {return mSpecif;} ///< Accessor - int NbMinPtEllipse() const {return mNbMinPtEllipse;} ///< Accessor - private : - - /// Memorize a detection as a label, if label image is init - void SetLabel(const cPt2dr& aPt,tU_INT1 aLabel); - - // =========== overridding cMMVII_Appli::methods ============ - int Exe() override; - cCollecSpecArg2007 & ArgObl(cCollecSpecArg2007 & anArgObl) override ; - cCollecSpecArg2007 & ArgOpt(cCollecSpecArg2007 & anArgOpt) override ; - - void GenerateVisuFinal() const; - void GenerateVisuDetail(std::vector &) const; - bool IsPtTest(const cPt2dr & aPt) const; ///< Is it a point marqed a test - - /// Potentially Add a new detected - void AddCdtE(const cCdEllipse & aCDE); - - /// Generate the xml-result - void DoExport(); - - - /// Method called do each image - void DoOneImage() ; - /// Method called do each image - void DoOneImageAndScale(tREAL8,const tIm & anIm ) ; - - /// Read Image from files, init Label Image, init eventually masq 4 debug, compute blurred version of input image - void ReadImagesAndBlurr(); - /// compute points that are "topologicall" : memorized in label image as label "eTopoTmpCC" - void ComputeTopoSadles(); - /// start from topo point, compute the "saddle" criterion, and use it for filtering on relative max - void SaddleCritFiler() ; - /// start from saddle point, optimize position on symetry criterion then filter on sym thresholds - void SymetryFiler() ; - - void MakeImageLabels(const std::string & aName,const tDIm &,const cDataIm2D & aDMasq) const; - - cPhotogrammetricProject mPhProj; - cTimerSegm mTimeSegm; - - cCdRadiom MakeCdtRadiom(cScoreTetaLine&,const cCdSym &,tREAL8 aThickness); - - // =========== Mandatory args ============ - - std::string mNameIm; ///< Name of background image - std::string mNameSpecif; ///< Name of specification file - - // =========== Optionnal args ============ - - // -- - - tREAL8 mThickness; ///< used for fine estimation of radiom - bool mOptimSegByRadiom; ///< Do we optimize the segment on average radiom - - tREAL8 mLInitTeta; ///< = 05.0; - tREAL8 mLInitProl; ///< = 03.0; - tREAL8 mLengtProlong; ///< = 20.0; - tREAL8 mStepSeg; ///< = 0.5; - tREAL8 mMaxCostCorrIm; ///< = 0.1; - int mNbMaxBlackCB; ///< Number max of point in black component of checkboard - tREAL8 mPropGrayDCD; ///< Proportion Black/White for extracting - int mNbBlur1; ///< = 4, Number of initial blurring - std::string mStrShow; - - std::vector mScales; ///< Different Scales at which computation is done def {1}, => 0.5 means biggers images - // - // ---------------- Thresholds for Saddle point criteria -------------------- - tREAL8 mDistMaxLocSad ; ///< =10.0, for supressing sadle-point, not max loc in a neighboorhoud - int mDistRectInt; ///< = 20, insideness of points for seed detection - size_t mMaxNbSP_ML0 ; ///< = 30000 Max number of best point saddle points, before MaxLoc - size_t mMaxNbSP_ML1 ; ///< = 2000 Max number of best point saddle points, after MaxLoc - cPt2di mPtLimCalcSadle; ///< =(2,1) limit point for calc sadle neighbour , included, - - - // ---------------- Thresholds for Symetry criteria -------------------- - tREAL8 mThresholdSym ; ///< = 0.5, threshlod for symetry criteria - tREAL8 mRayCalcSym0 ; ///< = 8.0 distance for evaluating symetry criteria - tREAL8 mDistDivSym ; ///< = 2.0 maximal distance to initial value in symetry opt - - int mNumDebugMT; - int mNumDebugSaddle; - - // ---------------- Thresholds for Ellipse criteria -------------------- - int mNbMinPtEllipse; - bool mTryC; - bool mRefinePos; - - // =========== Internal param ============ - - int mZoomVisuDetec; /// zoom Visu detail of detection - int mDefSzVisDetec; /// Default Sz Visu detection - cFullSpecifTarget * mSpecif; - - tIm mImInCur; ///< Input current image - cPt2di mSzImCur; ///< Size of current image - tDIm * mDImInCur; ///< Data ccurrent image - - tIm mImIn0; ///< Input file image - cPt2di mSzIm0; ///< Size file image - tDIm * mDImIn0; ///< Data input image - - - tIm mImBlur; ///< Blurred image, used in pre-detetction - tDIm * mDImBlur; ///< Data input image - bool mHasMasqTest; ///< Do we have a test image 4 debuf (with masq) - cIm2D mMasqTest; ///< Possible image of mas 4 debug, print info ... - cIm2D mImLabel; ///< Image storing labels of centers - cDataIm2D * mDImLabel; ///< Data Image of label - cIm2D mImTmp; ///< Temporary image for connected components - cDataIm2D * mDImTmp; ///< Data Image of "mImTmp" - - std::vector mVCdtSad; ///< Candidate that are selected as local max of saddle criteria - std::vector mNbSads; ///< For info, number of sadle points at different step - std::vector mVCdtSym; ///< Candidate that are selected on the symetry criteria - // - std::vector mVCdtMerged; // Candidate merged form various scales - tREAL8 mCurScale; /// Memorize the current value of the scale - bool mMainScale; /// Is it the first/main scale - cInterpolator1D * mInterpol; -}; - - - - -/* ================================================================= */ - - -/* **************************************************** */ -/* cCdSadle */ -/* **************************************************** */ - -int cCdSadle::TheCptNum=0; -int cCdSadle::TheNum2Debug=-2; - -cCdSadle::cCdSadle (const cPt2dr & aC,tREAL8 aCrit,bool isPTest) : - mC (aC) , - mSadCrit (aCrit) , - mIsPTest (isPTest), - mNum (TheCptNum++) -{ -} -cCdSadle::cCdSadle () : - mNum (-1) -{ -} - -bool cCdSadle::Is4Debug() const {return mNum == TheNum2Debug;} - - - - -/* ***************************************************** */ -/* */ -/* cCdRadiom */ -/* */ -/* ***************************************************** */ - -cCdRadiom::cCdRadiom -( - const cAppliCheckBoardTargetExtract * anAppli, - const cCdSym & aCdSym, - const cDataIm2D & aDIm, - tREAL8 aTeta0, - tREAL8 aTeta1, - tREAL8 aLength, - tREAL8 aThickness -) : - cCdSym (aCdSym), - mAppli (anAppli), - mIsOk (false), - mDIm (&aDIm), - mTetas {aTeta0,aTeta1}, - mLength (aLength), - mThickness (aThickness), - mCostCorrel (2.001), // over maximal theoreticall value - mRatioBW (0) -{ - static int aCpt=0 ; aCpt++; - - int aNbIn0=0,aNbIn1=0; - - cMatIner2Var aCorGrayAll; - cMatIner2Var aCorGrayInside; - - cTmpCdRadiomPos aCRC(*this,aThickness); - - std::vector aVPixEllipse; - ComputePtsOfEllipse(aVPixEllipse); - for (const auto & aPImI : aVPixEllipse) - { - tREAL8 aValIm = aDIm.GetV(aPImI); - cPt2dr aPImR = ToR(aPImI); - - auto [aState,aGrayTh] = aCRC.TheorRadiom(aPImR); - - if (IsInside(aState)) - { - aCorGrayInside.Add(aGrayTh,aValIm); - aNbIn0 += (aState == eTPosCB::eInsideBlack); - aNbIn1 += (aState == eTPosCB::eInsideWhite); - } - if (IsOk(aState)) - { - aCorGrayAll.Add(aGrayTh,aValIm); - } - } - - if ((aNbIn0==0) && (aNbIn1==0)) - return; - - mRatioBW = std::min(aNbIn0,aNbIn1) / (tREAL8) std::max(aNbIn0,aNbIn1); - if (mRatioBW <0.05) - { - return ; - } - - mIsOk = true; - - mCostCorrel = 1-aCorGrayAll.Correl(); - auto [a,b] = aCorGrayInside.FitLineDirect(); - mBlack = b ; - mWhite = a+b; -} - -tREAL8 cCdRadiom::Threshold(tREAL8 aWW) const -{ - return mBlack*(1-aWW) + mWhite *aWW; -} - -void cCdRadiom::OptimSegIm(const cDataIm2D & aDIm,tREAL8 aLength) -{ - // StdOut() << "TttTT=" << Threshold() << " " << mBlack << " " << mWhite << " " << mRatioBW << "\n"; - - std::vector> aVSegOpt; - for (int aKTeta=0 ; aKTeta<2 ; aKTeta++) - { - cPt2dr aTgt = FromPolar(aLength,mTetas[aKTeta]); - tSeg2dr aSegInit(mC-aTgt,mC+aTgt); - cOptimSeg_ValueIm aOSVI(aSegInit,0.5,aDIm,Threshold()); - tSeg2dr aSegOpt = aOSVI.OptimizeSeg(0.5,0.01,true,2.0); - - aVSegOpt.push_back(aSegOpt); - mTetas[aKTeta] = Teta(aSegOpt.V12()); - // mTetas[aKTeta] = aSegOpt.I// - } - - cPt2dr aC = aVSegOpt.at(0).InterSeg(aVSegOpt.at(1)); - - mC = aC; - cScoreTetaLine::NormalizeTetaCheckBoard(mTetas); -} - -void cCdRadiom::ComputePtsOfEllipse(std::vector & aRes) const -{ - ComputePtsOfEllipse(aRes,mLength); -} - - -void cCdRadiom::ComputePtsOfEllipse(std::vector & aRes,tREAL8 aLength) const -{ - aRes.clear(); - // [1] Compute the affinity that goes from unity circle to ellipse - // ---- x,y -> mC + x V0 + y V1 ------ - cPt2dr aV0 = FromPolar(aLength,mTetas[0]); - cPt2dr aV1 = FromPolar(aLength,mTetas[1]); - - cAff2D_r aMapEll2Ori(mC,aV0,aV1); - cAff2D_r aMapOri2Ell = aMapEll2Ori.MapInverse(); - - // [2] Compute the bounding box containing the ellipse - cTplBoxOfPts aBox; - int aNbTeta = 100; - for (int aKTeta=0 ; aKTeta aSeg(mC,mC+FromPolar(1.0,aTeta)); - - cPt2dr aPLoc = aSeg.ToCoordLoc(aPAbs); - - if (std::abs(aPLoc.y()) <= 1.0 + std::abs(aPLoc.x()) /30.0) - return true; - - return false; -} - -bool cCdRadiom::PtIsOnEll(cPt2dr & aPtAbs) const -{ - // point must be far enough of center (because close to, it's not easily separable) - if (Norm2(aPtAbs - mC)<3.0) - return false; - - // point canot be a point of the line - for (const auto & aTeta : mTetas ) - if (PtIsOnLine(aPtAbs,aTeta)) - return false; - - // extract the point that has the gray threshold (assuming gray starting point is bellow) - cGetPts_ImInterp_FromValue aGIFV(*mDIm,Threshold(),0.1,aPtAbs, VUnit(aPtAbs - mC)); - cPt2dr aNewP = aPtAbs; - if (aGIFV.Ok()) - { - // if interpoleted point is to far from initial : suscpicious - aNewP = aGIFV.PRes(); - if (Norm2(aPtAbs-aNewP)>2.0) - return false; - - cPt2dr aPGr = Proj(mDIm->GetGradAndVBL(aNewP)); - // StdOut() << "PGR=== " << aPGr << "\n"; - tREAL8 aSc = std::abs(CosWDef(aPGr,aNewP-mC,1.0)); - if (aSc<0.5) - return false; - - aPtAbs = aNewP; - } - else - return false; - - // cGetPts_ImInterp_FromValue aGIFV(*mDIm,aV,0.1,aPt+ToR(aDec)-aNorm, aNorm); - /* - cPt2dr aPGr = Proj(mDIm->GetGradAndVBL(aPtAbs)); - if (IsNull(aPGr)) return false; - - cPt2dr aDir = (aPtAbs-mC) / aPGr; - tREAL8 aTeta = - if (Norm2(aPGr) - - */ - - - - return true; -} - -void cCdRadiom::SelEllAndRefineFront(std::vector & aRes,const std::vector & aFrontI) const -{ - aRes.clear(); - for (const auto & aPix : aFrontI) - { - cPt2dr aRPix = ToR(aPix); - if (PtIsOnEll(aRPix)) - aRes.push_back(aRPix); - } -} - -bool cCdRadiom::FrontBlackCC(std::vector & aVFront,cDataIm2D & aDMarq,int aNbMax) const -{ - std::vector aRes; - aVFront.clear(); - - std::vector aVPtsEll; - ComputePtsOfEllipse(aVPtsEll,std::min(mLength,5.0)); - - tREAL8 aThrs = Threshold(); - for (const auto & aPix : aVPtsEll) - { - if (mDIm->GetV(aPix) & aV4 = Alloc4Neighbourhood(); - - cRect2 aImOk(aDMarq.Dilate(-10)); - bool isOk = true; - - while ((aIndBot != aRes.size()) && isOk) - { - for (const auto & aDelta : aV4) - { - cPt2di aPix = aRes.at(aIndBot) + aDelta; - if ((aDMarq.GetV(aPix)==0) && (mDIm->GetV(aPix) & aV8 = Alloc8Neighbourhood(); - // compute frontier points - for (const auto & aPix : aRes) - { - bool has8NeighWhite = false; - for (const auto & aDelta : aV8) - { - if (aDMarq.GetV(aPix+aDelta)==0) - has8NeighWhite = true; - } - - if (has8NeighWhite) - { - aVFront.push_back(aPix); - } - } - if (false && Is4Debug()) - { - StdOut() << "--xxx--HASH " << HashValue(aVFront,true) << " SZCC=" << aRes.size() - << " HELLL=" << HashValue(aVPtsEll,true) - << " HELLL=" << HashValue(aVPtsEll,true) - << " C=" << mC << " Thr=" << aThrs - << " L=" << mLength << "\n"; - } - // StdOut() << "FFFF=" << aVFront << "\n"; - - for (const auto & aPix : aRes) - aDMarq.SetV(aPix,0); - - return isOk; -} - - - - // if (has8NeighWhite && PtIsOnEll(aRPix)) - -void cCdRadiom::ShowDetail - ( - int aCptMarq, - const cScoreTetaLine & aSTL, - const std::string & aNameIm, - cDataIm2D & aMarq, - cFullSpecifTarget *aSpec - ) const -{ - cCdEllipse aCDE(*this,aMarq,-1,false); - if (! aCDE.IsOk()) - { - StdOut() << " @@@@@@@@@@@@@@@@@@@@@@@@@@@@ " << aCptMarq << "\n"; - return; - } - - std::pair aPairTeta(mTetas[0],mTetas[1]); - - aCDE.DecodeByL2CP(2.0/3.0); - StdOut() << " CptMarq=" << aCptMarq - << " NUM=" << mNum - << " Corrrr=" << mCostCorrel - << " Ratio=" << mRatioBW - << " V0="<< mBlack << " V1=" << mWhite - << " ScTeta=" << aSTL.Score2Teta(aPairTeta,2.0) - << " ScSym=" << mSymCrit - << " LLL=" << mLength - << " ThickN=" << mThickness - << " CODE=[" << aCDE.Code() << "]" - << " C=" << mC - << " DELL=" << aCDE.MaxEllD() - << " OK=" << aCDE.mIsOk - << " OUTCB=" << aCDE.BOutCB() - << "\n"; -} - -/* ***************************************************** */ -/* */ -/* cCdEllipse */ -/* */ -/* ***************************************************** */ - -void cCdEllipse::GenImageFail(const std::string & aWhyFail) -{ - static int aCpt=0; - cRGBImage aIm = mAppli->GenImaRadiom(*this); - StdOut() << "Fail for Num=" << mNum << " Cpt=" << aCpt << " reason=" << aWhyFail << "\n"; - aIm.ToJpgFileDeZoom(mAppli->NameVisu("Failed"+ aWhyFail,ToStr(aCpt++)),1); -} - - -cCdEllipse::cCdEllipse(const cCdRadiom & aCdR,cDataIm2D & aMarq,int aNbMax,bool isCircle) : - cCdRadiom (aCdR), - mSpec (mAppli->Specif()), - mEll (cPt2dr(0,0),0,1,1), - mMaxEllD (0.0), - mCode (nullptr), - mBOutCB (false), - mIsCircle (isCircle) -{ - - if (! mIsOk) - { - if(mIsPTest) StdOut() << "Ref cCdEllipse at L=" << __LINE__ << "\n" ; - return; - } - mIsOk = true; - std::vector aIFront; - mIsOk = FrontBlackCC(aIFront,aMarq,aNbMax); - - if (! mIsOk) - { - if(mIsPTest) StdOut() << "Ref cCdEllipse at L=" << __LINE__ << "\n" ; - return; - } - - { - cTmpCdRadiomPos aCRP(*this,1.0); - for (const auto & aPix : aIFront) - { - auto [aPos,aGray] = aCRP.TheorRadiom(ToR(aPix),2.0,1/20.0); - if (aPos == eTPosCB::eInsideWhite) - { - mBOutCB = true; - } - } - if (mBOutCB) - { - if(mIsPTest) StdOut() << "Ref cCdEllipse at L=" << __LINE__ << "\n" ; - mIsOk = false; - return; - } - } - - std::vector aEllFr; - SelEllAndRefineFront(aEllFr,aIFront); - - if ((int) aEllFr.size() < (mIsCircle ? 2 : mAppli->NbMinPtEllipse()) ) - { - if (mIsPTest) - GenImageFail("NbEllipse"); - mIsOk = false; - return; - } - - mIsOk = true; - - cEllipse_Estimate anEE(mC,false,mIsCircle); - for (const auto & aPixFr : aEllFr) - { - anEE.AddPt(aPixFr); - } - - mEll = anEE.Compute(); - - if (!mEll.Ok()) - { - if(mIsPTest) - { -// DebugEll=true; - GenImageFail("BadEll"); -// DebugEll=false; - } - mIsOk = false; - return; - } - - tREAL8 aThrs = 0.6+ mEll.LGa()/40.0; - for (const auto & aPixFr : aEllFr) - { - tREAL8 aD = mEll.NonEuclidDist(aPixFr); - UpdateMax(mMaxEllD,aD); - if (aD>aThrs) - { - if(mIsPTest) StdOut() << "Ref cCdEllipse at L=" << __LINE__ << "\n" ; - mIsOk = false; - return; - } - } - - - // In specif the corner are for a white sector, with name of transition (B->W or W->B) - // coming with trigonometric convention; here the angle have been comouted for a black sector - // The correction for angle have been made experimentally ... - mCornerlEl_WB = mEll.InterSemiLine(mTetas[0]); - mCornerlEl_BW = mEll.InterSemiLine(mTetas[1]+M_PI); - - - - cAff2D_r::tTabMin aTabIm{mC,mCornerlEl_WB,mCornerlEl_BW}; - cAff2D_r::tTabMin aTabMod{mSpec->Center(),mSpec->CornerlEl_WB(),mSpec->CornerlEl_BW()}; - - mAffIm2Mod = cAff2D_r::FromMinimalSamples(aTabIm,aTabMod); - // mAffIm2Mod -} - -bool cCdEllipse::BOutCB() const {return mBOutCB;} -bool cCdEllipse::IsCircle() const {return mIsCircle;} - -bool cCdEllipse::IsOk() const {return mIsOk;} -void cCdEllipse::AssertOk() const -{ - MMVII_INTERNAL_ASSERT_tiny(mIsOk,"No ellipse Ok in cCdEllipse"); -} - -cPt2dr cCdEllipse::M2I(const cPt2dr & aPMod) const -{ - AssertOk(); - return mAffIm2Mod.Inverse(aPMod); -} - -cPt2dr cCdEllipse::I2M(const cPt2dr & aPMod) const -{ - AssertOk(); - return mAffIm2Mod.Value(aPMod); -} - - -const cOneEncoding * cCdEllipse::Code() const {return mCode;} -tREAL8 cCdEllipse::MaxEllD() const {return mMaxEllD;} - -const cEllipse & cCdEllipse::Ell() const {AssertOk(); return mEll;} -const cPt2dr & cCdEllipse::CornerlEl_WB() const {AssertOk(); return mCornerlEl_WB;} -const cPt2dr & cCdEllipse::CornerlEl_BW() const {AssertOk(); return mCornerlEl_BW;} - -// tREAL8 cCdEllipse::ScoreCodeBlack(const cPt2dr& aDir,tREAL8 aRho, - -std::pair cCdEllipse::Length2CodingPart(tREAL8 aWeighWhite,const cPt2dr & aModCenterBit) const -{ - // Rho end search, 1.5 theoretical end of code , highly over estimated (no risk ?) - tREAL8 aRhoMaxRel = mSpec->Rho_2_EndCode() * 1.5; - std::pair aNoValue(-1,cPt2dr(0,0)); - - // Not sur meaningful with other mode - MMVII_INTERNAL_ASSERT_tiny(mSpec->Type()==eTyCodeTarget::eIGNIndoor,"Bad code in Length2CodingPart"); - - - cPt2dr aDirModel = VUnit(mSpec->Pix2Norm(aModCenterBit)); // -> normalize coord -> unitary vect - // distance , ratio on the line mC-Pt, between a normalized model pixel, and it corresponding image pixel - tREAL8 aMulN2I = Norm2(M2I(mSpec->Norm2Pix(aDirModel))-mC); - // - cPt2dr aDirIm = VUnit(M2I(aModCenterBit)-mC); // direction of line in image - - // Rho begin search in image, at mid position between check board and begining of code - tREAL8 aRho0 = aMulN2I * ((mSpec->Rho_0_EndCCB()+mSpec->Rho_1_BeginCode()) / 2.0); - - // Rho end search, 1.5 theoretical end of code , highly over estimated (no risk ?) - tREAL8 aRho1 = aMulN2I * aRhoMaxRel; - // step of research, overly small (no risk ?) - tREAL8 aStepRho = 0.2; - int aNbRho = round_up((aRho1-aRho0) / aStepRho); - aStepRho = (aRho1-aRho0) / aNbRho; - - // threshold for value computing the black code - tREAL8 aThresh = aWeighWhite * mWhite + (1-aWeighWhite) * mBlack; - - for (int aKRho = 0 ; aKRho<=aNbRho ; aKRho++) - { - tREAL8 aRho = aRho0 + aKRho * aStepRho; - cPt2dr aPt = mC + aDirIm * aRho; - - if (!mDIm->InsideBL(aPt)) // we are too far, end of game - { - return aNoValue; - } - else - { - auto [aVal,aGrad] = mDIm->GetPairGradAndVBL(aPt); - // we test also orientation of gradient, because with very small target, there is no clear separation , - // and we dont want to accept the checkboard as a - if ((aVal < aThresh) && (Scal(aGrad,aDirIm)<0)) - { - return std::pair (aRho/aMulN2I,aPt); - } - } - } - std::pair aDef(aRhoMaxRel,mC + aDirIm*aRhoMaxRel*aMulN2I); - - return aDef; -} - -tREAL8 cCdEllipse::ComputeThresholdsByRLE(const std::vector> & aVBR) const -{ - // cPt2di MaxRunLength(tU_INT4 aVal,size_t aPow2); - - - int aMaxWL = mSpec->Specs().mMaxRunL.x(); // Max run lenght for 0 (= white in general, 2 adapt ...) - aMaxWL = std::min(aMaxWL,(int)mSpec->NbBits()); - - size_t aFlag = 0; - int aRunLE = mSpec->NbBits(); - size_t aKBit = 0; - while ((aMaxWLNbBits()).x(); - } - - return aVBR.at(aKBit-1).second * 1.2; - -} - -tREAL8 cCdEllipse::ComputeThresholdsMin(const std::vector> & aVBR) const -{ - tREAL8 aV0 = aVBR[0].second; - - return aV0 * 1.25; -} - - -void cCdEllipse::DecodeByL2CP(tREAL8 aWeighWhite) -{ - std::vector > aVBR; // Vector Bits/Rho - const auto & aVC = mSpec->BitsCenters(); // Vector centers - for (size_t aKBit=0 ; aKBit(aKBit,aRho)); - } - - SortOnCriteria(aVBR,[](const auto & aPair) {return aPair.second;}); - tREAL8 aRhoMin = aVBR.at(0).second; - - - if (0) - { - std::vector > aDup = aVBR; - SortOnCriteria(aDup,[](const auto & aPair) {return aPair.first;}); - for (const auto & [aBit,aRho] : aDup) - StdOut() << " RRR=" << aRho/aRhoMin << "\n"; - } - - // tREAL8 aTreshold = ComputeThresholdsMin(aVBR); - tREAL8 aTreshold = ComputeThresholdsByRLE(aVBR); - - cDecodeFromCoulBits aDec(mSpec); - for (const auto & [aBit,aRho] : aVBR) - { - aDec.SetColBit(aRhoBitsCenters(); - tREAL8 aThrs = Threshold(aWW); - - for (size_t aKBit=0 ; aKBitInsideBL(aPIm)) - { - aDec.SetColBit(mDIm->GetVBL(aPIm) < aThrs , aKBit); - } - else - return nullptr; - } - return aDec.Encoding(); -} - - -/* ***************************************************** */ -/* */ -/* cTmpCdRadiomPos */ -/* */ -/* ***************************************************** */ - - -cTmpCdRadiomPos::cTmpCdRadiomPos(const cCdRadiom & aCDR,tREAL8 aThickness) : - cCdRadiom (aCDR), - mThickness (aThickness), - mSeg0 (mC,mC+FromPolar(1.0,mTetas[0])), - mSeg1 (mC,mC+FromPolar(1.0,mTetas[1])) -{ -} - -std::pair cTmpCdRadiomPos::TheorRadiom(const cPt2dr &aPt,tREAL8 aThickInit,tREAL8 aSteep) const -{ - eTPosCB aPos = eTPosCB::eUndef; - tREAL8 aGrayTh = -1; - - // we compute locacl coordinates because the sign of y indicate if we are left/right of the oriented segment - // and sign of x indicate if we are before/after the centre - cPt2dr aLoc0 = mSeg0.ToCoordLoc(aPt); - tREAL8 aY0 = aLoc0.y(); - tREAL8 aThick0 = aThickInit + aSteep * std::abs(aLoc0.x()); - - cPt2dr aLoc1 = mSeg1.ToCoordLoc(aPt); - tREAL8 aY1 = aLoc1.y(); - tREAL8 aThick1 = aThickInit + aSteep * std::abs(aLoc1.x()); - - // compute if we are far enough of S0/S1 because the computation of gray will change - // black/white if far enough, else interpolation - bool FarS0 = std::abs(aY0)> aThick0; - bool FarS1 = std::abs(aY1)> aThick1; - - if ( FarS0 && FarS1) - { - if ((aY0>0)!=(aY1>0)) - { - aPos = eTPosCB::eInsideBlack; - aGrayTh = 0.0; - } - else - { - aPos = eTPosCB::eInsideWhite; - aGrayTh = 1.0; - } - } - else if ((!FarS0) && FarS1) - { - // (! FarS0) => teta1 - // Red = teta1 , black on left on image, right on left in coord oriented - aPos = eTPosCB::eBorderRight; - int aSignX = (aLoc0.x() >0) ? -1 : 1; - aGrayTh = (aThick0+aSignX*aY0) / (2.0*aThick0); - } - else if (FarS0 && (!FarS1)) - { - aPos = eTPosCB::eBorderLeft; - int aSignX = (aLoc1.x() <0) ? -1 : 1; - aGrayTh = (aThick1+aSignX*aY1) / (2.0 * aThick1); - } - - return std::pair(aPos,aGrayTh); -} - -std::pair cTmpCdRadiomPos::TheorRadiom(const cPt2dr &aPt) const +namespace MMVII { - return TheorRadiom(aPt,mThickness,0.0); -} - - - -/* ********************************************* */ -/* */ -/* cCdMerged */ -/* */ -/* ********************************************* */ +bool DebugCB = false; -cCdMerged::cCdMerged(const cDataIm2D * aDIm0,const cCdEllipse & aCDE,tREAL8 aScale) : - cCdEllipse (aCDE), - mScale (aScale), - mC0 (mC * mScale), - mDIm0 (aDIm0) -{ -} +namespace NS_CHKBRD_TARGET_EXTR { /* *************************************************** */ /* */ @@ -1260,7 +60,7 @@ cAppliCheckBoardTargetExtract::cAppliCheckBoardTargetExtract(const std::vector0) { for (auto & aCdtM : mVCdtMerged) - aCdtM.OptimizePosition(*mInterpol); + aCdtM.OptimizePosition(*mInterpol,mStepRefinePos); } diff --git a/MMVII/src/CodedTarget/cCheckBoardTargetExtract.h b/MMVII/src/CodedTarget/cCheckBoardTargetExtract.h new file mode 100755 index 000000000..649af3f18 --- /dev/null +++ b/MMVII/src/CodedTarget/cCheckBoardTargetExtract.h @@ -0,0 +1,386 @@ +#include "CodedTarget.h" +#include "MMVII_2Include_Serial_Tpl.h" +#include "MMVII_Tpl_Images.h" +#include "MMVII_Interpolators.h" +#include "MMVII_Linear2DFiltering.h" +#include "MMVII_ImageMorphoMath.h" +#include "MMVII_2Include_Tiling.h" +#include "MMVII_Sensor.h" +#include "MMVII_HeuristikOpt.h" +#include "MMVII_ExtractLines.h" +#include "MMVII_TplImage_PtsFromValue.h" + + +namespace MMVII +{ + +namespace NS_CHKBRD_TARGET_EXTR { + +class cAppliCheckBoardTargetExtract ; + +class cCdSadle; +class cCdSym; +class cCdRadiom; +class cTmpCdRadiomPos ; +class cCdEllipse ; +class cCdMerged ; +class cOptimPosCdM; + +static constexpr tU_INT1 eNone = 0 ; +static constexpr tU_INT1 eTopo0 = 1 ; +static constexpr tU_INT1 eTopoTmpCC = 2 ; +static constexpr tU_INT1 eTopoMaxOfCC = 3 ; +static constexpr tU_INT1 eTopoMaxLoc = 4 ; +static constexpr tU_INT1 eFilterSym = 5 ; +static constexpr tU_INT1 eFilterRadiom = 6 ; +static constexpr tU_INT1 eFilterEllipse = 7 ; +static constexpr tU_INT1 eFilterCodedTarget = 8 ; + + +/* **************************************************** */ +/* cCdSadle */ +/* **************************************************** */ + +/// candidate that are pre-selected on the sadle-point criteria +class cCdSadle +{ + public : + cCdSadle (const cPt2dr & aC,tREAL8 aCrit,bool isPTest) ; + cCdSadle (); + + bool Is4Debug() const; + + /// Center + cPt2dr mC; + + /// Criterion of sadle point, obtain by fiting a quadric on gray level + tREAL8 mSadCrit; + + /// Is it a point marked + bool mIsPTest; + + /// Num : 4 debug + int mNum; + /// Use 2 compute mNum + static int TheCptNum; + /// Use to have breakpoint at creation + static int TheNum2Debug; +}; +// +/// candidate that are pre-selected after symetry criterion +class cCdSym : public cCdSadle +{ + public : + cCdSym(const cCdSadle & aCdSad,tREAL8 aCrit) : cCdSadle (aCdSad), mSymCrit (aCrit) { } + /// Criterion of symetry, obtain after optimisation of center + tREAL8 mSymCrit; + +}; + +/// candidate obtain after radiometric modelization, + +class cCdRadiom : public cCdSym +{ + public : + /// Cstr use the 2 direction + Thickness of transition between black & white + cCdRadiom(const cAppliCheckBoardTargetExtract *,const cCdSym &,const cDataIm2D & aDIm,tREAL8 aTeta1,tREAL8 aTeta2,tREAL8 aLength,tREAL8 aThickness); + + /// Theoretical threshold + tREAL8 Threshold(tREAL8 aWhite= 0.5 ) const ; + /// Once black/white & teta are computed, refine seg using + void OptimSegIm(const cDataIm2D & aDIm,tREAL8 aLength); + + /// compute an ellipse that "safely" contains the points of checkboard + void ComputePtsOfEllipse(std::vector & aRes,tREAL8 aLength) const; + /// call previous with length + void ComputePtsOfEllipse(std::vector & aRes) const; + + /// compute an ellipse that contain + bool FrontBlackCC(std::vector & aRes,cDataIm2D & aMarq,int aNbMax) const; + /// Select point of front that are on ellipse + void SelEllAndRefineFront(std::vector & aRes,const std::vector &) const; + + + /// Is the possibly the point on arc of black ellipse, if yes refine it + bool PtIsOnEllAndRefine(cPt2dr &) const; + /// Is the point on the line for one of angles + bool PtIsOnLine(const cPt2dr &,tREAL8 aTeta) const; + + /// Make a visualisation of geometry + void ShowDetail(int aCptMarq,const cScoreTetaLine & aSTL,const std::string &,cDataIm2D & aMarq, cFullSpecifTarget *) const; + + const cAppliCheckBoardTargetExtract * mAppli; + bool mIsOk; + const cDataIm2D * mDIm; + + tREAL8 mTetas[2]; + tREAL8 mLength; ///< length of the lines + tREAL8 mThickness; ///< thickness of the transition B/W + + tREAL8 mCostCorrel; // 1-Correlation of model + tREAL8 mRatioBW; // ratio min/max of BW + tREAL8 mScoreTeta; // ratio min/max of BW + tREAL8 mBlack; + tREAL8 mWhite; + cPt2di mDec; ///< Shit for visu +}; + + +class cCdEllipse : public cCdRadiom +{ + public : + cCdEllipse(const cCdRadiom &,cDataIm2D & aMarq,int aNbMax,bool isCircle); + bool IsOk() const; + const cEllipse & Ell() const; + const cPt2dr & CornerlEl_WB() const; + const cPt2dr & CornerlEl_BW() const; + cPt2dr M2I(const cPt2dr & aPMod) const; + cPt2dr I2M(const cPt2dr & aPIM) const; + bool IsCircle() const; + + + /** compute the "normalized" length to encoding part*/ + std::pair Length2CodingPart(tREAL8 aPropGray,const cPt2dr & aModCenterBit) const; + + /** adapted to case where the geometric prediction + * is good on direction but relatively bad on distance + */ + void DecodeByL2CP(tREAL8 aPropGray) ; + + /// Decode just by reading the value of predicted position of bits + const cOneEncoding * BasicDecode(tREAL8 aWeightWhite); + + /// Do the decoding, switch to one of the specialized method + const cOneEncoding * Decode(cFullSpecifTarget *,tREAL8 aPropGray); + + + const cOneEncoding * Code() const; ///< Accessor + bool BOutCB() const; ///< Accessor + tREAL8 MaxEllD() const; + tREAL8 ThrsEllD() const; + + + void GenImageFail(const std::string & aWhyFail); + + private : + + /// Most basic method, return minimal of all lenght + tREAL8 ComputeThresholdsMin(const std::vector> & aVBR) const; + /** "prefered" method, compute the minimal lenght under the constraint to have run length of white + over the value given specificiation */ + tREAL8 ComputeThresholdsByRLE(const std::vector> & aVBR) const; + + + void AssertOk() const; + + const cFullSpecifTarget * mSpec; + cEllipse mEll; + cPt2dr mCornerlEl_WB; + cPt2dr mCornerlEl_BW; + cAff2D_r mAffIm2Mod; + tREAL8 mMaxEllD; /// Maximal distance 2 ellipse (for frontier point, not on lines) + const cOneEncoding * mCode; + bool mBOutCB; /// Is there black point outside the check board + bool mIsCircle; ///< Was it obtained enforcing a circle +}; + +class cCdMerged : public cCdEllipse +{ + public : + cCdMerged(const cDataIm2D *,const cCdEllipse & aCDE,tREAL8 aScale) ; + + tREAL8 mScale; + cPt2dr mC0; // center at initial image scale + void OptimizePosition(const cInterpolator1D &,tREAL8 aStepEnd); + + const cDataIm2D * mDIm0; +}; + + + + + +enum class eTPosCB +{ + eUndef, + eInsideBlack, + eInsideWhite, + eBorderLeft, + eBorderRight + +}; + +inline bool IsInside(eTPosCB aState) {return (aState==eTPosCB::eInsideBlack) || (aState==eTPosCB::eInsideWhite) ;} +inline bool IsOk(eTPosCB aState) {return aState!=eTPosCB::eUndef;} + +/// Used temporary for theoreticall radiometric model compilation of radiom +class cTmpCdRadiomPos : public cCdRadiom +{ + public : + cTmpCdRadiomPos(const cCdRadiom &,tREAL8 aThickness); + + /// Theoreticall radiom of modelize checkboard + bool if was computed + std::pair TheorRadiom(const cPt2dr &,tREAL8 aThick,tREAL8 aSteep) const; + std::pair TheorRadiom(const cPt2dr &) const; + + tREAL8 mThickness; + cSegment2DCompiled mSeg0 ; + cSegment2DCompiled mSeg1 ; +}; + + +/* *************************************************** */ +/* */ +/* cAppliCheckBoardTargetExtract */ +/* */ +/* *************************************************** */ + +class cAppliCheckBoardTargetExtract : public cMMVII_Appli +{ + public : + typedef tREAL4 tElem; + typedef cIm2D tIm; + typedef cDataIm2D tDIm; + typedef cAffin2D tAffMap; + + + cAppliCheckBoardTargetExtract(const std::vector & aVArgs,const cSpecMMVII_Appli & aSpec); + + + /// Generate a small image, centred on decteted "cCdRadiom", showing the main carateristic detecte + cRGBImage GenImaRadiom(cCdRadiom &,int aSz) const; + /// Call "GenImaRadiom" with default size + cRGBImage GenImaRadiom(cCdRadiom &) const; + /// For generating visualizatio, + std::string NameVisu(const std::string & aPref,const std::string aPost="") const; + + /// Add the information specfic to a "cCdEllipse" + void ComplImaEllipse(cRGBImage &,const cCdEllipse &) const; + + + const cFullSpecifTarget * Specif() const {return mSpecif;} ///< Accessor + int NbMinPtEllipse() const {return mNbMinPtEllipse;} ///< Accessor + private : + + /// Memorize a detection as a label, if label image is init + void SetLabel(const cPt2dr& aPt,tU_INT1 aLabel); + + // =========== overridding cMMVII_Appli::methods ============ + int Exe() override; + cCollecSpecArg2007 & ArgObl(cCollecSpecArg2007 & anArgObl) override ; + cCollecSpecArg2007 & ArgOpt(cCollecSpecArg2007 & anArgOpt) override ; + + void GenerateVisuFinal() const; + void GenerateVisuDetail(std::vector &) const; + bool IsPtTest(const cPt2dr & aPt) const; ///< Is it a point marqed a test + + /// Potentially Add a new detected + void AddCdtE(const cCdEllipse & aCDE); + + /// Generate the xml-result + void DoExport(); + + + /// Method called do each image + void DoOneImage() ; + /// Method called do each image + void DoOneImageAndScale(tREAL8,const tIm & anIm ) ; + + /// Read Image from files, init Label Image, init eventually masq 4 debug, compute blurred version of input image + void ReadImagesAndBlurr(); + /// compute points that are "topologicall" : memorized in label image as label "eTopoTmpCC" + void ComputeTopoSadles(); + /// start from topo point, compute the "saddle" criterion, and use it for filtering on relative max + void SaddleCritFiler() ; + /// start from saddle point, optimize position on symetry criterion then filter on sym thresholds + void SymetryFiler() ; + + void MakeImageLabels(const std::string & aName,const tDIm &,const cDataIm2D & aDMasq) const; + + cPhotogrammetricProject mPhProj; + cTimerSegm mTimeSegm; + + cCdRadiom MakeCdtRadiom(cScoreTetaLine&,const cCdSym &,tREAL8 aThickness); + + // =========== Mandatory args ============ + + std::string mNameIm; ///< Name of background image + std::string mNameSpecif; ///< Name of specification file + + // =========== Optionnal args ============ + + // -- + + tREAL8 mThickness; ///< used for fine estimation of radiom + bool mOptimSegByRadiom; ///< Do we optimize the segment on average radiom + + tREAL8 mLInitTeta; ///< = 05.0; + tREAL8 mLInitProl; ///< = 03.0; + tREAL8 mLengtProlong; ///< = 20.0; + tREAL8 mStepSeg; ///< = 0.5; + tREAL8 mMaxCostCorrIm; ///< = 0.1; + int mNbMaxBlackCB; ///< Number max of point in black component of checkboard + tREAL8 mPropGrayDCD; ///< Proportion Black/White for extracting + int mNbBlur1; ///< = 4, Number of initial blurring + std::string mStrShow; + + std::vector mScales; ///< Different Scales at which computation is done def {1}, => 0.5 means biggers images + // + // ---------------- Thresholds for Saddle point criteria -------------------- + tREAL8 mDistMaxLocSad ; ///< =10.0, for supressing sadle-point, not max loc in a neighboorhoud + int mDistRectInt; ///< = 20, insideness of points for seed detection + size_t mMaxNbSP_ML0 ; ///< = 30000 Max number of best point saddle points, before MaxLoc + size_t mMaxNbSP_ML1 ; ///< = 2000 Max number of best point saddle points, after MaxLoc + cPt2di mPtLimCalcSadle; ///< =(2,1) limit point for calc sadle neighbour , included, + + + // ---------------- Thresholds for Symetry criteria -------------------- + tREAL8 mThresholdSym ; ///< = 0.5, threshlod for symetry criteria + tREAL8 mRayCalcSym0 ; ///< = 8.0 distance for evaluating symetry criteria + tREAL8 mDistDivSym ; ///< = 2.0 maximal distance to initial value in symetry opt + + int mNumDebugMT; + int mNumDebugSaddle; + + // ---------------- Thresholds for Ellipse criteria -------------------- + int mNbMinPtEllipse; + bool mTryC; + tREAL8 mStepRefinePos; + + // =========== Internal param ============ + + int mZoomVisuDetec; /// zoom Visu detail of detection + int mDefSzVisDetec; /// Default Sz Visu detection + cFullSpecifTarget * mSpecif; + + tIm mImInCur; ///< Input current image + cPt2di mSzImCur; ///< Size of current image + tDIm * mDImInCur; ///< Data ccurrent image + + tIm mImIn0; ///< Input file image + cPt2di mSzIm0; ///< Size file image + tDIm * mDImIn0; ///< Data input image + + + tIm mImBlur; ///< Blurred image, used in pre-detetction + tDIm * mDImBlur; ///< Data input image + bool mHasMasqTest; ///< Do we have a test image 4 debuf (with masq) + cIm2D mMasqTest; ///< Possible image of mas 4 debug, print info ... + cIm2D mImLabel; ///< Image storing labels of centers + cDataIm2D * mDImLabel; ///< Data Image of label + cIm2D mImTmp; ///< Temporary image for connected components + cDataIm2D * mDImTmp; ///< Data Image of "mImTmp" + + std::vector mVCdtSad; ///< Candidate that are selected as local max of saddle criteria + std::vector mNbSads; ///< For info, number of sadle points at different step + std::vector mVCdtSym; ///< Candidate that are selected on the symetry criteria + // + std::vector mVCdtMerged; // Candidate merged form various scales + tREAL8 mCurScale; /// Memorize the current value of the scale + bool mMainScale; /// Is it the first/main scale + cInterpolator1D * mInterpol; +}; + + +}; // NS_CHKBRD_TARGET_EXTR +}; // MMVII