From 3d2a85cf604b74513bd5b6d5a4cee1cac639aa81 Mon Sep 17 00:00:00 2001 From: bobpan Date: Tue, 26 Jan 2021 09:47:38 +0800 Subject: [PATCH] add molun car code --- molunCar/CVUtils.cpp | 2872 ++++++++++++++++++++++ molunCar/CVUtils.h | 610 +++++ molunCar/DynamicProgramSearch.cpp | 243 ++ molunCar/DynamicProgramSearch.h | 118 + molunCar/ImageCompareModel.cpp | 2446 ++++++++++++++++++ molunCar/ImageCompareModel.h | 380 +++ molunCar/MultiScaleImageCompareModel.cpp | 146 ++ molunCar/MultiScaleImageCompareModel.h | 74 + molunCar/MultiScaleObj.cpp | 64 + molunCar/MultiScaleObj.h | 87 + molunCar/StdUtils.h | 457 ++++ molunCar/TransSolver.cpp | 297 +++ molunCar/TransSolver.h | 26 + molunCar/cvdrawutils.cpp | 182 ++ molunCar/cvdrawutils.h | 38 + molunCar/cvmatutils.cpp | 217 ++ molunCar/cvmatutils.h | 26 + molunCar/pointpair.h | 78 + 18 files changed, 8361 insertions(+) create mode 100644 molunCar/CVUtils.cpp create mode 100644 molunCar/CVUtils.h create mode 100644 molunCar/DynamicProgramSearch.cpp create mode 100644 molunCar/DynamicProgramSearch.h create mode 100644 molunCar/ImageCompareModel.cpp create mode 100644 molunCar/ImageCompareModel.h create mode 100644 molunCar/MultiScaleImageCompareModel.cpp create mode 100644 molunCar/MultiScaleImageCompareModel.h create mode 100644 molunCar/MultiScaleObj.cpp create mode 100644 molunCar/MultiScaleObj.h create mode 100644 molunCar/StdUtils.h create mode 100644 molunCar/TransSolver.cpp create mode 100644 molunCar/TransSolver.h create mode 100644 molunCar/cvdrawutils.cpp create mode 100644 molunCar/cvdrawutils.h create mode 100644 molunCar/cvmatutils.cpp create mode 100644 molunCar/cvmatutils.h create mode 100644 molunCar/pointpair.h diff --git a/molunCar/CVUtils.cpp b/molunCar/CVUtils.cpp new file mode 100644 index 0000000..461ecf5 --- /dev/null +++ b/molunCar/CVUtils.cpp @@ -0,0 +1,2872 @@ +#include "CVUtils.h" +#include "DynamicProgramSearch.h" +#include "StdUtils.h" +// no need to code for plotting in visual studio later than 2017, +// install the ArrayPlotter extension to see the data distribution +#if (_MSC_VER < 1910) // vs2017 +#include "../3rd/cvplot/cvplot.h" +#endif +#include "Luffy.h" +#include "cvmatutils.h" +#include "cvmlutils.h" +#include "cvdrawutils.h" +#include "TransSolver.h" + +namespace CyclopsUtils { + +Mat gDummyMat; +Scalar gDummyScalar; + +bool localMeanVarNorm(const Mat& srcMat, Mat& dstMat, int winRadius, double tarMean/* = 120*/, double tarStd /*= 30*/) +{ + int width = srcMat.cols; + int height = srcMat.rows; + + if (winRadius * 2 + 1 >= width || winRadius * 2 + 1 >= height) + { + return false; + } + + dstMat = srcMat.clone(); + + Mat sumMat, sqSumMat; + cv::integral(srcMat, sumMat, sqSumMat, CV_64F); + int area = (2 * winRadius + 1)*(2 * winRadius + 1); + for (int i = winRadius; i < height - winRadius; i++) + { + const uchar *pData = srcMat.ptr(i); + uchar *tarData = dstMat.ptr(i); + double *pSumY1 = (double *)sumMat.ptr(i - winRadius); + double *pSumY2 = (double *)sumMat.ptr(i + winRadius + 1); + double *pSqSumY1 = (double *)sqSumMat.ptr(i - winRadius); + double *pSqSumY2 = (double *)sqSumMat.ptr(i + winRadius + 1); + for (int j = winRadius; j < width - winRadius; j++) + { + double sumX1Y1 = pSumY1[j - winRadius]; + double sumX2Y2 = pSumY2[j + winRadius + 1]; + double sumX1Y2 = pSumY2[j - winRadius]; + double sumX2Y1 = pSumY1[j + winRadius + 1]; + double sqSumX1Y1 = pSqSumY1[j - winRadius]; + double sqSumX2Y2 = pSqSumY2[j + winRadius + 1]; + double sqSumX1Y2 = pSqSumY2[j - winRadius]; + double sqSumX2Y1 = pSqSumY1[j + winRadius + 1]; + double val = sumX2Y2 + sumX1Y1 - sumX1Y2 - sumX2Y1; + double meanVal = val / area; + double sqSum = sqSumX2Y2 + sqSumX1Y1 - sqSumX1Y2 - sqSumX2Y1; + double stdVar = sqrt((sqSum - 2 * val*meanVal + area * meanVal * meanVal) / area); + double s = tarStd / stdVar; + double t = tarMean - meanVal * s; + + int value = pData[j] * s + t; + + if (value > 255) + { + tarData[j] = 255; + } + else + { + tarData[j] = value; + } + } + } + + return true; +} + +Mat gridThre(const Mat& img, int gridWidth, int gridHeight, float threStdDevScale, + LogicOper oper /*= LP_LOGIC_SMALLER*/, + float majority /*= 0.8*/) +{ + Mat ret = Mat::zeros(img.size(), CV_8UC1); + int gridXNum = img.cols / gridWidth + 1; + int gridYNum = img.rows / gridHeight + 1; + for (int gy = 0; gy < gridYNum - 1; ++gy) + { + int y = gy*gridHeight; + for (int gx = 0; gx < gridXNum - 1; ++gx) + { + int x = gx*gridWidth; + Rect roi(x, y, gridWidth, gridHeight); + Mat roiImg(img, roi); + double mean, stddev; + meanStdDev(roiImg, Mat(), &mean, &stddev, majority); + Mat threImg; + switch (oper) + { + case LP_LOGIC_SMALLER: + threImg = roiImg < (mean + stddev*threStdDevScale); + break; + case LP_LOGIC_LARGER: + threImg = roiImg > (mean + stddev*threStdDevScale); + break; + } + + threImg.copyTo(Mat(ret, roi)); + } + } + for (int gy = 0; gy < gridYNum; ++gy) + { + int y = gy*gridHeight; + Rect roi(img.cols - gridWidth, y, gridWidth, gridHeight); + roi &= Rect(0, 0, img.cols, img.rows); + if (roi.width == 0 || roi.height == 0) + { + continue; + } + Mat roiImg(img, roi); + double mean, stddev; + meanStdDev(roiImg, Mat(), &mean, &stddev, majority); + Mat threImg; + switch (oper) + { + case LP_LOGIC_SMALLER: + threImg = roiImg < (mean + stddev*threStdDevScale); + break; + case LP_LOGIC_LARGER: + threImg = roiImg > (mean + stddev*threStdDevScale); + break; + } + threImg.copyTo(Mat(ret, roi)); + } + return ret; +} +Mat gridWhiteThre(const Mat& img, int gridWidth, int gridHeight, float threStdDevScale, float majority /*= 0.8*/) +{ + return gridThre(img, gridWidth, gridHeight, threStdDevScale, LP_LOGIC_LARGER, majority); +} +Mat gridBlackThre(const Mat& img, int gridWidth, int gridHeight, float threStdDevScale, float majority /*= 0.8*/) +{ + return gridThre(img, gridWidth, gridHeight, threStdDevScale, LP_LOGIC_SMALLER, majority); + + Mat ret = Mat::zeros(img.size(), CV_8UC1); + int gridXNum = img.cols / gridWidth + 1; + int gridYNum = img.rows / gridHeight + 1; + for (int gy = 0; gy < gridYNum - 1; ++gy) + { + int y = gy*gridHeight; + for (int gx = 0; gx < gridXNum - 1; ++gx) + { + int x = gx*gridWidth; + Rect roi(x, y, gridWidth, gridHeight); + Mat roiImg(img, roi); + double mean, stddev; + meanStdDev(roiImg, Mat(), &mean, &stddev, majority); + Mat threImg = roiImg < (mean + stddev*threStdDevScale); + threImg.copyTo(Mat(ret, roi)); + } + } + for(int gy = 0; gy < gridYNum; ++gy) + { + int y = gy*gridHeight; + Rect roi((gridXNum - 1)*gridWidth, y, gridWidth, gridHeight); + roi &= Rect(0, 0, img.cols, img.rows); + if (roi.width == 0 || roi.height == 0) + { + continue; + } + Mat roiImg(img, roi); + double mean, stddev; + meanStdDev(roiImg, Mat(), &mean, &stddev, (float)0.8); + Mat threImg = roiImg < (mean + stddev*threStdDevScale); + threImg.copyTo(Mat(ret, roi)); + } + return ret; +} + +void converToType(cv::Mat& mat, int mattype) +{ + cv::Mat _mat; + mat.convertTo(_mat, mattype); + mat = _mat; +} + +void genScharrImage(Mat& img) +{ + Mat sobelx, sobely; + Sobel(img, sobelx, CV_32FC1, 1, 0, CV_SCHARR); + Sobel(img, sobely, CV_32FC1, 0, 1, CV_SCHARR); + img = sobelx.mul(sobelx) + sobely.mul(sobely); + Mat tempImg; + img.convertTo(tempImg, CV_32FC1); + Mat tempImg0; + sqrt(tempImg, tempImg0); + img = tempImg0; +} + +void genSobelImage(Mat& img, Mat* pSobelx /*= NULL*/, Mat* pSobely /*= NULL*/) +{ + Mat sobelx, sobely; + Sobel(img, sobelx, CV_32FC1, 1, 0, BORDER_REPLICATE); + Sobel(img, sobely, CV_32FC1, 0, 1, BORDER_REPLICATE); + img = sobelx.mul(sobelx) + sobely.mul(sobely); + Mat tempImg; + img.convertTo(tempImg, CV_32FC1); + Mat tempImg0; + sqrt(tempImg, tempImg0); + img = tempImg0; + + if (pSobelx) + { + *pSobelx = sobelx; + } + if (pSobely) + { + *pSobely = sobely; + } +} + +cv::Mat genXDeriLineKernel(int w, int type, double absVal) +{ + assert(w % 2 == 1); + + Mat leftKernel = Mat::ones(1, w / 2, type)*absVal; + Mat rightKernel = Mat::ones(1, w / 2, type)*absVal*(-1); + Mat kernel = Mat::zeros(1, w, type); + leftKernel.copyTo(kernel.colRange(0, w / 2)); + rightKernel.copyTo(kernel.colRange(w / 2 + 1, w)); + return kernel; +} + +cv::Mat genXDeriMat(const Mat& img, int w, double absVal) +{ + assert(w % 2 == 1); + int kernelRadius = w / 2; + + Mat xKernel = genXDeriLineKernel(kernelRadius * 2 + 1, CV_32FC1, 1); + Mat xDeriMat; + sepFilter2D(img, xDeriMat, CV_32FC1, xKernel, Mat::ones(1, 1, CV_32FC1)); + + return xDeriMat; +} + +cv::Mat normEachRow(const Mat& img) +{ + Mat ret(img.rows, img.cols, img.type()); + for (int y = 0; y < img.rows; y++) + { + Mat rowMat = img.row(y); + double minVal, maxVal; + cv::minMaxIdx(rowMat, &minVal, &maxVal); + rowMat = rowMat / maxVal; + rowMat.copyTo(ret.row(y)); + } + return ret; +} + +cv::Mat genGradientDir4EachRow(const Mat& img) +{ + assert(img.cols >= 2); + + Mat xDeriMat = genXDeriMat(img, 3, 1); + Mat threMat; + threshold(xDeriMat, threMat, 0, 1, cv::THRESH_BINARY); + + return threMat; +} + +void findMatElementsEquals(const Mat& img, vector& pointVec, float val, int xPadding) +{ + assert(img.type() == CV_32FC1); + + for (int y = 0; y < img.rows; ++y) + { + float* pRowData = (float*)img.row(y).data; + float* pRowDataStart = pRowData; + float* pRowDataEnd = pRowData + img.cols - xPadding; + pRowData += xPadding; + while (pRowData != pRowDataEnd) + { + if (abs(*pRowData - val) < 0.0001) + { + pointVec.push_back(Point(pRowData - pRowDataStart, y)); + } + pRowData++; + } + } +} + +double localMatSum(const Mat& mat, const Rect& roi) +{ + Mat localMat(mat, roi); + return sum(localMat).val[0]; +} + +cv::Mat normCanvas(const Mat& img) +{ + Mat canvas; + double maxVal, minVal; + minMaxIdx(img, &minVal, &maxVal); + if (maxVal <= 1.0) + { + img.convertTo(canvas, CV_8UC1, 255); + } + else + { + img.convertTo(canvas, CV_8UC1); + } + return canvas; +} + +void findEdgePointsEachRow(const Mat& img, vector& edgePointVec, int xPadding) +{ + // img is a binary image. + // an edge point in a row satisfies: + // 1) left neighbor pixels are all black (0) or white (1); + // 2) right neighbor pixels are all white or black. + + // find candidate turning points + Mat sumKernelMat = Mat::ones(1, 2, img.type()); + Mat sumMat; + sepFilter2D(img, sumMat, img.type(), sumKernelMat, Mat::ones(1, 1, img.type())); + vector turningPointVec; + + // for pixels ... 0, 0, 1, 1 ..., turning point is + // * + findMatElementsEquals(sumMat, turningPointVec, 1.0, xPadding); + + // filter turning points + vector filteredTurningPointVec; + int localRadius = 30; + int localSumTor = 5; + for (size_t i = 0; i < turningPointVec.size(); ++i) + { + Point pt(turningPointVec[i]); + Rect leftRoi(pt.x - localRadius, pt.y, localRadius, 1); + double leftSum = localMatSum(img, leftRoi); + Rect rightRoi(pt.x, pt.y, localRadius, 1); + double rightSum = localMatSum(img, rightRoi); + if (leftSum > rightSum && abs(leftSum - localRadius) < localSumTor && rightSum < 0.0001) + { + filteredTurningPointVec.push_back(pt); + } + else if (leftSum < rightSum && leftSum < 0.0001 && abs(rightSum - localRadius) < localSumTor) + { + filteredTurningPointVec.push_back(pt); + } + } + + + Mat canvas = drawPoints(img, filteredTurningPointVec, 125, 10); + + + edgePointVec = filteredTurningPointVec; +} + +Mat sumEachRow2(const Mat& img) +{ +#define _sumEachRow2(t)\ +if (img.channels() == 1) return sumEachRow(img);\ +else if (img.channels() == 2) return sumEachRowN(img);\ +else if (img.channels() == 3) return sumEachRowN(img);\ +else if (img.channels() == 4) return sumEachRowN(img);\ +else { _ASSERTE(false && "not implemented"); return gDummyMat; } + + switch (img.depth()) + { + case CV_8U: + _sumEachRow2(unsigned char); + case CV_16S: + _sumEachRow2(short); + case CV_32S: + _sumEachRow2(int); + case CV_32F: + _sumEachRow2(float); + case CV_64F: + _sumEachRow2(double); + default: + _ASSERTE(false && "not implemented"); + } + return gDummyMat; +} + +Mat sumEachCol2(const Mat& img) +{ +#define _sumEachCol2(t)\ +if (img.channels() == 1) return sumEachCol(img);\ +else if (img.channels() == 2) return sumEachColN(img);\ +else if (img.channels() == 3) return sumEachColN(img);\ +else if (img.channels() == 4) return sumEachColN(img);\ +else { _ASSERTE(false && "not implemented"); return gDummyMat; } + + switch (img.depth()) + { + case CV_8U: + _sumEachCol2(unsigned char); + case CV_16S: + _sumEachCol2(short); + case CV_32S: + _sumEachCol2(int); + case CV_32F: + _sumEachCol2(float); + case CV_64F: + _sumEachCol2(double); + default: + _ASSERTE(false && "not implemented"); + } + return gDummyMat; +} + +cv::Mat thresholdEachRowLocally(const Mat& img, int localRange /*= 501*/, int C /*= 10*/) +{ + Mat ret = img.clone(); + for (int y = 0; y < img.rows; ++y) + { + Mat rowMat = img.row(y); + uchar* pData = (uchar*)rowMat.data; + cv::adaptiveThreshold(rowMat, ret.row(y), 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, localRange, C); + } + return ret; +} + +cv::Mat thresholdEachRow(const Mat& img, float threScale /*= 0.5*/) +{ + Mat ret = img.clone(); + for (int y = 0; y < img.rows; ++y) + { + Mat rowMat = img.row(y); + double rowSum = sum(rowMat).val[0]; + double rowAvg = rowSum / img.cols * threScale; + Mat rowThre = ret.row(y); + threshold(rowMat, rowThre, rowAvg, 255, THRESH_BINARY); + } + return ret; +} + +cv::Mat thresholdEachRow(const Mat& img, const Mat& rowThre, float threScale) +{ + Mat ret = Mat::zeros(img.rows, img.cols, img.type()); + for (int y = 0; y < img.rows; ++y) + { + Mat retRow = ret.row(y); + uchar* pRetData = retRow.data; + Mat imgRow = img.row(y); + uchar* pData = imgRow.data; + double* pThreData = (double*)rowThre.data; + for (int x = 0; x < img.cols; ++x) + { + if (*pData < (*pThreData)*threScale) + { + *pRetData = 0; + } + else + { + *pRetData = 255; + } + pData++; + pThreData++; + pRetData++; + } + } + return ret; +} + +void convertPointPair2PointfPair(const vector& vec0, vector& vec1) +{ + vec1.clear(); + for (auto i = vec0.begin(); i != vec0.end(); ++i) + { + PointfPair pointfPair(i->first, i->second); + pointfPair.setAver(i->aver()); + pointfPair.setStr(i->getStr()); + vec1.push_back(pointfPair); + } +} + +double pointPairDis(const PointPair& i, const PointPair& j) +{ + double dis0 = pointDis(i.first, j.first); + double dis1 = pointDis(i.second, j.second); + return (dis0 + dis1) / 2.0; +} + +void segEachRow(const Mat& img, vector& pointPairVec, int xPadding) +{ + for (int y = 0; y < img.rows; ++y) + { + Mat rowMat = img.row(y); + uchar* pRowData = rowMat.data; + uchar* pRowStart = pRowData; + uchar* pRowEnd = rowMat.data + img.cols - xPadding; + pRowData += xPadding; + int leftX = -1; + while (pRowData != pRowEnd) + { + uchar val = *pRowData; + if (val == 0 && leftX == -1) + { + leftX = pRowData - pRowStart; + } + else if (val != 0 && leftX >= 0) + { + Point leftPt(leftX, y); + Point rightPt(pRowData - pRowStart - 1, y); + PointPair pointPair(leftPt, rightPt); + pointPairVec.push_back(pointPair); + leftX = -1; + } + pRowData++; + } + } +} + +void fillPointPairVal(const Mat& img, std::string filePath, vector& pointPairVec) +{ + for (auto i = pointPairVec.begin(); i != pointPairVec.end(); ++i) + { + Mat mat(img, Rect(i->first.x, i->first.y, i->second.x - i->first.x, 1)); + i->setAver(sum(mat).val[0] / (double)mat.cols); + std::stringstream ss; + ss << filePath << " x: " << i->first.x << " y: " << i->first.y; + i->setStr(ss.str()); + } +} + +cv::Mat getFirstChannel(const Mat& img) +{ + vector channels; + split(img, channels); + return channels.front(); +} +Mat getChannel(const Mat& img, int i) +{ + if (i < 0 || i >= img.channels()) + { + return Mat(); + } + vector channels; + split(img, channels); + + return channels[i]; +} + +void mulEachRow(Mat& img, const Mat& scaleRow) +{ + for (int i = 0; i < img.rows; ++i) + { + Mat row = img.row(i); + uchar* pRowData = (uchar*)row.data; + double* pScaleData = (double*)scaleRow.data; + for (int j = 0; j < row.cols; ++j) + { + *pRowData *= *pScaleData; + ++pRowData; + ++pScaleData; + } + } +} + +void genRandomPoints(const Mat& img, vector& points, RNG rng, int sampleCnt /* = 1000 */) +{ + sampleCnt = cv::min(sampleCnt, img.rows * img.cols ); + for (int i = 0; i < sampleCnt; ++i) + { + Vec3b* pData = (Vec3b*)img.row(rng.uniform(0, img.rows)).data; + Vec3b& d = pData[rng.uniform(0, img.cols)]; + + Point p; + p.x = d[0]; + p.y = d[1]; + points.push_back(p); + } +} + +void plot8uVec(const Mat& vec, float scale /*= 1.0*/) +{ + + Mat canvas = Mat::zeros(100 * scale, 100 * scale, CV_8UC1); + Rect rect(0, 0, canvas.cols * scale, canvas.rows * scale); + + int size = cv::max(vec.rows, vec.cols); + uchar* pVecData = vec.data; + for (int i = 0; i < size; ++i) + { + Point pt(i * scale, canvas.rows - 1 - pVecData[i] * scale); + if (rect.contains(pt)) + { + canvas.at(pt.y, pt.x) = 255; + } + } + imshow("plot8uVec", canvas); +} + +void plot32fVec(const Mat& vec, float scale /*= 1.0*/) +{ + Mat canvas = Mat::zeros(100 * scale, 100 * scale, CV_8UC1); + Rect rect(0, 0, canvas.cols * scale, canvas.rows * scale); + int size = cv::max(vec.rows, vec.cols); + float* pVecData = (float*)vec.data; + for (int i = 0; i < size; ++i) + { + Point pt(i * scale, canvas.rows - 1 - pVecData[i] * scale); + if (rect.contains(pt)) + { + canvas.at(pt.y, pt.x) = 255; + } + } + imshow("plot32fVec", canvas); +} + +cv::Mat calcHist(const Mat& img, const Mat& mask, int histSize /*= 256*/, int minVal, int maxVal) +{ + Mat histMat; + float fRange[] = { minVal, maxVal }; + const float* fHistRange = { fRange }; + + cv::calcHist(&img, 1, NULL, mask, histMat, 1, &histSize, &fHistRange); + return histMat; +} + +Mat resize(const Mat& img, float s, int interMethod /*= cv::INTER_LINEAR*/) +{ + Mat ret; + Size newSize(img.cols*s, img.rows*s); + if (newSize.width == 0 || newSize.height == 0) + { + return img; + } + resize(img, ret, Size(), s, s, interMethod); + return ret; +} + +void writeFile(const Mat& mat, std::string filePath, std::string matName /*= "mat"*/) +{ + FileStorage fs; + fs.open(filePath, FileStorage::WRITE); + fs << matName << mat; + fs.release(); +} + +cv::Mat readFile(std::string filePath, std::string matName) +{ + FileStorage fs; + fs.open(filePath, FileStorage::READ); + Mat ret; + fs[matName] >> ret; + return ret; +} + +void gaussianBlurEachRow(const Mat& src, Mat& dst, int ksize /*= 3*/) +{ + dst = Mat::zeros(src.rows, src.cols, src.type()); + for (int y = 0; y < src.rows; y++) + { + Mat dstRow = dst.row(y); + Mat srcRow = src.row(y); + cv::GaussianBlur(srcRow, dstRow, cv::Size(ksize, 1), 1.0); + } +} + +void medianBlurEachRow(const Mat& src, Mat& dst, int ksize /*= 3*/) +{ + dst = Mat::zeros(src.rows, src.cols, src.type()); + for (int y = 0; y < src.rows; y++) + { + Mat dstRow = dst.row(y); + Mat srcRow = src.row(y); + cv::medianBlur(srcRow, dstRow, 1); + } +} + +double maxLaplacianX(const Mat& rowMat, float scale /*= 1.0*/) +{ + Mat sRowMat; + Mat lapMat; + + if (scale != 1.0) + { + resize(rowMat, sRowMat, Size(), scale, 1.0, INTER_CUBIC); + Laplacian(sRowMat, lapMat, CV_32FC1, 3); + } + else + { + Laplacian(rowMat, lapMat, CV_32FC1); + } + double minVal, maxVal; + int minIdx, maxIdx; + minMaxIdx(lapMat, &minVal, &maxVal, &minIdx, &maxIdx); + return maxIdx / scale; +} + +double minLaplacianX(const Mat& rowMat, float scale /*= 1.0*/) +{ + Mat sRowMat; + Mat lapMat; + + if (scale != 1.0) + { + resize(rowMat, sRowMat, Size(), scale, 1.0, INTER_CUBIC); + Laplacian(sRowMat, lapMat, CV_32FC1, 3); + } + else + { + Laplacian(rowMat, lapMat, CV_32FC1); + } + double minVal, maxVal; + int minIdx, maxIdx; + minMaxIdx(lapMat, &minVal, &maxVal, &minIdx, &maxIdx); + return minIdx / scale; +} + +void Laplacian1D(const Mat& src, Mat& dst, int ddepth, int ksize /*= 1*/) +{ + assert(src.rows == 1); + Mat kernel; + float K1[3] = { 1, -2, 1 }; + float K3[5] = { 1, 2, -6, 2, 1 }; + float K5[7] = { 1, 1, 1, -6, 1, 1, 1 }; + float K7[9] = { 1, 1, 1, 1, -8, 1, 1, 1, 1 }; + float K9[11] = { 1, 1, 1, 1, 1, -10, 1, 1, 1, 1, 1 }; + switch (ksize) + { + case 1: + kernel = Mat(1, 3, CV_32F, K1); + break; + case 3: + kernel = Mat(1, 5, CV_32F, K3); + break; + case 5: + kernel = Mat(1, 7, CV_32F, K5); + break; + case 7: + kernel = Mat(1, 9, CV_32F, K7); + break; + case 9: + kernel = Mat(1, 11, CV_32F, K9); + break; + default: + assert(0); + break; + } + filter2D(src, dst, ddepth, kernel); +} + + +void _filterKeyPointsByNeighborDistance(vector& vec, float ndis) +{ + vector ret; + for (size_t i = 0; i < vec.size(); ++i) + { + KeyPoint kp0 = vec[i]; + bool isDiscard = false; + for (size_t j = i + 1; j < vec.size(); ++j) + { + KeyPoint kp1 = vec[j]; + float dis = pointDis(kp0.pt, kp1.pt); + if (dis < ndis) + { + if (kp0.response < kp1.response) + { + isDiscard = true; + break; + } + } + } + if (!isDiscard) + { + ret.push_back(kp0); + } + } + vec = ret; +} + +void filterKeyPointsByNeighborDistance(vector& vec, float ndis) +{ + _filterKeyPointsByNeighborDistance(vec, ndis); + vec = vector(vec.rbegin(), vec.rend()); + _filterKeyPointsByNeighborDistance(vec, ndis); +} + +float IC_Angle_u8(const Mat& image, const int half_k, Point2f pt, + const vector & u_max) +{ + int m_01 = 0, m_10 = 0; + + const uchar* center = &image.at(cvRound(pt.y), cvRound(pt.x)); + + // Treat the center line differently, v=0 + for (int u = -half_k; u <= half_k; ++u) + m_10 += u * center[u]; + + // Go line by line in the circular patch + int step = (int)image.step1(); + for (int v = 1; v <= half_k; ++v) + { + // Proceed over the two lines + int v_sum = 0; + int d = u_max[v]; + for (int u = -d; u <= d; ++u) + { + int val_plus = center[u + v*step], val_minus = center[u - v*step]; + v_sum += (val_plus - val_minus); + m_10 += u * (val_plus + val_minus); + } + m_01 += v * v_sum; + } + + return fastAtan2((float)m_01, (float)m_10); +} + +float IC_Angle_f32(const Mat& image, const int half_k, Point2f pt, + const vector & u_max) +{ + float m_01 = 0, m_10 = 0; + + const float* center = &image.at(cvRound(pt.y), cvRound(pt.x)); + + // Treat the center line differently, v=0 + for (int u = -half_k; u <= half_k; ++u) + m_10 += u * center[u]; + + // Go line by line in the circular patch + int step = (int)image.step1(); + for (int v = 1; v <= half_k; ++v) + { + // Proceed over the two lines + float v_sum = 0; + int d = u_max[v]; + for (int u = -d; u <= d; ++u) + { + float val_plus = center[u + v*step], val_minus = center[u - v*step]; + v_sum += (val_plus - val_minus); + m_10 += u * (val_plus + val_minus); + } + m_01 += v * v_sum; + } + + return fastAtan2((float)m_01, (float)m_10); +} + +float IC_Angle(const Mat& image, const int half_k, Point2f pt, + const vector & u_max) +{ + if (image.type() == CV_8UC1) + { + return IC_Angle_u8(image, half_k, pt, u_max); + } + else if (image.type() == CV_32FC1) + { + return IC_Angle_f32(image, half_k, pt, u_max); + } + else + { + std::cout << "image type " << image.type() << " is not supported" << std::endl; + return FLT_MAX; + } +} + +void computeOrientation(const Mat& image, vector& keypoints, + int halfPatchSize, const vector& umax) +{ + // Process each keypoint + for (vector::iterator keypoint = keypoints.begin(), + keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint) + { + keypoint->angle = IC_Angle(image, halfPatchSize, keypoint->pt, umax); + } +} + +void genUMax(vector& umax, int halfPatchSize) +{ + umax.resize(halfPatchSize + 2); + + int v, v0, vmax = cvFloor(halfPatchSize * sqrt(2.f) / 2 + 1); + int vmin = cvCeil(halfPatchSize * sqrt(2.f) / 2); + for (v = 0; v <= vmax; ++v) + umax[v] = cvRound(sqrt((double)halfPatchSize * halfPatchSize - v * v)); + + // Make sure we are symmetric + for (v = halfPatchSize, v0 = 0; v >= vmin; --v) + { + while (umax[v0] == umax[v0 + 1]) + ++v0; + umax[v] = v0; + ++v0; + } +} + +void filterKeyPointsByRotationInvariants(vector& vec, const Mat& img, + FeatureDetector* pFeatDetector, float tor) +{ + Point2f cen(img.cols / 2.0, img.rows / 2.0); + +// no longer supported in opencv3.4.1 +#if (CV_MAJOR_VERSION < 3) + int patchSize = pFeatDetector->getInt("patchSize"); +#else + int patchSize = pFeatDetector->descriptorSize(); +#endif + int halfPatchSize = patchSize / 2; + vector umax; + genUMax(umax, halfPatchSize); + + for (int i = 0; i < 360; ++i) + { + Mat trans = getRotationMatrix2D(cen, i, 1.0); + Mat transedImg; + warpAffine(img, transedImg, trans, Size(img.cols, img.rows)); + computeOrientation(img, vec, halfPatchSize, umax); + } +} + +double localIC_Angle(const Mat& img, Point2f center, int patchSize) +{ + int halfPatchSize = patchSize / 2; + vector umax; + genUMax(umax, halfPatchSize); + + return IC_Angle(img, halfPatchSize, center, umax); +} + +double localAngle_WeightedCen(const Mat& img, Point2f center) +{ +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vImg = img/255.0; +#endif + Point2f weightedCen(0, 0); + float sum = 0; + for (int y = 0; y < img.rows; ++y) + { + float* pRowData = (float*)img.row(y).data; + for (int x = 0; x < img.cols; ++x) + { + float val = pRowData[x]; + if (val == 0) + { + continue; + } + weightedCen.x += x*val; + weightedCen.y += y*val; + sum += val; + } + } + weightedCen.x /= sum; + weightedCen.y /= sum; + + weightedCen = weightedCen - center; + + return fastAtan2(weightedCen.y, weightedCen.x); +} + +double localAngle_(const Mat& img, Point2f center, Mat mask) +{ +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vImg = img / 255.0; +#endif + Scalar meanScalar, stddevScalar; + meanStdDev(img, meanScalar, stddevScalar, mask); + Mat meanNormImg = img - meanScalar.val[0]; + float thre = stddevScalar.val[0] * 4; + meanNormImg.setTo(0, meanNormImg < thre); + meanNormImg.setTo(1, meanNormImg > thre); + return localAngle_WeightedCen(meanNormImg, center); +} + +void upperMajorityMask(const Mat& img, Mat& mask, float majority) +{ + Mat hist; + majorityHistUpper(img, Mat(), hist, majority); + float* pHistData = (float*)hist.data; + int histSize = hist.rows > hist.cols ? hist.rows : hist.cols; + float* pEndHistData = pHistData + histSize; + int idx = 0; + while (pHistData != pEndHistData) + { + if (*pHistData) + { + break; + } + idx++; + pHistData++; + } + mask = (img >= idx); +} + +void lowerMajorityHist(const Mat& img, const Mat& mask, Mat& hist, float majority) +{ + hist = calcHist(img, mask); + + // suppose there is only one peak + float* pHistData = (float*)hist.data; + float curRatio = 1.0; + int totalNum = sum(hist).val[0]; + + float* pFront = pHistData; + float* pTail = pHistData + 254; + while (pFront != pTail) + { + if (*pTail == 0) + { + pTail--; + continue; + } + + { + curRatio -= *pTail / totalNum; + *pTail = 0; + pTail--; + } + if (curRatio <= majority) + { + break; + } + } + +} + +void majorityHistLower(const Mat& img, const Mat& mask, Mat& hist, float majority) +{ + hist = calcHist(img, mask); + + // suppose there is only one peak + float* pHistData = (float*)hist.data; + float curRatio = 1.0; + int totalNum = sum(hist).val[0]; + + float* pFront = pHistData; + float* pTail = pHistData + 254; + while (pFront != pTail) + { + if (*pTail == 0) + { + pTail--; + continue; + } + + { + curRatio -= *pTail / totalNum; + *pTail = 0; + pTail--; + } + if (curRatio <= majority) + { + break; + } + } + +} + +void majorityHistUpper(const Mat& img, const Mat& mask, Mat& hist, float majority) +{ + hist = calcHist(img, mask); + + // suppose there is only one peak + float* pHistData = (float*)hist.data; + float curRatio = 1.0; + int totalNum = sum(hist).val[0]; + + float* pFront = pHistData; + float* pTail = pHistData + 254; + while (pFront != pTail) + { + if (*pFront == 0) + { + pFront++; + continue; + } + + { + curRatio -= *pFront / totalNum; + *pFront = 0; + pFront++; + } + if (curRatio <= majority) + { + break; + } + } + +} +void majorityHist(const Mat& img, const Mat& mask, Mat& hist, float majority) +{ + hist = calcHist(img, mask); + + // suppose there is only one peak + float* pHistData = (float*)hist.data; + float curRatio = 1.0; + int totalNum = sum(hist).val[0]; + + float* pFront = pHistData; + float* pTail = pHistData + 254; + while (pFront != pTail) + { + if (*pFront == 0) + { + pFront++; + continue; + } + if (*pTail == 0) + { + pTail--; + continue; + } + if (*pFront < *pTail) + { + curRatio -= *pFront / totalNum; + *pFront = 0; + pFront++; + } + else + { + curRatio -= *pTail / totalNum; + *pTail = 0; + pTail--; + } + if (curRatio <= majority) + { + break; + } + } + + +} + +Mat lowerMajorityMask(const Mat& img, const Mat& mask, float majority) +{ + Mat hist; + lowerMajorityHist(img, mask, hist, majority); + float* pHistData = (float*)hist.data; + + Mat ret = mask.clone(); + + for (int i = 0; i < img.rows; ++i) + { + uchar* pRowData = (uchar*)img.row(i).data; + uchar* pMaskRowData = (uchar*)ret.row(i).data; + for (int j = 0; j < img.cols; ++j) + { + uchar val = pRowData[j]; + if (!pHistData[val]) + { + pMaskRowData[j] = 0; + } + } + } + + return ret; +} + +void meanStdDev(const Mat& img, const Mat& mask, + double* pMean, double* pStdDev, float majority, int type) +{ + Mat hist; + switch (type) + { + default: + case 0: + majorityHist(img, mask, hist, majority); + break; + case 1: + majorityHistLower(img, mask, hist, majority); + break; + case 2: + majorityHistUpper(img, mask, hist, majority); + break; + } + + // suppose there is only one peak + Mat rowHist = toRowVec(hist); + float* pHistData = (float*)rowHist.data; + + histMeanStddev(rowHist, pMean, pStdDev); +} + +float interpolate(float* pY, int n, float stepX, float x) +{ + int lIdx = (int)(x/stepX); + int rIdx = lIdx + 1; + if (rIdx > n - 1) + { + return pY[n - 1]; + } + assert(lIdx >= 0 && lIdx < n && rIdx >= 0 && rIdx < n); + float s = (x - lIdx*stepX)/stepX; + float ly = pY[lIdx]; + float ry = pY[rIdx]; + return ly + (ry - ly)*s; +} + +cv::Mat cocentricNorm(const Mat& img, Point2f center, const Mat& weightMat, float dstMeanVal) +{ + assert(weightMat.empty() || weightMat.type() == CV_32FC1); + + int w = img.cols; + int h = img.rows; + vector corners; + corners.push_back(Point2f(0, 0)); + corners.push_back(Point2f(0, h)); + corners.push_back(Point2f(w, h)); + corners.push_back(Point2f(w, 0)); + vector cornerDisVec; + for_each(corners.begin(), corners.end(), [&](const Point2f& pt) + { + double dis = pointDis(center, pt); + cornerDisVec.push_back(dis); + }); + + auto farthestCornerDis = max_element(cornerDisVec.begin(), cornerDisVec.end()); + float maxRadius = *farthestCornerDis; + + int radiusNum = floorf(maxRadius); + //radiusNum = 20; + float radiusStep = (maxRadius / radiusNum); + Mat cocentricSumMat = Mat::zeros(1, radiusNum, CV_32FC1); + float* pSumData = (float*)cocentricSumMat.data; + Mat cocentricWeightSumMat = Mat::zeros(1, radiusNum, CV_32FC1); + float* pWeightSumData = (float*)cocentricWeightSumMat.data; + Mat radiusMat(img.rows, img.cols, CV_32FC1); + + for (int y = 0; y < h; y++) + { + const Mat& imgRow = img.row(y); + float* pImgRowData = (float*)imgRow.data; + float* pRadiusRowData = (float*)radiusMat.row(y).data; + + float* pWeightRowData = NULL; + if (!weightMat.empty()) + { + pWeightRowData = (float*)weightMat.row(y).data; + } + + for (int x = 0; x < w; x++) + { + //std::cout << x << " " << y << std::endl; + float weight; + if (pWeightRowData) + { + weight = pWeightRowData[x]; + } + else + { + weight = 1.0; + } + float val = pImgRowData[x] * weight; + float radius = pointDis(Point2f(x, y), center); + pRadiusRowData[x] = radius; + int radiusIdx0 = (int)(radius / radiusStep); + assert(radiusIdx0 >= 0); + int radiusIdx1 = radiusIdx0 + 1; + if (radiusIdx0 >= radiusNum - 1) + { + pSumData[radiusNum - 1] += val; + pWeightSumData[radiusNum - 1] += weight; + } + else + { + float s = (radius - radiusStep*radiusIdx0) / radiusStep; + pSumData[radiusIdx0] += val*s; + pSumData[radiusIdx1] += val*(1 - s); + pWeightSumData[radiusIdx0] += s*weight; + pWeightSumData[radiusIdx1] += (1 - s)*weight; + } + } + } + + for (int i = 0; i < radiusNum; ++i) + { + //float radius = (i*radiusStep + radiusStep) / 2; + if (pWeightSumData[i] == 0) + { + + } + else + { + pSumData[i] /= pWeightSumData[i]; + } + } + + Mat retMat = Mat::zeros(img.rows, img.cols, img.type()); + Mat normMask = Mat::zeros(img.rows, img.cols, img.type()); + for (int y = 0; y < h; y++) + { + float* pImgRowData = (float*)img.row(y).data; + float* pRetRowData = (float*)retMat.row(y).data; + float* pRadiusData = (float*)radiusMat.row(y).data; + float *pNormData = (float *)normMask.row(y).data; + for (int x = 0; x < w; x++) + { + float val = pImgRowData[x]; + float radius = pRadiusData[x]; + float mean = interpolate(pSumData, radiusNum, radiusStep, radius); + if (mean == 0) + { + continue; + } + float newVal = (float)val * dstMeanVal / mean; + + // if (newVal - dstMeanVal > -30) + //{ + pRetRowData[x] = newVal; + //pNormData[x] = 255; + // } + //else + //{ + // continue; + //} + + } + } + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat viewRetMat = retMat/255.0; +#endif + /*Mat retNromMask = normMask.mul(retMat); + Mat normTest = retNromMask / 255.0;*/ + + return retMat; + //return normTest; +} + +cv::Mat meanNorm(const Mat& img, const Mat& mask, float dstMeanVal, float majority /*= 1.0*/, PixelSelectionMethod method /*= LP_PIXEL_SELECT_ALL*/) +{ + double meanVal = 0; + if (method == LP_PIXEL_SELECT_ALL) + { + meanVal = mean(img, mask).val[0]; + } + else if (method == LP_PIXEL_SELECT_MAJORITY_FAST || + method == LP_PIXEL_SELECT_MAJORITY) + { + meanStdDev(img, mask, &meanVal, NULL, majority); + } + return img + (int)(dstMeanVal - meanVal); +} + +cv::Mat getRoiImg(const Mat& img, vector ptVec, int radius) +{ + Rect defectRoi(genRectCenRadius(ptVec, radius)); + + defectRoi &= Rect(0, 0, img.cols, img.rows); + return Mat(img, defectRoi); +} + +cv::Mat getRoiImg(const Mat& img, Point2i cen, int radius) +{ + Rect defectRoi(genRectCenRadius(cen, radius)); + + defectRoi &= Rect(0, 0, img.cols, img.rows); + return Mat(img, defectRoi); +} + +cv::Mat filterY(const Mat& img, float* kernel, int size, int ddepth /*= CV_32FC1*/) +{ + Mat kernelMat(size, 1, CV_32FC1, kernel); + + Mat ret; + cv::filter2D(img, ret, ddepth, kernelMat); + + return ret; +} + +float circleDegree(const vector& contour) +{ + Rect br = boundingRect(contour); + float r = sqrt((float)(br.width*br.width / 4 + br.height*br.height / 4)); + float circleArea = CV_PI*r*r; + float area = contourArea(contour); + if (circleArea < 0.00001) + { + return 0; + } + return area / circleArea; +} + +float lpContourArea(const vector& contour) +{ + double area = 0; + if (contour.size() == 1) + { + area = 1; + } + else if (contour.size() != 0) + { + area = contourArea(contour); + area += arcLength(contour, true); + } + return area; +} + +cv::Mat minMaxNorm(const Mat& img, double tarMin, double tarMax, const Mat& mask /*= Mat()*/) +{ + double minVal, maxVal; + minMaxIdx(img, &minVal, &maxVal, 0, 0, mask); + double s = ((tarMax - tarMin) / (maxVal - minVal)); + return img * s + (tarMin - minVal * s); +} + +double longShortRatio(const Size& s) +{ + if (s.width > s.height) + { + if (s.height < 0.000001) + { + return 0; + } + return (double)s.width / (double)s.height; + } + else + { + if (s.width < 0.000001) + { + return 0; + } + return (double)s.height / (double)s.width; + } +} + +void closeShapesToConvex(const Mat& mask, Mat& closedMask) +{ + closedMask.create(mask.rows, mask.cols, mask.type()); + closedMask.setTo(0); + + Mat canvas; + mask.copyTo(canvas); + vector< vector > contours; + findContours(canvas, contours, RETR_LIST, cv::CHAIN_APPROX_NONE); + vector< vector > convextContours; + for (size_t i = 0; i < contours.size(); ++i) + { + const vector& contour = contours[i]; + vector convexContour; + convexHull(contour, convexContour); + convextContours.push_back(convexContour); + } + fillPoly(closedMask, convextContours, Scalar(255, 255, 255)); +} + +cv::Mat genCircleMask(int rows, int cols, int type, + Point cen, double r, const Scalar& color, int thickness, int lineType /*= 8*/, int shift /*= 0*/) +{ + Mat mask = Mat::zeros(rows, cols, type); + circle(mask, cen, r, color, thickness, lineType, shift); + return mask; +} + +void openOper(Mat& img, int w) +{ + Mat kernel(w, w, CV_8UC1); + kernel.setTo(1); + openOper(img, kernel); +} + +void openOper(Mat& img, const Mat& kernel) +{ + erode(img, img, kernel); + dilate(img, img, kernel); +} + +void closeOper(Mat& img, int w) +{ + Mat kernel(w, w, CV_8UC1); + kernel.setTo(1); + closeOper(img, kernel); +} + +void closeOper(Mat& img, const Mat& kernel) +{ + dilate(img, img, kernel); + erode(img, img, kernel); +} + +CV_WRAP GroupMatcher::GroupMatcher( + int normType /*= NORM_L2*/, bool crossCheck /*= false*/) + : BFMatcher(normType, crossCheck) +{ + +} + +Ptr GroupMatcher::clone(bool emptyTrainData /*= false*/) const +{ + return new GroupMatcher(); +} + +// no longer supported in opencv3.4.1 +#if (CV_MAJOR_VERSION < 3) +AlgorithmInfo* GroupMatcher::info() const +{ + return NULL; +} +#endif + +void GroupMatcher::knnMatchImpl(const Mat& queryDescriptors, + vector >& matches, + int k, + const vector& masks /*= vector()*/, + bool compactResult /*= false*/) +{ + BFMatcher::knnMatchImpl(queryDescriptors, matches, k, masks, + compactResult); + + +} + +void GroupMatcher::radiusMatchImpl(const Mat& queryDescriptors, + vector >& matches, + float maxDistance, + const vector& masks /*= vector()*/, + bool compactResult /*= false*/) +{ + BFMatcher::radiusMatchImpl(queryDescriptors, + matches, maxDistance, masks, compactResult); +} + + +Mat getContourBoundedRoiImg(const Mat& src, const vector& contour, Rect* pRoiRect /* = NULL*/) +{ + Rect cr = boundingRect(contour); + Mat cmask(cr.height, cr.width, CV_8UC1); + cmask.setTo(0); + cv::fillPoly(cmask, vector< vector >(1, contour), Scalar(255, 255, 255), 8, 0, cr.tl()*(-1.0)); + Mat localMask; + Mat(src, cr).copyTo(localMask); + if (pRoiRect) + { + *pRoiRect = cr; + } + return localMask & cmask; +} + +void filterContours(const vector< vector >& contours, const Mat& srcImg, + vector< vector >& filteredContours, + int minArea, double minLength, double minLongShortRatio, + double maxCircleDegree, vector* pAreaMaxContour) +{ + int areaMaxIdx = -1; + double maxarea = -1; + for (int j = 0; j < contours.size(); ++j) + { + const vector& contour = contours[j]; + if (contour.size() < 5) + { + continue; + } + + int area = -1; + if (!srcImg.empty()) + { + Mat localMask = getContourBoundedRoiImg(srcImg, contour); + area = countNonZero(localMask); + } + else + { + area = contourArea(contour); + } + + RotatedRect rr = minAreaRect(contour); + + double l = max(rr.size.width, rr.size.height); + if (area < minArea && l < minLength) + { + continue; + } + + double cd = circleDegree(contour); + if (cd > maxCircleDegree) + { + continue; + } + + double lsr = longShortRatio(rr.size); + if (lsr < minLongShortRatio) + { + continue; + } + + if (area > maxarea) + { + maxarea = area; + areaMaxIdx = j; + } + + filteredContours.push_back(contour); + } + + if (areaMaxIdx >= 0 && pAreaMaxContour) + { + *pAreaMaxContour = contours[areaMaxIdx]; + } +} + +void filterContours(Mat hsvColorThresImg, + int minArea, double minLength, double minLongShortRatio, + double maxCircleDegree, vector* pAreaMaxContour /*= NULL*/) +{ + vector< vector > contours; + Mat canvas = hsvColorThresImg.clone(); + findContours(canvas, contours, RETR_LIST, CHAIN_APPROX_NONE); + canvas.setTo(0); + + vector< vector > filteredContours; + filterContours(contours, hsvColorThresImg, filteredContours, + minArea, minLength, minLongShortRatio, maxCircleDegree, pAreaMaxContour); + + // redraw + cv::fillPoly(canvas, filteredContours, Scalar(255, 255, 255)); + hsvColorThresImg &= canvas; +} + + +int filterSmallAndFindMaxContours(vector< vector >& contours, double minArea) +{ + vector< vector > filteredContours; + int maxindex = -1; + double maxarea = -1; + for (int j = 0; j < contours.size(); ++j) + { + const vector& contour = contours[j]; + double area = contourArea(contour); + if (area > minArea) + { + filteredContours.push_back(contour); + } + + if (area > maxarea) + { + maxarea = area; + maxindex = j; + } + } + contours = filteredContours; + return maxindex; +} + + +void filterContours(Mat& img, double minArea) +{ + Mat canvas; + img.copyTo(canvas); + + vector< vector > contours; + findContours(canvas, contours, RETR_LIST, cv::CHAIN_APPROX_NONE); + filterSmallAndFindMaxContours(contours, minArea); + canvas.setTo(0); + cv::fillPoly(canvas, contours, Scalar(255, 255, 255)); + + img = canvas; +} + + +Mat genInRangeMask(const Mat& src, std::map>& colorRanges) +{ + Mat mask = Mat::zeros(src.rows, src.cols, CV_8UC1); + + for (auto iter = colorRanges.begin(); iter != colorRanges.end(); ++iter) + { + int key = iter->first; + Scalar low = iter->second.first; + Scalar high = iter->second.second; + Mat imgThresholded; + inRange(src, low, high, imgThresholded); + mask |= imgThresholded; + } + + return mask; +} + +void localCloseOper(Mat& img, vector< vector >& contours, float longerScale) +{ + Mat tmp = img.clone(); + for (int i = 0; i < contours.size(); ++i) + { + const vector& contour = contours[i]; + RotatedRect rr = minAreaRect(contour); + int longer = cv::max(rr.size.width, rr.size.height)*longerScale; + + Rect br = boundingRect(contour); + br.x -= longer*1.5; + br.y -= longer*1.5; + br.width += longer * 3; + br.height += longer * 3; + + br.x = cv::max(0, br.x); + br.y = cv::max(0, br.y); + br.width = cv::min(img.cols - br.x - 1, br.width); + br.height = cv::min(img.rows - br.y - 1, br.height); + + Mat roiMat = Mat(img, br).clone(); + closeOper(roiMat, Mat::ones(longer, 1, CV_8UC1)); + br.x -= 1; + br.y -= 1; + br.x = cv::max(0, br.x); + br.y = cv::max(0, br.y); + + roiMat.copyTo(Mat(tmp, br)); + } + img |= tmp; +} + +Mat genWithCopiedCols(const Mat& m, int multiples = 3) +{ + Mat ret(m.rows, m.cols * multiples, m.type()); + for (int i = 0; i < multiples; ++i) + { + m.copyTo(ret.colRange(i*m.cols, i*m.cols + m.cols)); + } + return ret; +} + + + +double matDisWithReversedRows(const Mat& m0, const Mat& m1) +{ + assert(m0.rows == m1.rows); + assert(m0.cols == m1.cols); + + int rows = m0.rows; + int cols = m0.cols; + + Mat rowDisVec(rows, 1, CV_64FC1); + double* pRowDisVec = (double*)rowDisVec.data; + for (int i = 0; i < rows; ++i) + { + Mat row0 = 255 - m0.row(i); + Mat row1 = 255 - m1.row(rows - i - 1); + //Mat weightedRow0 = 255 - row0; + //*pRowDisVec = ((row0 - row1).dot(weightedRow0)); + Mat row0f; + row0.convertTo(row0f, CV_32FC1); + Mat row1f; + row1.convertTo(row1f, CV_32FC1); + *pRowDisVec = compareHist(row0f, row1f, CV_COMP_CORREL); + pRowDisVec++; + } + + Mat kernel = cv::getGaussianKernel(rows * 2, 1).rowRange(rows, rows * 2); + + return rowDisVec.dot(kernel); +} + +double matDis(const Mat& m0, const Mat& m1) +{ + assert(m0.rows == m1.rows); + assert(m0.cols == m1.cols); + + int rows = m0.rows; + int cols = m0.cols; + + Mat rowDisVec(rows, 1, CV_64FC1); + double* pRowDisVec = (double*)rowDisVec.data; + for (int i = 0; i < rows; ++i) + { + Mat row0 = 255 - m0.row(i); + Mat row1 = 255 - m1.row(i); + + Mat row0f; + row0.convertTo(row0f, CV_32FC1); + Mat row1f; + row1.convertTo(row1f, CV_32FC1); + *pRowDisVec = compareHist(row0f, row1f, CV_COMP_CORREL); + pRowDisVec++; + } + + Mat kernel = cv::getGaussianKernel(rows * 2, 1).rowRange(rows, rows * 2); + + return rowDisVec.dot(kernel); +} + +int searchBestMatchDx(int searchRadius, int startCol, int endCol, const Mat& curRow, const Mat& preRowMid) +{ + vector valVec; + for (int dx = -searchRadius; dx <= searchRadius; dx++) + { + int curStartCol = startCol + dx; + int curEndCol = endCol + dx; + Mat curRowMid = curRow.colRange(curStartCol, curEndCol); + double val = -matDisWithReversedRows(curRowMid, preRowMid); + valVec.push_back(val); + } + auto minIter = std::min_element(valVec.begin(), valVec.end()); + int bestDx = minIter - valVec.begin() - searchRadius; + + return bestDx; +} + +Mat searchBestMatchSubRowMat(const Mat& img, int startRow, int rowWidth, int searchRadius, const Mat& preRowMid) +{ + Mat curRow = genWithCopiedCols(img.rowRange(startRow, rowWidth + startRow), 3); + int startCol = img.cols; + int endCol = img.cols * 2; + + int bestDx = searchBestMatchDx(searchRadius, startCol, endCol, curRow, preRowMid); + + return curRow.colRange(startCol + bestDx, endCol + bestDx); +} + +void xEnergeyMat(const Mat& img, Mat& energyMat, + int kernelWidth = 12, int backgroundThre = 40, int method = 2) +{ + switch (method) + { + case 0: + { + // sobel edge + Mat sobelMat; + cv::Sobel(img, sobelMat, CV_32FC1, 1, 0); + sobelMat = cv::abs(sobelMat); + energyMat = sobelMat; + } + break; + case 1: + { + // edge with first derivative + Mat kernel = cv::getGaussianKernel(kernelWidth, 4).t(); + Mat halfKernel(kernel.colRange(0, kernel.cols / 2)); + halfKernel *= -1.0; + filter2D(img, energyMat, CV_32FC1, kernel); + energyMat = abs(energyMat); + } + break; + case 2: + { + // edge with second derivative + Mat kernel = cv::getGaussianKernel(kernelWidth, 4).t(); + Mat halfKernel(kernel.colRange(0, kernel.cols / 2)); + halfKernel *= -1.0; + filter2D(img, energyMat, CV_32FC1, kernel); + energyMat = abs(energyMat); + + Mat secondEnergyMat; + filter2D(energyMat, secondEnergyMat, CV_32FC1, kernel); + secondEnergyMat = abs(secondEnergyMat); + + energyMat = secondEnergyMat; + } + break; + case 3: + { + // darker edge with first derivative + Mat kernel = cv::getGaussianKernel(kernelWidth, 4).t(); + Mat gaussMat; + filter2D(img, gaussMat, CV_32FC1, kernel); + gaussMat = 255.0 - gaussMat; + Mat halfKernel(kernel.colRange(0, kernel.cols / 2)); + halfKernel *= -1.0; + filter2D(img, energyMat, CV_32FC1, kernel); + energyMat = abs(energyMat) + gaussMat*0.2; + } + case 4: + { + // darker edge with thresholding + Mat threMat = img < backgroundThre; + converToType(threMat, CV_32FC1); + + Mat kernel = cv::getGaussianKernel(kernelWidth, 4).t(); + + Mat halfKernel(kernel.colRange(0, kernel.cols / 2)); + halfKernel *= -1.0; + filter2D(img, energyMat, CV_32FC1, kernel); + energyMat = abs(energyMat) + threMat; + } + break; + } +} + +void findBoundaryY(const Mat& img, vector& vec, vector& energyVec, + int gradientKernelWidth = 12, + int smoothKernelWidth = 32, + float smoothWeight = 3.0, + int backgroundThre = 40, + int energyType = 4) +{ + // generate energy mat (sobel) + Mat energyMat; + xEnergeyMat(img, energyMat, gradientKernelWidth, backgroundThre, energyType); + + int n = 4; + Mat energyMatBack = energyMat.clone(); + + Mat smoothImg, showMat; + { + Mat kernel = cv::getGaussianKernel(smoothKernelWidth, 4.0).t(); + filter2D(img, smoothImg, CV_32FC1, kernel); + } + + float w = pow(10, smoothWeight); + dynamicProgramYWithSmooth(energyMat, n, w/255.0, smoothImg); + findMaxYPath(energyMat, vec, energyVec, n); + + recoverPathEnergy(energyVec); +} + +void findBoundaryY(const Mat& img0, const Mat& img1, int targetWidth, + vector& vec, vector& energyVec) +{ + // generate energy mat + Mat energyMat0, energyMat1; + xEnergeyMat(img0, energyMat0); + xEnergeyMat(img1, energyMat1); + + // generate smooth mat (gauss) + Mat smoothImg0, smoothImg1, showMat; + { + Mat kernel = cv::getGaussianKernel(31, 4.0).t(); + filter2D(img0, smoothImg0, CV_32FC1, kernel); + filter2D(img1, smoothImg1, CV_32FC1, kernel); + } + + int n = 4; + float sw = 100.0 / 255.0; + dynamicProgramYWithSmooth(energyMat0, energyMat1, + smoothImg0, smoothImg1, targetWidth, n, sw); + + findMaxYPath(energyMat0, vec, energyVec, n); + + recoverPathEnergy(energyVec); +} + +// assuming with two boundaries (left and right) +void autoAlignEachRowGloballyWithIndependentBoundaries(Mat& img) +{ + // divide into two part: left and right + Mat leftImg(img.colRange(0, img.cols / 2)); + Mat rightImg(img.colRange(img.cols / 2, img.cols)); + + // search a boundary for each part + vector leftVec, rightVec; + vector leftEnergyVec, rightEnergyVec; + findBoundaryY(leftImg, leftVec, leftEnergyVec); + findBoundaryY(rightImg, rightVec, rightEnergyVec); + + // draw path + Mat canvas = img.clone(); + drawPointsY(leftVec, canvas, 0, 0, 255); + drawPointsY(rightVec, canvas, canvas.cols / 2, 0, 255); + + // align centers of each row + int rightBaseX = img.cols / 2; + int centerX = (leftVec[0] + rightVec[0] + rightBaseX) / 2; + Mat ret = img.clone(); + ret.setTo(0); + + // move each row to align + for (int i = 0; i < img.rows; ++i) + { + int curCenterX = (leftVec[i] + rightVec[i] + rightBaseX) / 2; + int dx = centerX - curCenterX; + Mat curRow = genWithCopiedCols(img.row(i), 3); + curRow.colRange(img.cols - dx, img.cols * 2 - dx).copyTo(ret.row(i)); + } + + ret.copyTo(img); +} + +void autoAlignEachRowWithWeightedXCen(Mat& img, int tarCenX, vector& dxVec) +{ + dxVec.resize(img.rows, 0); + + for (int i = 0; i < img.rows; ++i) + { + Mat r = img.row(i); + uchar* p = r.data; + uchar* ep = p + r.cols; + int j = 0; + float wx = 0; + float ws = 0; + while (p != ep) + { + float w = (*p) * (*p); + ws += w; + wx += j*w; + j++; + p++; + } + wx /= ws; + int dx = floorf(tarCenX - wx); + dxVec[i] = dx; + } + + alignEachRow(img, dxVec); +} + +void alignEachRow(Mat& img, const vector& dxVec) +{ + Mat ret = img.clone(); + ret.setTo(0); + + for (int i = 0; i < img.rows; ++i) + { + Mat curRow = genWithCopiedCols(img.row(i), 3); + int dx = dxVec[i]; + curRow.colRange(img.cols - dx, img.cols * 2 - dx).copyTo(ret.row(i)); + } + + ret.copyTo(img); +} + +void autoAlignEachRowGloballyWithWidthConstraint(Mat& img, int tarCenX, vector& dxVec) +{ + // divide into two part: left and right + Mat leftImg(img.colRange(0, img.cols / 2)); + Mat rightImg(img.colRange(img.cols / 2, img.cols)); + + // search a boundary for each part + vector leftVec, rightVec; + vector leftEnergyVec, rightEnergyVec; + findBoundaryY(leftImg, leftVec, leftEnergyVec); + findBoundaryY(rightImg, rightVec, rightEnergyVec); + + // draw path + Mat canvas = img.clone(); + drawPointsY(leftVec, canvas, 0, 0, 255); + drawPointsY(rightVec, canvas, canvas.cols / 2, 0, 255); + + float avrLeft = sum(leftVec.begin(), leftVec.end()) / leftVec.size(); + float avrRight = sum(rightVec.begin(), rightVec.end()) / rightVec.size(); + int tarWidth = avrRight + leftImg.cols - avrLeft; + + leftVec.clear(); + leftEnergyVec.clear(); + findBoundaryY(leftImg, rightImg, tarWidth, leftVec, leftEnergyVec); + + rightVec = leftVec; + add(rightVec.begin(), rightVec.end(), tarWidth); + + drawPointsY(leftVec, canvas, 0, 0, 180); + drawPointsY(rightVec, canvas, 0, 0, 180); + + // align centers of each row + int centerX = leftVec[0] + tarWidth / 2; + if (tarCenX >= 0) + { + centerX = tarCenX; + } + Mat ret = img.clone(); + ret.setTo(0); + + // move each row to align + dxVec.resize(img.rows, 0); + for (int i = 0; i < img.rows; ++i) + { + int curCenterX = leftVec[i] + tarWidth / 2; + int dx = centerX - curCenterX; + dxVec[i] = dx; + Mat curRow = genWithCopiedCols(img.row(i), 3); + curRow.colRange(img.cols - dx, img.cols * 2 - dx).copyTo(ret.row(i)); + } + + ret.copyTo(img); +} + +void autoAlignEachRowLocally(Mat& img, int searchRadius /*= 5*/, int rowWidth /*= 1*/, const Mat& preImg /*= Mat()*/) +{ + Mat ret = img.clone(); + ret.setTo(0); + + Mat preRow; + if (!preImg.empty()) + { + preRow = genWithCopiedCols(preImg.rowRange(0, rowWidth), 3); + } + else + { + preRow = genWithCopiedCols(img.rowRange(0, rowWidth), 3); + } + + Mat preRowMid = preRow.colRange(img.cols, img.cols * 2); + if (!preImg.empty()) + { + preRowMid = searchBestMatchSubRowMat(img, 0, rowWidth, searchRadius, preRowMid); + } + preRowMid.copyTo(ret.rowRange(0, rowWidth)); + + for (int i = rowWidth; i <= img.rows - rowWidth; i += rowWidth) + { + preRowMid = searchBestMatchSubRowMat(img, i, rowWidth, searchRadius, preRowMid); + + preRowMid.copyTo(ret.rowRange(i, i + rowWidth)); + } + + ret.copyTo(img); +} + +void autoAlignEachRowWithAutoThreshold(Mat& img, int tarCenX, vector& dxVec) +{ + dxVec.resize(img.rows, 0); + + for (int i = 0; i < img.rows; ++i) + { + Mat row(img.row(i)); + Mat trow; + threshold(row, trow, 0, 255, THRESH_OTSU); + uchar* p = trow.data; + uchar* ep = p + trow.cols; + int l, r; + while (p != ep) + { + if (*p) + { + l = p - trow.data; + break; + } + p++; + } + p = ep - 1; + do + { + if (*p) + { + r = p - trow.data; + break; + } + p--; + } while (p != trow.data); + int curCenX = floorf((l + r) / 2.0); + dxVec[i] = tarCenX - curCenX; + } + + alignEachRow(img, dxVec); +} + +void alignEachRowRightBound(Mat& img, int tarCen, vector& dxVec, + int gradientKernelWidth /*= 12*/, + int smoothBoundaryKernelWidth /*= 32*/, + float smoothBoundaryWeight /*= 3.0*/, + int backgroundThre /*= 40*/) +{ + // right part + Mat rightImg(img.colRange(img.cols / 2, img.cols)); + + // search a boundary + vector rightVec; + vector rightEnergyVec; + findBoundaryY(rightImg, rightVec, rightEnergyVec, gradientKernelWidth, + smoothBoundaryKernelWidth, smoothBoundaryWeight, backgroundThre); + + // draw path + Mat canvas = img.clone(); + drawPointsY(rightVec, canvas, canvas.cols / 2, 0, 255); + + // align centers of each row + int rightBaseX = img.cols / 2; + int centerX = tarCen; + Mat ret = img.clone(); + ret.setTo(0); + + // move each row to align + for (int i = 0; i < img.rows; ++i) + { + int dx = img.cols - rightVec[i] - rightBaseX - 1; + dxVec.push_back(dx); + Mat curRow = genWithCopiedCols(img.row(i), 3); + curRow.colRange(img.cols - dx, img.cols * 2 - dx).copyTo(ret.row(i)); + } + + ret.copyTo(img); +} + +cv::Mat genDirColorImg(const Mat& img, Mat* pValueMat /*= NULL*/) +{ + Mat hsvImg(img.rows, img.cols, CV_8UC3); + vector channels(3); + channels[0] = img; + channels[1] = Mat::zeros(img.rows, img.cols, CV_8UC1); + channels[2] = channels[1]; + channels[1].setTo(255); + if (pValueMat) + { + channels[2] = *pValueMat; + } + else + { + channels[2].setTo(255); + } + merge(channels, hsvImg); + Mat rgbImg; + cvtColor(hsvImg, rgbImg, COLOR_HSV2BGR); + return rgbImg; +} + +void genSobelDir(Mat& img) +{ + Mat srcImg = img.clone(); + Mat sobelX, sobelY; + genSobelImage(srcImg, &sobelX, &sobelY); + img = genSobelDir(sobelX, sobelY); +} + +cv::Mat genSobelDir(const Mat& sobelX, const Mat& sobelY) +{ + Mat img(sobelX.rows, sobelX.cols, CV_8UC1); + for (int i = 0; i < img.rows; ++i) + { + Mat row = img.row(i); + float* pDataX = (float*)sobelX.row(i).data; + float* pDataY = (float*)sobelY.row(i).data; + uchar* pData = row.data; + for (int j = 0; j < img.cols; ++j) + { + float dx = pDataX[j]; + float dy = pDataY[j]; + float angle = 0; + if (dx == 0 && dy == 0) + { + angle = 0; + } + else + { + angle = atan2f(dy, dx); + if (angle < 0) + { + angle += 2.0 * M_PI; + } + } + pData[j] = floorf((angle / M_PI) * 90); + } + } + return img; +} + +void genContrastImg(const Mat& img, Mat& contrastImg, const Mat& kernelMat) +{ + Mat maxMat, minMat; + dilate(img, maxMat, kernelMat); + erode(img, minMat, kernelMat); + + contrastImg = maxMat - minMat; +} + +void genContrastImg(const Mat& img, Mat& contrastImg, int ksize) +{ + Mat kernelMat(ksize, ksize, CV_8UC1); + genContrastImg(img, contrastImg, kernelMat); +} + +void genYContrastImg(const Mat& img, Mat& constrastImg, int ksize) +{ + Mat kernelMat(ksize, 1, CV_8UC1); + genContrastImg(img, constrastImg, kernelMat); +} + +void genHalfContrastImg(const Mat& img, Mat& contrastImg, const Mat& kernelMat) +{ + assert(img.type() == CV_8UC1); + + uchar* pData = img.data; + int wstep = img.step; + int kernelXStep = kernelMat.cols; + int kernelYStep = kernelMat.rows; + + contrastImg.create(img.rows / kernelXStep, img.cols / kernelYStep, CV_8UC1); + uchar* pContrastData = contrastImg.data; + int wContrastStep = contrastImg.step; + + for (int y = 0; y < img.rows; y += kernelYStep) + { + uchar* pRowData = pData; + uchar* pContrastRowData = pContrastData; + for (int x = 0; x < img.cols; x += kernelXStep) + { + Mat roiMat(kernelMat.rows, kernelMat.cols, CV_8UC1, pRowData); + roiMat.step = wstep; + + + + pRowData += kernelXStep; + pContrastRowData += 1; + } + pData += wstep*kernelYStep; + pContrastData += wContrastStep; + } +} + +void genHalfContrastImg(const Mat& img, Mat& contrastImg, int ksize) +{ + +} + +void cvtColor(Mat& img, int t) +{ + Mat tImg; + cvtColor(img, tImg, t); + img = tImg; +} + +cv::Mat rotateImage(const Mat& img, Point2f cen, float degree) +{ + Mat t = getRotationMatrix2D(cen, degree, 1.0); + Mat rImg; + warpAffine(img, rImg, t, img.size(), INTER_CUBIC, BORDER_CONSTANT); + return rImg; +} + +cv::Mat normAngle(const Mat& img, Mat mask) +{ + Point2f cen(img.cols / 2.0, img.rows / 2.0); + double angle = localAngle_(img, cen, mask); + return rotateImage(img, cen, -angle); +} + +float normAngle(float angle) +{ + angle = angle - (int)(angle / 360.0) * 360; + if (angle < 0) + { + angle += 360.0; + } + return angle; +} + +void printMatInfo(const Mat& m, int baseIndentNum /*= 0*/, int indent /*= 4*/) +{ + printIndent(baseIndentNum, indent); + std::cout << "rows: " << m.rows << ", " << + "cols: " << m.cols << ", " << + "channels: " << m.channels() << ", " << + "elementSize: " << m.elemSize() << "; "; +} + +string templateMatchMethod2Str(int method) +{ + switch (method) + { + case cv::TM_SQDIFF: + return "TM_SQDIFF"; + case cv::TM_SQDIFF_NORMED: + return "TM_SQDIFF_NORMED"; + case cv::TM_CCORR: + return "TM_CCORR"; + case cv::TM_CCORR_NORMED: + return "TM_CCORR_NORMED"; + case cv::TM_CCOEFF: + return "TM_CCOEFF"; + case cv::TM_CCOEFF_NORMED: + return "TM_CCOEFF_NORMED"; + default: + return ""; + } +} + +void printIndent(int indentNum, int indent /*= 4*/) +{ + for (int i = 0; i < indentNum; i++) + { + for (int j = 0; j < indent; j++) + { + std::cout << " "; + } + } +} + +Mat genSimpleXGradient(const Mat& m) +{ + Mat kernel(1, 2, CV_32FC1); + float* p = (float*)kernel.data; + p[0] = -1; + p[1] = 1; + Mat dm; + filter2D(m, dm, CV_32FC1, kernel, + Point(-1, -1), 0, BORDER_REFLECT); + return dm; +} + +void uniformLocalMinExtremas(const Mat& vec, Mat& localExtremaValVec, + vector& idxVec, int n, int refineRange) +{ + idxVec.resize(n); + + float step = (float)(vec.cols - 1) / (float)n; + float* pVec = (float*)vec.data; + localExtremaValVec.create(1, n + 1, CV_32FC1); + float* pExtremaValVec = (float*)localExtremaValVec.data; + + float* pVecData = (float*)vec.data; + for (int i = 0; i < n; ++i) + { + int idx = floorf(step*i); + int sIdx = idx - refineRange; + int eIdx = idx + refineRange; + sIdx = max(0, sIdx); + eIdx = min(vec.cols - 1, eIdx) + 1; + float* pExtrema = std::min_element(pVec + sIdx, pVec + eIdx); + idxVec[i] = pExtrema - pVec; + pExtremaValVec[i] = *pExtrema; + } + + pExtremaValVec[n] = pExtremaValVec[0]; +} + +void equalizeHist(const Mat& src, Mat& dst, const Mat& mask /*= Mat()*/) +{ + if (mask.empty()) + { + cv::equalizeHist(src, dst); + return; + } + Mat hist = calcHist(src, mask); + hist *= 255.0 / countNonZero(mask); + + float* pHistData = (float*)hist.data; + Mat mapHist = Mat::zeros(hist.rows, hist.cols, CV_32FC1); + float* pMapHistData = (float*)mapHist.data; + pMapHistData[0] = pHistData[0]; + for (int i = 1; i < 256; ++i) + { + pMapHistData[i] = pMapHistData[i - 1] + pHistData[i]; + } + + dst.create(src.size(), src.type()); + for (int y = 0; y < dst.rows; ++y) + { + uchar* pDstData = dst.row(y).data; + uchar* pSrcData = src.row(y).data; + for (int x = 0; x < dst.cols; ++x) + { + pDstData[x] = pMapHistData[pSrcData[x]]; + } + } +} + +void removeHighlights(const Mat& src, Mat& dst, float thre) +{ + dst = Mat::zeros(src.size(), src.type()); + Mat hightlightMask = src < thre; + src.copyTo(dst, hightlightMask); +} + +void genSectorSumVec(const Mat& img, const Mat& weightMat, Mat& hist, + Mat& weightHist, const Point2f& cen, float angleStep, + Mat* pAngMat /*= NULL*/, Mat* pMagMat /*= NULL*/) +{ + int count = floorf(360.0 / angleStep); + hist = Mat::zeros(1, count, CV_32FC1); + weightHist = Mat::zeros(1, count, CV_32FC1); + float* pData = (float*)img.data; + float* pWeightData = (float*)weightMat.data; + float* pHistData = (float*)hist.data; + float* pWeightHistData = (float*)weightHist.data; + float *x = new float[img.cols]; + for (int i = 0; i < img.cols; ++i) x[i] = i - cen.x; + Mat xRow(1, img.cols, CV_32FC1, x); + Mat magMat(img.rows, img.cols, CV_32FC1); + Mat angMat(img.rows, img.cols, CV_32FC1); + for (int i = 0; i < img.rows; ++i) + { + Mat yRow = Mat::ones(xRow.size(), xRow.type())*(i - cen.y); + + Mat magRow, angRow; + cartToPolar(xRow, yRow, magRow, angRow, true); + magRow.copyTo(magMat.row(i)); + angRow.copyTo(angMat.row(i)); + + float* pAngleData = (float*)angRow.data; + float* pRadiusData = (float*)magRow.data; + for (int j = 0; j < img.cols; ++j) + { + if (i == 100 && j == 90) + { + int a = 0; + } + float val = pData[j]; + float weight = pWeightData[j]; + float radius = pRadiusData[j]; + if (radius == 0) + { + continue; + } + float angle = pAngleData[j]; + + val *= weight; + + float angleRange = 28.6479 / radius; + float langle = angle - angleRange; + float rangle = angle + angleRange; + + int lIntAngle = (int)(langle); + int rIntAngle = (int)rangle; + int li = normAngle(lIntAngle); + int ri = normAngle(rIntAngle); + float totalRange = angleRange*2.0; + //pHistData[li] += val*(langle - ceil(langle)) / totalRange; + pHistData[li] += val*(1 - langle + lIntAngle) / totalRange; + //pWeightHistData[li] += weight*(langle - ceil(langle)) / totalRange; + pWeightHistData[li] += weight*(1 - langle + lIntAngle) / totalRange; + pHistData[ri] += val*(rangle - rIntAngle) / totalRange; + pWeightHistData[ri] += weight*(rangle - rIntAngle) / totalRange; + lIntAngle++; +#if defined(LITTLE_CPP11) + if (_isnan(pHistData[li]) || _isnan(pHistData[ri])) +#else + if (isnan(pHistData[li]) || isnan(pHistData[ri])) +#endif + { + waitKey(); + } + while (lIntAngle < rIntAngle) + { + li = normAngle(lIntAngle); + pHistData[li] += val / totalRange; + pWeightHistData[li] += weight / totalRange; + lIntAngle++; + } + } + pData += img.step1(); + pWeightData += weightMat.step1(); + } + + if (pAngMat) + { + *pAngMat = angMat; + } + if (pMagMat) + { + *pMagMat = magMat; + } + + free(x); +} + +Mat matDivideNonzero(const Mat& m0, const Mat& m1) +{ + // m0/m1 + Mat m1copy = m1.clone(); + m0.copyTo(m1copy, m1copy < 0.0000001); + return m0 / m1copy; +} + + +Mat matDivideNonzero(float m0, const Mat& m1) +{ + // m0/m1 + Mat m1copy = m1.clone(); + m1copy.setTo(m0, m1 < 0.0000001); + return m0 / m1copy; +} + + +void normSectors(Mat& _img, Mat weightMat, const Point2f& cen, + float angleStep, float tarVal) +{ + if (weightMat.empty()) + { + weightMat = Mat::ones(_img.size(), CV_32FC1); + } + Mat img = _img; + if (img.channels() != 1) + { + img = getFirstChannel(img); + } + if (img.type() != CV_32FC1) + { + converToType(img, CV_32FC1); + } + int count = floorf(360.0 / angleStep); + Mat hist = Mat::zeros(1, count, CV_32FC1); + Mat weightHist = Mat::zeros(1, count, CV_32FC1); + Mat magMat(img.rows, img.cols, CV_32FC1); + Mat angMat(img.rows, img.cols, CV_32FC1); + + genSectorSumVec(img, weightMat, hist, weightHist, cen, angleStep, + &angMat, &magMat); + + hist = matDivideNonzero(hist, weightHist); + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vAngleMat = angMat / 360; + Mat vMagMat = magMat / sqrt(img.cols*img.cols / 4.0 + img.rows*img.rows / 4.0); + Mat vHist = hist; + vHist = minMaxNorm(vHist, 0, 1.0); + Mat vWeightHist = weightHist; + vWeightHist = minMaxNorm(vWeightHist, 0, 1.0); +#endif + + Mat scaleHist = matDivideNonzero(tarVal, hist); + + applySectorScales(img, scaleHist, angMat); + + _img = img; +} + +void normSectors_tarImg(Mat& img, Mat weightMat, const Point2f& cen, float angleStep, + const Mat& tarImg) +{ + Mat hist, weightHist, angMat, magMat; + genSectorSumVec(img, weightMat, hist, weightHist, cen, angleStep, + &angMat, &magMat); + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vHist0 = minMaxNorm(hist, 0, 1.0); + Mat vWeightHist = minMaxNorm(weightHist, 0, 1.0); +#endif + + hist = matDivideNonzero(hist, weightHist); + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vHist1 = hist / 255.0; +#endif + + Mat tarHist; + genSectorSumVec(tarImg, weightMat, tarHist, weightHist, cen, angleStep); + tarHist = matDivideNonzero(tarHist, weightHist); + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vTarHist = tarHist/255.0; +#endif + + normSectorsMeans_tarHist(img, hist, cen, angleStep, tarHist, angMat); +} + +void normSectorsMeans_tarHist(Mat& img, const Mat& hist, + const Point2f& cen, float angleStep, const Mat& tarHist, const Mat& angMat) +{ + Mat scaleHist = matDivideNonzero(tarHist, hist); + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vScaleHist = minMaxNorm(scaleHist, 0, 1.0); +#endif + applySectorScales(img, scaleHist, angMat); +} + +void applySectorScales(Mat& img, const Mat& scaleHist, const Mat& angMat) +{ + float* pData = (float*)img.data; + float* pHistData = (float*)scaleHist.data; + for (int i = 0; i < img.rows; ++i) + { + Mat angRow = angMat.row(i); + + float* pAngleData = (float*)angRow.data; + for (int j = 0; j < img.cols; ++j) + { + float angle = pAngleData[j]; + int base = (int)angle; + float s = pHistData[base]; + pData[j] *= s; + } + pData += img.step1(); + } +} + +cv::Point2f imgCen(const Mat& img) +{ + return Point2f(img.cols / 2.0, img.rows / 2.0); +} + +cv::Mat getForeImage(const Mat & src, const Mat &backgroundImg) +{ + Mat resizedBackgroundImg = backgroundImg; + if (backgroundImg.size() != src.size()) { + resize(backgroundImg, resizedBackgroundImg, src.size()); + } + return (src - resizedBackgroundImg); +} +#define ALG_RESIZE_IMAGE_WIDTH 416.0 + +cv::Mat findCircleObject(const Mat &src, const Mat& backgroundImg, + int nThres /*= 20*/, luffy_base::luffyCircle *pCircle /*= NULL*/) +{ + if (src.empty() || backgroundImg.empty() || src.rows < 500) { + return Mat(); + } + + assert(backgroundImg.type() == CV_8UC1); + Mat imgTmp, imgBinary; + const cv::Size cSize = cv::Size(ALG_RESIZE_IMAGE_WIDTH, floorf(ALG_RESIZE_IMAGE_WIDTH / (float)src.cols*(float)src.rows)); + cv::resize(src, imgTmp, cSize); + Mat foregroundImg = getForeImage(imgTmp, backgroundImg); + + using namespace luffy_base; + luffy_threshold::Threshold(foregroundImg, imgBinary, nThres); + + Mat dilatedImgBin; + dilate(imgBinary, dilatedImgBin, Mat::ones(7, 7, CV_32FC1)); + erode(dilatedImgBin, imgBinary, Mat::ones(7, 7, CV_32FC1)); + + openOper(imgBinary, Mat::ones(1, 13, CV_32FC1)); + + vector> conts; + cv::findContours(imgBinary, conts, RETR_EXTERNAL, CHAIN_APPROX_NONE); + imgBinary.setTo(0); + for (int i = 0; i < conts.size(); i++) { + const vector &pt = conts.at(i); + if (pt.size() < 20) { + continue; + } + Rect rt = boundingRect(pt); + if (rt.width < 5 || rt.height < 5) { + continue; + } + drawContours(imgBinary, conts, i, Scalar::all(255), -1); + } + Mat hit; vector pts; + luffy_hit::firstHit4Circle(imgBinary, hit, pts, Point(cSize.width / 2, cSize.height / 2), 0, cSize.width / 2, 360, luffy_hit::emHitOut2In); + + //luffy_imageProc::RansacParam rs(0.02, 2.5, 70, 100, 220); + luffy_imageProc::RansacParam rs(0.001, 1.5, 2240, 100, 420); + vector pts2 = luffy_imageProc::fitModelbyRansac(pts, luffy_imageProc::emModelCircle, &rs); + +#ifdef _DEBUG + Mat imgColor; + cv::cvtColor(imgTmp, imgColor, CV_GRAY2BGR); + for (int i = 0; i < pts.size(); i++) { + imgColor.at(pts.at(i))[0] = 255;//B + imgColor.at< cv::Vec3b >(pts.at(i))[1] = 0;//G + imgColor.at< cv::Vec3b >(pts.at(i))[2] = 0;//R + } + for (int i = 0; i < pts2.size(); i++) { + imgColor.at(pts2.at(i))[0] = 0;//B + imgColor.at< cv::Vec3b >(pts2.at(i))[1] = 0;//G + imgColor.at< cv::Vec3b >(pts2.at(i))[2] = 255;//R + } +#endif + + float fRadius; + Point2f ptCenter; + bool bFind = luffy_imageProc::lsCircleFit(pts2, fRadius, ptCenter); + + if (!bFind) { + return Mat(); + } + Mat dst; + const int nOffset = 1; + fRadius += nOffset; + Rect rt(ptCenter.x - fRadius + nOffset, ptCenter.y - fRadius + nOffset, 2 * fRadius, 2 * fRadius); + rt &= Rect(0, 0, imgTmp.cols, imgTmp.rows); + Mat test; + imgTmp(rt).copyTo(test); + static int nCount = cv::getTickCount(); + //QString str = "d://image//" + QString::number(cv::getTickCount()); + //QString strTest = str + "_1.jpg"; + //cv::imwrite(strTest.toLatin1().data(), test); + // + if (pCircle) { + float fScale = src.cols / ALG_RESIZE_IMAGE_WIDTH; + Mat matBig = src - backgroundImg; + pCircle->fRadius = fRadius * fScale; + pCircle->ptCenter = Point(ptCenter.x * fScale, ptCenter.y * fScale); + Mat matBinary; + luffy_threshold::Threshold(matBig, matBinary, nThres); + Mat hit; vector pts; + luffy_hit::firstHit4Circle(matBinary, hit, pts, pCircle->ptCenter, 0, pCircle->fRadius + 10, 360, luffy_hit::emHitOut2In); + luffy_imageProc::RansacParam rs(0.01, 2.5, 100, 100, 220); + vector pts2 = luffy_imageProc::fitModelbyRansac(pts, luffy_imageProc::emModelCircle, &rs); + float fRadius2; + Point2f ptCenter2; + bool bFind = luffy_imageProc::lsCircleFit(pts2, fRadius2, ptCenter2); + if (bFind) { + pCircle->fRadius = fRadius2; + pCircle->ptCenter = ptCenter2; + Rect rt(ptCenter2.x - fRadius2 + nOffset, ptCenter2.y - fRadius2 + nOffset, 2 * fRadius2, 2 * fRadius2); + src(rt).copyTo(dst); + int nWidth = ((int)((double)dst.cols / fScale / 4)) * 4; + cv::resize(dst, dst, cv::Size(nWidth, nWidth)); + //QString strTest = str + "_2.jpg"; + //cv::imwrite(strTest.toLatin1().data(), dst); + } + } + if (dst.empty()) { + imgTmp(rt).copyTo(dst); + } + + return dst; +} + +bool ensureGrayImg(const Mat& img, Mat& gray) +{ + if (img.channels() == 3) + { + cvtColor(img, gray, COLOR_BGR2GRAY); + return true; + } + else if (img.channels() == 4) + { + cvtColor(img, gray, COLOR_BGRA2GRAY); + return true; + } + else if (img.channels() == 1) + { + gray = img; + return false; + } + else + { + assert(false && "unsupported image conversion."); + return false; + } +} + +double hsvDis(const Vec3b& v0, const Vec3b& v1) +{ + float h0 = v0[0] * 2.0 * 0.01745; + float s0 = v0[1] * (v0[2] / 255.0) * 2.0; + float h1 = v1[0] * 2.0 * 0.01745; + float s1 = v1[1] * (v1[2] / 255.0) * 2.0; + + double dVal = norm(Vec3f(cos(h0)*s0, sin(h0)*s0, v0[2]), + Vec3f(cos(h1)*s1, sin(h1)*s1, v1[2])); + + return dVal; +} + +cv::Mat genHSVColorDisMat(const Mat& m0, const Mat& m1) +{ + Scalar mean, stddev; + meanStdDev(m1, mean, stddev); + Vec3b baseColor(mean.val[0], mean.val[1], mean.val[2]); + return genHSVColorDisMat(m0, baseColor); +} + +cv::Mat genHSVColorDisMat(const Mat& m, Vec3b baseColor) +{ + Mat ret = Mat::zeros(m.size(), CV_32FC1); + uchar* pRet = ret.data; + uchar* pData0 = m.data; + for (int i = 0; i < m.rows; ++i) + { + float* pRet32f = (float*)pRet; + Vec3b* pData32f0 = (Vec3b*)pData0; + for (int j = 0; j < m.cols; ++j) + { + Vec3b& v0 = pData32f0[j]; + pRet32f[j] = (float)hsvDis(v0, baseColor); + } + + pRet += ret.step; + pData0 += m.step; + } + return ret; +} + +cv::Mat genHSVColorDisMat(const Mat& m) +{ + Scalar mean, stddev; + meanStdDev(m, mean, stddev); + return genHSVColorDisMat(m, Vec3b(mean.val[0], mean.val[1], mean.val[2])); +} + +cv::Mat genBlueGreenRatioMat(const Mat& m, float alpha /*= 50.0*/, float beta /*= -17.0*/) +{ + vector channels; + split(m, channels); + Mat blue32f, green32f; + channels[0].convertTo(blue32f, CV_32FC1); + channels[1].convertTo(green32f, CV_32FC1, 1.0, -beta); + Mat ratioMat = green32f / blue32f; + Mat ratioMat8u; + ratioMat.convertTo(ratioMat8u, CV_8UC1, alpha); + return ratioMat8u; +} + +Mat genColorFuncDisMat(const Mat& m, float a, float b, float c) +{ + vector channels; + split(m, channels); + Mat blue32f, green32f; + channels[0].convertTo(blue32f, CV_32FC1); + channels[1].convertTo(green32f, CV_32FC1); + Mat sqrBlue = blue32f.mul(blue32f, a); + Mat bBlue = blue32f*b; + //Mat dis = ((sqrBlue + bBlue) - green32f); + Mat dis = (green32f - sqrBlue + c) / blue32f; + Mat ret; + dis.convertTo(ret, CV_8UC1, 255); + return ret; +} + +void setChannel(Mat& img, int i, Mat chnMat) +{ + vector channels; + split(img, channels); + channels[i] = chnMat; + merge(channels, img); +} + +}; diff --git a/molunCar/CVUtils.h b/molunCar/CVUtils.h new file mode 100644 index 0000000..0e5777b --- /dev/null +++ b/molunCar/CVUtils.h @@ -0,0 +1,610 @@ +/*! \file CVUtils.h + \brief Some functions extend opencv classes + + Created: 2015/06/22, author: Jin Bingwen. +*/ + +#ifndef __CVUtils_h_ +#define __CVUtils_h_ + +#include "StdUtils.h" +#include +#include + +#include +#include +#include +#include +#include "pointpair.h" + +#if defined(_DEBUG) && defined(_VIEW_INTERNAL_MAT) +#define DEBUG_VIEW_INTERNAL_MAT +#endif + +#if defined(_DEBUG) && defined(_DETAIL_LOG) +#define DEBUG_DETAIL_LOG +#endif + +#define M_PI CV_PI + +#define INTER_POINT_Y(p0x, p0y, p1x, p1y, p2x) ((p1y - p0y)/(p1x - p0x)*(p2x - p0x) + p0y) +#define INTER_POINT_X(p0x, p0y, p1x, p1y, p2y) ((p1x - p0x)/(p1y - p0y)*(p2y - p0y) + p0x) + +using std::vector; +using std::pair; +using namespace cv; + +typedef Size_ Sized; +typedef Size_ Sizef; + +namespace CyclopsUtils { + +// Hold an empty dummy mat which is useful when you want to return a Mat() incase of some failure, +// or pass a Mat() as defualt function parameter. +// This will save some computation cost for initialization and deallocation of a local variable. +extern Mat gDummyMat; + +// Hold an empty dummy Scalar which is useful when you want to return a Scalar() incase of some failure, +// or pass a Scalar() as defualt function parameter. +// This will save some computation cost for initialization and deallocation of a local variable. +extern Scalar gDummyScalar; + +string templateMatchMethod2Str(int method); + +// Carmack fast InvSqrt! +inline float InvSqrt(float x) +{ + float xhalf = 0.5f*x; + int i = *(int*)&x; + i = 0x5f3759df - (i >> 1); // ¼ÆËãµÚÒ»¸ö½üËÆ¸ù + x = *(float*)&i; + x = x*(1.5f - xhalf*x*x); // Å£¶Ùµü´ú·¨ + return x; +} + +void printIndent(int indentNum, int indent = 4); + +template +void printProperty(int indentNum, string label, _Ty val) +{ + printIndent(indentNum); + std::cout << label << ": " << val << "; " << std::endl; +} + +void printMatInfo(const Mat& m, int baseIndentNum = 0, int indent = 4); + +void alignEachRowRightBound(Mat& img, int tarCen, vector& dxVec, + int gradientKernelWidth = 12, + int smoothBoundaryKernelWidth = 32, + float smoothBoundaryWeight = 3.0, + int backgroundThre = 40); +void autoAlignEachRowGloballyWithIndependentBoundaries(Mat& img); +void autoAlignEachRowWithWeightedXCen(Mat& img, int tarCenX, vector& dxVec); +void autoAlignEachRowWithAutoThreshold(Mat& img, int tarCenX, vector& dxVec); +void alignEachRow(Mat& img, const vector& dxVec); +void autoAlignEachRowGloballyWithWidthConstraint(Mat& img, int tarCenX, vector& dxVec); +void autoAlignEachRowLocally(Mat& img, int searchRadius = 5, int rowWidth = 1, const Mat& preImg = Mat()); + +void openOper(Mat& img, int w); +void openOper(Mat& img, const Mat& kernel); +void closeOper(Mat& img, int w); +void closeOper(Mat& img, const Mat& kernel); +void localCloseOper(Mat& img, vector< vector >& contours, float longerScale); + +Mat genCircleMask(int rows, int cols, int type, + Point cen, double r, + const Scalar& color, int thickness, int lineType = 8, int shift = 0); + +void closeShapesToConvex(const Mat& mask, Mat& closedMask); + +double longShortRatio(const Size& s); + +float lpContourArea(const vector& contour); + + +float circleDegree(const vector& contour); + + +enum LogicOper +{ + LP_LOGIC_LARGER, + LP_LOGIC_SMALLER +}; + +Mat gridThre(const Mat& img, int gridWidth, int gridHeight, float threStdDevScale, LogicOper oper /*= LP_LOGIC_SMALLER*/, float majority /*= 0.8*/); +Mat gridWhiteThre(const Mat& img, int gridWidth, int gridHeight, float threStdDevScale, float majority /*= 0.8*/); +Mat gridBlackThre(const Mat& img, int gridWidth, int gridHeight, float threStdDevScale, float majority = 0.8); + +void converToType(cv::Mat& mat, int mattype); + +void genScharrImage(Mat& img); +void genSobelImage(Mat& img, Mat* pSobelx = NULL, Mat* pSobely = NULL); +void genSobelDir(Mat& img); +Mat genSobelDir(const Mat& sobelX, const Mat& sobelY); +Mat genDirColorImg(const Mat& img, Mat* pValueMat = NULL); + +template +void histMeanStddev(const Mat& hist, double* pMean, double* pStddev) +{ + _T* pData = (_T*)hist.data; + double sum = 0; + int count = 0; + for (int i = 0; i < hist.cols; ++i) + { + if (!pData[i]) + { + continue; + } + count += pData[i]; + sum += pData[i]*i; + } + if (count == 1) + { + if (pMean) + { + *pMean = sum; + } + if (pStddev) + { + *pStddev = 0; + } + return; + } + *pMean = sum / (double)count; + sum = 0; + for (int i = 0; i < hist.cols; ++i) + { + if (!pData[i]) + { + continue; + } + double d = i - *pMean; + sum += d*d*pData[i]; + } + *pStddev = sqrt(sum / (double)(count - 1)); +} + +bool localMeanVarNorm(const Mat& srcMat, Mat& dstMat, int winRadius, double tarMean = 120, double tarStd = 30); + +Mat genXDeriLineKernel(int w, int type, double absVal); + +Mat genXDeriMat(const Mat& img, int w, double absVal); + +/************************************************************************/ +/* Normalize */ +/************************************************************************/ +Mat normEachRow(const Mat& img); + +enum PixelSelectionMethod +{ + LP_PIXEL_SELECT_ALL, + LP_PIXEL_SELECT_MAJORITY, + LP_PIXEL_SELECT_MAJORITY_FAST +}; + +Mat cocentricNorm(const Mat& img, Point2f center, const Mat& mask, float dstMeanVal); + +Mat meanNorm(const Mat& img, const Mat& mask, float dstMeanVal, + float majority = 1.0, + PixelSelectionMethod method = LP_PIXEL_SELECT_ALL); + +Mat minMaxNorm(const Mat& img, double tarMin, double tarMax, const Mat& mask = Mat()); + +Mat normCanvas(const Mat& img); +/**********************************************************************/ + +Mat genGradientDir4EachRow(const Mat& img); + +void findMatElementsEquals(const Mat& img, vector& pointVec, float val, int xPadding); + +double localMatSum(const Mat& mat, const Rect& roi); + +void findEdgePointsEachRow(const Mat& img, vector& edgePointVec, int xPadding); + +template +Mat sumEachRow(const Mat& iMat) +{ + _ASSERTE(iMat.channels() == 1); + Mat oVec(iMat.rows, 1, DataType<_Ty>::type); + for (int i = 0; i < iMat.rows; ++i) + { + Mat rowVec = iMat.row(i); + Scalar s = cv::sum(rowVec); + oVec.at<_Ty>(i) = s.val[0]/rowVec.cols; + } + return oVec; +} + +template +Mat sumEachRowN(const Mat& iMat) +{ + _ASSERTE(iMat.channels() == cn); + Mat oVec(iMat.rows, 1, CV_MAKETYPE(DataType<_Ty>::type, cn)); + for (int i = 0; i < iMat.rows; ++i) + { + Mat rowVec = iMat.row(i); + Scalar s = cv::sum(rowVec); + Vec<_Ty, cn>& d = oVec.at >(i); + for (int j = 0; j < cn; ++j) + d[j] = s[j] / iMat.cols; + } + return oVec; +} + +Mat sumEachRow2(const Mat& img); + +template +Mat sumEachCol(const Mat& iMat) +{ + _ASSERTE(iMat.channels() == 1); + Mat oVec(1, iMat.cols, DataType<_Ty>::type); + for (int i = 0; i < iMat.cols; ++i) + { + Mat colVec = iMat.col(i); + Scalar s = cv::sum(colVec); + oVec.at<_Ty>(i) = s.val[0]/colVec.rows; + } + return oVec; +} + +template +Mat sumEachColN(const Mat& iMat) +{ + _ASSERTE(iMat.channels() == cn); + Mat oVec(1, iMat.cols, CV_MAKETYPE(DataType<_Ty>::type, cn)); + for (int i = 0; i < iMat.cols; ++i) + { + Mat colVec = iMat.col(i); + Scalar s = cv::sum(colVec); + Vec<_Ty, cn>& d = oVec.at >(i); + for (int j = 0; j < cn; ++j) + d[j] = s[j] / colVec.rows; + } + return oVec; +} + +Mat sumEachCol2(const Mat& img); + +/************************************************************************/ +/* Threshold */ +/************************************************************************/ +Mat thresholdEachRowLocally(const Mat& img, int localRange = 501, int C = 10); + +Mat thresholdEachRow(const Mat& img, float threScale = 0.5); + +Mat thresholdEachRow(const Mat& img, const Mat& rowThre, float threScale); + +// As OpenCV doesn't provide any API to get automatic threshold value only without actually apply the threshold, +// however in some cases, we need this value only to apply our own threshold type. +// So, copied out the private getThreshVal_Otsu_8u and getThreshVal_Triangle_8u function here, +// and we provide the uniformed getAutoThresValue function to call the above two. +// Pass in THRESH_OTSU for Otsu threshold, and THRESH_TRIANGLE for the other. +double getAutoThresValue(const Mat& img, int type = THRESH_OTSU); +/**********************************************************************/ + +void segEachRow(const Mat& img, vector& pointPairVec, int xPadding); + +void fillPointPairVal(const Mat& img, std::string filePath, vector& pointPairVec); + +Mat getFirstChannel(const Mat& img); +Mat getChannel(const Mat& img, int i); +void setChannel(Mat& img, int i, Mat chnMat); + +void mulEachRow(Mat& img, const Mat& scaleRow); + +void genRandomPoints(const Mat& img, vector& points, RNG rng, int sampleCnt = 1000); + +void plot8uVec(const Mat& vec, float scale = 1.0); + +void plot32fVec(const Mat& vec, float scale = 1.0); + +Mat calcHist(const Mat& img, const Mat& mask, int histSize = 256, int minVal = 0, int maxVal = 256); + +template +Mat calcHist(const vector& vals, unsigned int histSize, T* pMinVal = nullptr, T* pMaxVal = nullptr) +{ + _ASSERTE(histSize > 1); + if (histSize <= 1) return gDummyMat; + + int minValIdx = -1; + int maxValIdx = -1; + int valSize = vals.size(); + if (!pMinVal || !pMaxVal) { + for (int i = 0; i < valSize; ++i) + { + const T& v = vals[i]; + if (minValIdx < 0 || v < vals[minValIdx]) { + minValIdx = i; + } + if (maxValIdx < 0 || v > vals[maxValIdx]) { + maxValIdx = i; + } + } + } + T minVal = pMinVal ? *pMinVal : vals[minValIdx]; + T maxVal = pMaxVal ? *pMaxVal : vals[maxValIdx]; + if (minVal == maxVal) return gDummyMat; + + double histStep = (maxVal - minVal) / (histSize-1); // ensure max value is in analysis. + + Mat hist(2, histSize, CV_32S, Scalar(0)); + int* axis = (int*)hist.ptr(1); + for (int i = 0; i < histSize; ++i) + axis[i] = histStep * i + minVal; + + int* data = (int*)hist.ptr(0); + + for (int i = 0; i < valSize; ++i) + { + const T& v = vals[i]; + int histIdx = (v - minVal) / histStep; + data[histIdx]++; + } + + return hist; +} + +Mat resize(const Mat& img, float s, int interMethod = cv::INTER_LINEAR); + +void writeFile(const Mat& mat, std::string filePath, std::string matName = "mat"); + +Mat readFile(std::string filePath, std::string matName); + +void gaussianBlurEachRow(const Mat& src, Mat& dst, int ksize = 3); + +void medianBlurEachRow(const Mat& src, Mat& dst, int ksize = 3); + +double maxLaplacianX(const Mat& rowMat, float scale = 1.0); + + +double minLaplacianX(const Mat& rowMat, float scale = 1.0); + +void Laplacian1D(const Mat& src, Mat& dst, int ddepth, int ksize = 1); + +void filterKeyPointsByNeighborDistance(vector& vec, float dis); + +void filterKeyPointsByRotationInvariants(vector& vec, const Mat& img, + FeatureDetector* pDesExtractor, float tor); + +double localIC_Angle(const Mat& img, Point2f center, int patchSize); +double localAngle_WeightedCen(const Mat& img, Point2f center); +double localAngle_(const Mat& img, Point2f center, Mat mask); + +class GroupMatcher : public BFMatcher +{ +public: + CV_WRAP GroupMatcher(int normType = NORM_L2, bool crossCheck = false); + virtual ~GroupMatcher() {} + + virtual bool isMaskSupported() const { return false; } + + virtual Ptr clone(bool emptyTrainData = false) const; + +// no longer supported in opencv3.4.1 +#if (CV_MAJOR_VERSION < 3) + AlgorithmInfo* info() const; +#endif +protected: + virtual void knnMatchImpl(const Mat& queryDescriptors, vector >& matches, int k, + const vector& masks = vector(), bool compactResult = false); + virtual void radiusMatchImpl(const Mat& queryDescriptors, vector >& matches, float maxDistance, + const vector& masks = vector(), bool compactResult = false); + + int normType; + bool crossCheck; +}; + +void upperMajorityMask(const Mat& img, Mat& mask, float majority); +void majorityHistLower(const Mat& img, const Mat& mask, Mat& hist, float majority); +void majorityHistUpper(const Mat& img, const Mat& mask, Mat& hist, float majority); +Mat lowerMajorityMask(const Mat& img, const Mat& mask, float majority); +void meanStdDev(const Mat& img, const Mat& mask, double* pMean, double* pStdDev, float majority, int type = 0); + + +Mat getRoiImg(const Mat& img, Point2i cen, int radius); + + +Mat getRoiImg(const Mat& img, vector ptVec, int radius); + + +Mat filterY(const Mat& img, float* kernel, int size, int ddepth = CV_32FC1); + +Mat getContourBoundedRoiImg(const Mat& src, const vector& contour, Rect* pRoiRect = NULL); + +void filterContours(Mat hsvColorThresImg, + int minArea, double minLength, double minLongShortRatio, + double maxCircleDegree, vector* pAreaMaxContour = NULL); +void filterContours(Mat& img, double minArea); +void filterContours(const vector< vector >& contours, const Mat& srcImg, + vector< vector >& filteredContours, int minArea, double minLength, + double minLongShortRatio, double maxCircleDegree, vector* pAreaMaxContour = NULL); +int filterSmallAndFindMaxContours(vector< vector >& contours, double minArea); +Mat genInRangeMask(const Mat& src, std::map>& colorRanges); + +void genContrastImg(const Mat& img, Mat& contrastImg, const Mat& kernelMat); +void genContrastImg(const Mat& img, Mat& contrastImg, int ksize); +void genYContrastImg(const Mat& img, Mat& constrastImg, int ksize); + +void genHalfContrastImg(const Mat& img, Mat& contrastImg, const Mat& kernelMat); +void genHalfContrastImg(const Mat& img, Mat& contrastImg, int ksize); + +void cvtColor(Mat& img, int t); + +Mat rotateImage(const Mat& img, Point2f cen, float degree); +Mat normAngle(const Mat& img, Mat mask); + +Mat genSimpleXGradient(const Mat& m); + +void uniformLocalMinExtremas(const Mat& vec, Mat& localExtremaValVec, + vector& idxVec, int n, int refineRange); + +void equalizeHist(const Mat& src, Mat& dst, const Mat& mask = gDummyMat); + +void removeHighlights(const Mat& src, Mat& dst, float thre); + +void genSectorSumVec(const Mat& img, const Mat& weightMat, Mat& hist, + Mat& weightHist, const Point2f& cen, float angleStep, + Mat* pAngMat = NULL, Mat* pMagMat = NULL); +void applySectorScales(Mat& img, const Mat& hist, const Mat& angMat); +void normSectors(Mat& img, Mat weightMat, const Point2f& cen, float angleStep, + float tarVal); +void normSectors_tarImg(Mat& img, Mat weightMat, const Point2f& cen, float angleStep, + const Mat& tarImg); +void normSectorsMeans_tarHist(Mat& img, const Mat& hist, const Point2f& cen, + float angleStep, const Mat& tarHist, const Mat& angMat); + +Point2f imgCen(const Mat& img); + +class luffyCircle; +cv::Mat findCircleObject(const Mat &src, const Mat& backgroundImg, + int nThres = 20, luffyCircle *pCircle = NULL); + +bool ensureGrayImg(const Mat& img, Mat& gray); + +/************************************************************************/ +/* Color Space */ +/************************************************************************/ + +double hsvDis(const Vec3b& v0, const Vec3b& v1); +Mat genHSVColorDisMat(const Mat& m0, const Mat& m1); +Mat genHSVColorDisMat(const Mat& m, Vec3b baseColor); +Mat genHSVColorDisMat(const Mat& m); +Mat genBlueGreenRatioMat(const Mat& m, float alpha = 166.32, float beta = -16.414); +Mat genColorFuncDisMat(const Mat& m, float a = 0.001, float b = 0.4061, float c = -5.6845); + +template +float validate_diff(T result, T target, float err_tol) +{ + T diff = result - target; + float normDiff = cv::norm(diff); + return normDiff <= err_tol ? 0 : normDiff; +} + +template +T getImgSubPix(const Mat& img, float x, float y) +{ + _ASSERTE(!img.empty()); + _ASSERTE(x >= 0 && y >= 0 && x <= img.cols - 1 && y <= img.rows - 1); + + int x0 = floor(x); + int y0 = floor(y); + bool noSubX = x0 == x; + bool noSubY = y0 == y; + if (noSubX && noSubY) { + return img.at(y0, x0); + } + else if (noSubX) { + int y1 = y0 + 1; + double c = y1 - y; + double d = 1. - c; + + const T* p0 = (const T*)img.ptr(y0); + const T* p1 = (const T*)img.ptr(y1); + + T ret = p0[x0] * c + p1[x0] * d; + return ret; + } + else if (noSubY) { + int x1 = x0 + 1; + double a = x1 - x; + double b = 1. - a; + + const T* p0 = (const T*)img.ptr(y0); + + T ret = a * p0[x0] + b * p0[x1]; + return ret; + } + else { + int x1 = x0 + 1; + int y1 = y0 + 1; + + double a = x1 - x; + double b = 1. - a; + double c = y1 - y; + double d = 1. - c; + + const T* p0 = (const T*)img.ptr(y0); + const T* p1 = (const T*)img.ptr(y1); + + T ret = (a * p0[x0] + b * p0[x1]) * c + + (a * p1[x0] + b * p1[x1]) * d; + + return ret; + } +} + +template +vector cvtMat2Vec(const Mat& img) +{ + _ASSERTE(!img.empty()); + int count = img.cols * img.rows; + + std::vector vals(count); + if (img.isContinuous()) { + memcpy((T*)vals.data(), (T*)img.data, sizeof(T)*count); + } + else { + int rows = img.rows; + T* vals_p = vals.data(); + size_t rowStep = sizeof(T)*img.cols; + for (int i = 0; i < rows; ++i) { + memcpy(vals_p, img.ptr(i), rowStep); + vals_p += img.cols; + } + } + + return vals; +} + +template +std::list cvtMat2List(const Mat& img) +{ + _ASSERTE(!img.empty()); + int rows = img.rows; + int cols = img.cols; + int count = cols * rows; + + std::list vals; + for (int i = 0; i < rows; ++i) { + const S* img_p = img.ptr(i); + for (int j = 0; j < cols; ++j) { + vals.push_back((S)(img_p[j])); + } + } + + return vals; +} + +template +T getNthElement(const Mat& img, int n) +{ + _ASSERTE(!img.empty()); + int count = img.cols * img.rows; + _ASSERTE(n < count); + + std::vector vals = cvtMat2Vec(img); + + std::nth_element(vals.begin(), vals.begin() + n, vals.end()); + + return vals[n]; +} + +int getLocalMaximun(const Mat& vec, double thres, std::vector* pMaxIdxVec); +int getLocalMinimun(const Mat& vec, double thres, std::vector* pMaxIdxVec); + +enum EnumAbnormalType { + AbTypePositive = 0, + AbTypeNegative, + AbTypeBoth +}; +int detectAbnormal(const Mat& vec, int kernelSize, EnumAbnormalType abnormalType, double abnormalThreshold, + Mat* pScores = nullptr, std::vector* pAbIdxVec = nullptr); + +}; + +using namespace CyclopsUtils; + +#include "GeomUtils.h" + +#endif // __CVUtils_h_ + diff --git a/molunCar/DynamicProgramSearch.cpp b/molunCar/DynamicProgramSearch.cpp new file mode 100644 index 0000000..8321526 --- /dev/null +++ b/molunCar/DynamicProgramSearch.cpp @@ -0,0 +1,243 @@ +#include "DynamicProgramSearch.h" +#include + +using std::list; + +void dynamicProgramY(Mat& disMat, int n /*= 2*/) +{ + for (int y = 1; y < disMat.rows; ++y) + { + for (int x = 0; x < disMat.cols; ++x) + { + // find max neighbor + int sy = y - 1; + float maxVal = -1; + int sj = x - n >= 0 ? x - n : 0; + int ej = x + n <= disMat.cols - 1 ? x + n : disMat.cols - 1; + for (int j = sj; j <= ej; j++) + { + float val = disMat.at(sy, j); + if (maxVal < val) + { + maxVal = val; + } + } + + disMat.at(y, x) += maxVal; + } + } +} + +void dynamicProgramX(Mat& disMat, int n /*= 2*/) +{ + for (int x = 1; x < disMat.cols; ++x) + { + int sx = x - 1; + for (int y = 0; y < disMat.rows; ++y) + { + // find max neighbor + float maxVal = -1; + int si = max(y - n, 0); + int ei = min(y + n, disMat.rows - 1); + for (int i = si; i <= ei; i++) + { + float val = disMat.at(i, sx); + if (maxVal < val) + { + maxVal = val; + } + } + + disMat.at(y, x) += maxVal; + } + } +} + +void findMaxXPath(const Mat& disMat, vector& vec, vector& energyVec, + int n /*= 2*/) +{ + int sy = 0; + int ey = disMat.rows - 1; + vec.resize(disMat.cols, -1); + energyVec.resize(disMat.cols, -1); + for (int x = disMat.cols - 1; x >= 0; --x) + { + float maxVal = -1; + int maxIdx = -1; + for (int y = sy; y <= ey; ++y) + { + float val = disMat.at(y, x); + if (maxVal < val) + { + maxVal = val; + maxIdx = y; + } + } + vec[x] = maxIdx; + energyVec[x] = maxVal; + sy = maxIdx - n; + ey = maxIdx + n; + sy = (sy >= 0 ? sy : 0); + ey = (ey < disMat.rows ? ey : disMat.rows - 1); + } +} + +void findMaxYPath(const Mat& disMat, vector& vec, vector& energyVec, int n /*= 2*/) +{ + int sx = 0; + int ex = disMat.cols - 1; + vec.resize(disMat.rows, -1); + energyVec.resize(disMat.rows, -1); + for (int y = disMat.rows - 1; y >= 0; --y) + { + float maxVal = -1; + int maxIdx = -1; + for (int x = sx; x <= ex; ++x) + { + float val = disMat.at(y, x); + if (maxVal < val) + { + maxVal = val; + maxIdx = x; + } + } + vec[y] = maxIdx; + energyVec[y] = maxVal; + sx = maxIdx - n; + ex = maxIdx + n; + sx = (sx >= 0 ? sx : 0); + ex = (ex < disMat.cols ? ex : disMat.cols - 1); + } +} + +void recoverPathEnergy(vector& vec) +{ + assert(!vec.empty()); + + auto i = vec.rbegin(); + auto j = i; + ++j; + while (j != vec.rend()) + { + *i = *i - *j; + ++i; + ++j; + } +} + +void dynamicProgramYWithSmooth(Mat& disMat, + int n /*= 2*/, float sw /*= 1.0*/, + const Mat& smoothMat /*= Mat()*/) +{ + for (int y = 1; y < disMat.rows; ++y) + { + for (int x = 0; x < disMat.cols; ++x) + { + // find max neighbor + int sy = y - 1; + float maxVal = -1; + int sj = x - n >= 0 ? x - n : 0; + int ej = x + n <= disMat.cols - 1 ? x + n : disMat.cols - 1; + float baseSVal = 0; + if (!smoothMat.empty()) + { + baseSVal = smoothMat.at(y, x); + } + for (int j = sj; j <= ej; j++) + { + float disVal = disMat.at(sy, j); + float sVal = 0; + if (!smoothMat.empty()) + { + sVal = smoothMat.at(sy, j); + } + float energyVal = disVal + (255 - abs(sVal - baseSVal))*sw; + if (maxVal < energyVal) + { + maxVal = energyVal; + } + } + + disMat.at(y, x) += maxVal; + } + } +} + +void dynamicProgramYWithSmooth(Mat& disMat0, Mat& disMat1, + const Mat& smoothMat0, + const Mat& smoothMat1, + int targetWidth, + int n /* = 2 */, + float sw /* = 1.0 */) +{ + assert(targetWidth <= disMat0.cols + disMat1.cols); + + for (int y = 1; y < disMat0.rows; ++y) + { + int sx = 0; + int ex = disMat0.cols; + if (targetWidth < disMat0.cols) + { + sx = disMat0.cols - targetWidth; + } + if (targetWidth > disMat1.cols) + { + ex = disMat0.cols - (targetWidth - disMat1.cols); + } + + for (int x = sx; x < ex; ++x) + { + // find max neighbor + int sy = y - 1; + float maxVal = -1; + int sj0 = x - n >= 0 ? x - n : 0; + int ej0 = x + n <= disMat0.cols - 1 ? x + n : disMat0.cols - 1; + int sj1 = sj0 + targetWidth - disMat0.cols; + int ej1 = ej0 + targetWidth - disMat0.cols; + if (sj1 < 0) + { + sj0 += -sj1; + } + if (ej1 > disMat1.cols - 1) + { + ej0 -= ej1 - disMat1.cols + 1; + } + float baseSVal0 = 0; + if (!smoothMat0.empty()) + { + baseSVal0 = smoothMat0.at(y, x); + } + float baseSVal1 = 0; + if (!smoothMat1.empty()) + { + baseSVal1 = smoothMat1.at(y, x + targetWidth - smoothMat0.cols); + } + for (int j = sj0; j <= ej0; j++) + { + float disVal0 = disMat0.at(sy, j); + int j1 = j + targetWidth - disMat0.cols; + float disVal1 = disMat1.at(sy, j1); + float sVal0 = 0; + if (!smoothMat0.empty()) + { + sVal0 = smoothMat0.at(sy, j); + } + float sVal1 = 0; + if (!smoothMat1.empty()) + { + sVal1 = smoothMat1.at(sy, j1); + } + float energyVal = disVal0 + disVal1 + + (255 - abs(sVal0 - baseSVal0))*sw + + (255 - abs(sVal1 - baseSVal1))*sw; + if (maxVal < energyVal) + { + maxVal = energyVal; + } + } + + disMat0.at(y, x) += maxVal; + } + } +} + diff --git a/molunCar/DynamicProgramSearch.h b/molunCar/DynamicProgramSearch.h new file mode 100644 index 0000000..3d1c0e4 --- /dev/null +++ b/molunCar/DynamicProgramSearch.h @@ -0,0 +1,118 @@ +#ifndef _DynamicProgramSearch_h_ +#define _DynamicProgramSearch_h_ + +#include +#include "CVUtils.h" + +using namespace cv; + +using std::vector; + +void dynamicProgramYWithSmooth(Mat& disMat, int n = 2, float sw = 1.0, const Mat& smoothMat = Mat()); +void dynamicProgramYWithSmooth(Mat& disMat0, Mat& disMat1, + const Mat& smoothMat0, const Mat& smoothMat1, int targetWidth, int n, float sw); + +void dynamicProgramY(Mat& disMat, int n = 2); +void dynamicProgramX(Mat& disMat, int n = 2); + +void findMaxXPath(const Mat& disMat, vector& vec, vector& energyVec, + int n = 2); +void findMaxYPath(const Mat& disMat, vector& vec, vector& energyVec, + int n = 2); + +void recoverPathEnergy(vector& vec); + +inline float ptLineDisY(const Vec4f& line, const Point& pt) +{ + float p0x = line.val[2]; + float p0y = line.val[3]; + float p1x = line.val[0] + p0x; + float p1y = line.val[1] + p0y; + + float px = pt.x; + float py = pt.y; + + float y = p0y + (p1y - p0y) / (p1x - p0x) * (px - p0x); + + float dy = y - py; + if (dy > 0) + { + return dy; + } + else + { + return -dy; + } +} + +inline float ptLineDis(const Vec4f& line, const Point& pt) +{ + float a = line.val[1]; + float b = -line.val[0]; + float c = -a*line.val[2] - b*line.val[3]; + return (a * (float)pt.x + b * (float)pt.y + c) * InvSqrt(a*a + b*b); +} + +inline float ptLineDis(const Vec4f& line, const Point2f& pt) +{ + float a = line.val[1]; + float b = -line.val[0]; + float c = -a*line.val[2] - b*line.val[3]; + return (a * pt.x + b * pt.y + c) * InvSqrt(a*a + b*b); +} + +inline float ptLineDis(const Vec4f& line, const vector& ptVec) +{ + float ret = 0; + for (int i = 0; i < ptVec.size(); ++i) + { + const Point& pt(ptVec[i]); + float d = ptLineDis(line, pt); + ret += abs(d); + } + return ret; +} + +template +void drawPointsX(vector& xVec, Mat& img, int sx, int sy, int color = 255, int w = 1) +{ + for (int x = 0; x < xVec.size(); ++x) + { + int y = xVec[x] + sy; + img.at<_T>(y, x + sx) = color; + for (int i = 1; i <= w / 2; ++i) + { + if (y - i >= 0) + { + img.at<_T>(y - i, x + sx) = color; + } + if (y + i < img.rows) + { + img.at<_T>(y + i, x + sx) = color; + } + } + } +} + +template +void drawPointsY(vector& yVec, Mat& img, int sx, int sy, int color = 255, int w = 1) +{ + for (int y = 0; y < yVec.size(); ++y) + { + int x = yVec[y] + sx; + img.at<_T>(y + sy, x) = color; + for (int i = 1; i <= w / 2; ++i) + { + if (x - i >= 0) + { + img.at<_T>(y + sy, x - i) = color; + } + if (x + i < img.cols) + { + img.at<_T>(y + sy, x + i) = color; + } + } + } +} + +#endif // _DynamicProgramSearch_h_ diff --git a/molunCar/ImageCompareModel.cpp b/molunCar/ImageCompareModel.cpp new file mode 100644 index 0000000..5d21887 --- /dev/null +++ b/molunCar/ImageCompareModel.cpp @@ -0,0 +1,2446 @@ +/*! \file ImageCompareModel.cpp + + Copyright (C) 2014 Hangzhou Leaper. + + Created by bang.jin at 2017/02/04. + +*/ + +#include "ImageCompareModel.h" +#include "CVUtils.h" +#include "Luffy.h" +// no need to code for plotting in visual studio later than 2017, +// install the ArrayPlotter extension to see the data distribution +#if (_MSC_VER < 1910) // vs2017 +#include "../3rd/cvplot/cvplot.h" +#endif +#include "MultiScaleImageCompareModel.h" +#include +#include +//#define DEBUG_VIEW_INTERNAL_MAT +#define IMGCMP_STR_NAME "name" +#define IMGCMP_STR_IMAGE_COMPARE_MODEL "imageCompareModel" +#define IMGCMP_STR_ALIGN_BASE_IMAGE "alignBaseImage" +#define IMGCMP_STR_COMPARE_BASE_IMAGE "compareBaseImage" +#define IMGCMP_STR_WEIGHT_MAT "weightMat" +#define IMGCMP_STR_MATCH_VAL_SCALE "matchValScale" +#define IMGCMP_STR_TARGET_MEAN_VAL "targetMeanVal" +#define IMGCMP_STR_TARGET_STDDEV_VAL "targetStddevVal" +#define IMGCMP_STR_REPEAT_NUM "repeatNum" +#define IMGCMP_STR_TRUE_SAMPLE_DIS_MEAN "trueSampleDisMean" +#define IMGCMP_STR_TRUE_SAMPLE_DIS_STDDEV "trueSampleDisStddev" +#define IMGCMP_STR_TRUE_SAMPLE_DIS_MIN "trueSampleDisMin" +#define IMGCMP_STR_TRUE_SAMPLE_DIS_MAX "trueSampleDisMax" +#define IMGCMP_STR_DIS_THRE "disThre" +#define IMGCMP_STR_FALSE_SAMPLE_MIN_DIS "falseSampleMinDis" +#define IMGCMP_STR_TRAIN_DIS "trainDis" +#define IMGCMP_STR_SMALLEST_MATCH_DIS "falseMinDis" +#define IMGCMP_STR_AVER_DIAMETER "averageDiameter" +#define IMGCMP_STR_INSIDE_WEIGHT "insideWeightimage" +#define IMGCMP_STR_INSIDE_AVE_IMAGE "insideBaseimage" +#define IMGCMP_STR_INSIDE_COMP_AVE_IMAGE "insideCompareBaseImage" +#define IMGCMP_STR_INSIDE_RADIUS "insideRadius" +#define IMGCMP_STR_INSIDE_TEMPL "innerTempl" + +#define IMGCMP_CACHE_MAX_SIZE 100 + +#define MIN_CIRCLE_RADII 5 +#define MAX_CIRCLE_RADII 110 +int ImageCompareModel::m_parallelFlag = 0; +cv::Point2f ImageCompareModel::refineCircleCen(const Mat& img, Point2f cen) +{ + int r = 2; + vector vec; + double minDiff = DBL_MAX; + Point2f bestCenter; + vector bestDftVec; + for (float y = cen.y - r; y <= cen.y + r; y += 0.5) + { + for (float x = cen.x - r; x <= cen.x + r; x += 0.5) + { + std::cout << "newCenter: " << x << "-" << y << ", "; + + Point2f newCenter(x, y); + + vector vec; + selfRotationSimilarityFeature(img, vec, newCenter); + + vector dftVec; + dft(vec, dftVec); + +// no need to code for plotting in visual studio later than 2017, +// install the ArrayPlotter extension to see the data distribution +#if (_MSC_VER < 1910) // vs2017 + CvPlot::plot("refineCenter", &(vec[0]), vec.size()); +#endif + + float diffVal = sum(vec.begin(), vec.end()); + + vec.push_back(diffVal); + + if (minDiff > diffVal) + { + minDiff = diffVal; + bestCenter = newCenter; + bestDftVec = dftVec; + } + + std::cout << "diff: " << diffVal << ", " + " minDiff: " << minDiff << ", " + " bestCenter: " << bestCenter.x << "-" << bestCenter.y << + std::endl; + } + } + + return bestCenter; +} + +cv::Mat ImageCompareModel::genWeightImage(const Mat& img, Point2f center, + float innerR /*= -1*/, float outterR /*= -1*/) +{ + if (innerR == -1) + { + // default is 30 + innerR = img.rows*0.175; + } + if (outterR == -1) + { + // default is max radius - 10 + outterR = img.rows*0.475; + } + + Mat weightImg; + weightImg.create(img.size(), CV_32FC1); + weightImg.setTo(0); + + uchar* pData = weightImg.data; + for (int y = 0; y < weightImg.rows; y++) + { + float* pFData = (float*)weightImg.row(y).data; + for (int x = 0; x < weightImg.cols; x++) + { + float& val = pFData[x]; + float dx = center.x - x; + float dy = center.y - y; + float r = sqrt(dx*dx + dy*dy); + if (r > outterR || r < innerR) + { + val = 0; + } + else + { + val = min(outterR - r, r - innerR); + } + } + } + + return weightImg; +} + +void ImageCompareModel::selfRotationSimilarityFeature( + const Mat& img, vector& vec, Point2f center /*= Point2f(-1, -1)*/) +{ + float angleStep = 1.0; + if (center.x < 0 || center.y < 0) + { + center = Point2f(img.cols / 2.0, img.rows / 2.0); + } + + double bestVal = DBL_MAX; + Mat bestRImg; + Mat fImg; + img.convertTo(fImg, CV_32FC1); + + Mat weightImg = genWeightImage(img, center); + fImg = fImg.mul(weightImg); + + double sumVal = sum(weightImg).val[0]; + + Mat baseImg; + { + Mat t = getRotationMatrix2D(center, 1.0, 1.0); + Mat rImg; + warpAffine(fImg, rImg, t, fImg.size(), CV_INTER_CUBIC); + t = getRotationMatrix2D(center, -1.0, 1.0); + warpAffine(rImg, baseImg, t, rImg.size(), CV_INTER_CUBIC); + } + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat viewBaseImg = baseImg / 255.0/25.0; + Mat viewfImg = fImg / 255.0/25.0; +#endif + + for (float a = 0; a <= 360.0; a += angleStep) + { + Mat t = getRotationMatrix2D(center, a, 1.0); + Mat rImg; + warpAffine(fImg, rImg, t, fImg.size(), CV_INTER_CUBIC); + + Mat diffImg = baseImg - rImg; + //diffImg = diffImg.mul(weightImg); + double diffVal = norm(diffImg)/sumVal; + vec.push_back(diffVal); + } +} + +cv::Mat ImageCompareModel::genMask(const Mat& img, Point2f center, + float innerR /*= -1*/, float outterR /*= -1*/, int type /*= CV_32FC1*/) +{ + Mat mask(img.size(), CV_8UC1); + mask.setTo(0); + if (innerR == -1) + { + // default is 30 + innerR = img.rows*0.178; + //innerR = img.rows; + } + if (outterR == -1) + { + // default is max radius - 10 + outterR = img.rows*0.425; + //outterR = img.rows; + } + circle(mask, center, outterR, Scalar(255), -1); + circle(mask, center, innerR, Scalar(0), -1); + if (type != CV_8UC1) + { + converToType(mask, type); + mask /= 255; + } + return mask; +} + +void ImageCompareModel::genMask() +{ + m32fMaskImg = genMask(mAlignBaseImg, Point2f(mAlignBaseImg.cols / 2.0, mAlignBaseImg.rows / 2.0)); + m8uMaskImg = genMask(mAlignBaseImg, Point2f(mAlignBaseImg.cols / 2.0, mAlignBaseImg.rows / 2.0), + -1.0, -1.0, CV_8UC1); + m32fInsideMaskImg = genInsideMask(mInSideBaseImg, Point2f(mInSideBaseImg.cols / 2.0, mInSideBaseImg.rows / 2.0)); + m8uInsideMaskImg = genInsideMask(mInSideBaseImg, Point2f(mInSideBaseImg.cols / 2.0, mInSideBaseImg.rows / 2.0), + -1.0, -1.0, CV_8UC1); +} + +void ImageCompareModel::printInfo() +{ + std::cout << IMGCMP_STR_IMAGE_COMPARE_MODEL << ": "; + std::cout << std::endl; + + printProperty(1, IMGCMP_STR_NAME, mName); + + printIndent(1); + std::cout << IMGCMP_STR_ALIGN_BASE_IMAGE << ": "; + std::cout << std::endl; + printMatInfo(mAlignBaseImg, 2); + std::cout << std::endl; + + printIndent(1); + std::cout << IMGCMP_STR_WEIGHT_MAT << ": "; + std::cout << std::endl; + printMatInfo(mWeightMat, 2); + std::cout << std::endl; + + printProperty(1, IMGCMP_STR_MATCH_VAL_SCALE, mMatchValScale); + printProperty(1, IMGCMP_STR_TARGET_MEAN_VAL, mTargetMeanVal); + printProperty(1, IMGCMP_STR_TARGET_STDDEV_VAL, mTargetStddevVal); + printProperty(1, IMGCMP_STR_REPEAT_NUM, mRepeatNum); + printProperty(1, IMGCMP_STR_DIS_THRE, mDisThre); + printProperty(1, IMGCMP_STR_TRUE_SAMPLE_DIS_STDDEV, mTrueSampleDisStddev); + printProperty(1, IMGCMP_STR_TRUE_SAMPLE_DIS_MEAN, mTrueSampleDisMean); + printProperty(1, IMGCMP_STR_TRUE_SAMPLE_DIS_MAX, mTrueSampleDisMax); + printProperty(1, IMGCMP_STR_TRUE_SAMPLE_DIS_MIN, mTrueSampleDisMin); + + printProperty(1, "filePath", mFilePath); + printProperty(1, "isEnableCache", mIsEnableCache); +} + +void ImageCompareModel::saveImages(string dirPath) +{ + imwrite(dirPath + "/base.png", mAlignBaseImg); + imwrite(dirPath + "/weight.png", mWeightMat); +} + +bool ImageCompareModel::save2file(string filePath) +{ + FileStorage fs(filePath, FileStorage::WRITE); + if (!fs.isOpened()) + { + return false; + } + + fs << IMGCMP_STR_NAME << mName << + IMGCMP_STR_ALIGN_BASE_IMAGE << mAlignBaseImg << + IMGCMP_STR_COMPARE_BASE_IMAGE << mCompareBaseImg << + IMGCMP_STR_WEIGHT_MAT << mWeightMat << + IMGCMP_STR_INSIDE_AVE_IMAGE << mInSideBaseImg << + IMGCMP_STR_INSIDE_COMP_AVE_IMAGE << mInsideCompareBaseImg << + IMGCMP_STR_INSIDE_WEIGHT << mInsideWeightMat << + IMGCMP_STR_INSIDE_TEMPL << innerTempl << + IMGCMP_STR_MATCH_VAL_SCALE << mMatchValScale << + IMGCMP_STR_TARGET_MEAN_VAL << mTargetMeanVal << + IMGCMP_STR_TARGET_STDDEV_VAL << mTargetStddevVal << + IMGCMP_STR_REPEAT_NUM << mRepeatNum << + IMGCMP_STR_TRUE_SAMPLE_DIS_MEAN << mTrueSampleDisMean << + IMGCMP_STR_TRUE_SAMPLE_DIS_STDDEV << mTrueSampleDisStddev << + IMGCMP_STR_TRUE_SAMPLE_DIS_MIN << mTrueSampleDisMin << + IMGCMP_STR_TRUE_SAMPLE_DIS_MAX << mTrueSampleDisMax << + IMGCMP_STR_FALSE_SAMPLE_MIN_DIS << getFalseSampleMinDis() << + IMGCMP_STR_DIS_THRE << mDisThre << + IMGCMP_STR_TRAIN_DIS << mDisMat << + IMGCMP_STR_AVER_DIAMETER << meanDiameter << + IMGCMP_STR_INSIDE_RADIUS << rInner; + + setFilePath(filePath); + + return true; +} + +bool ImageCompareModel::readFromFile(string filePath) +{ + FileStorage fs(filePath, FileStorage::READ); + if (!fs.isOpened()) + { + return false; + } + + fs[IMGCMP_STR_ALIGN_BASE_IMAGE] >> mAlignBaseImg; + fs[IMGCMP_STR_COMPARE_BASE_IMAGE] >> mCompareBaseImg; + fs[IMGCMP_STR_WEIGHT_MAT] >> mWeightMat; + fs[IMGCMP_STR_INSIDE_AVE_IMAGE] >> mInSideBaseImg; + fs[IMGCMP_STR_INSIDE_COMP_AVE_IMAGE] >> mInsideCompareBaseImg; + fs[IMGCMP_STR_INSIDE_WEIGHT] >> mInsideWeightMat; + fs[IMGCMP_STR_INSIDE_TEMPL] >> innerTempl; + mMatchValScale = (double)fs[IMGCMP_STR_MATCH_VAL_SCALE]; + + FileNode fn = fs[IMGCMP_STR_TARGET_MEAN_VAL]; + if (!fn.empty()) + { + mTargetMeanVal = (int)fn; + } + + fn = fs[IMGCMP_STR_TARGET_STDDEV_VAL]; + if (!fn.empty()) + { + mTargetStddevVal = (int)fn; + } + + fn = fs[IMGCMP_STR_REPEAT_NUM]; + if (!fn.empty()) + { + mRepeatNum = (int)fn; + } + else + { + //mRepeatNum = computeRepeatNum(); + } + + fn = fs[IMGCMP_STR_NAME]; + if (!fn.empty()) + { + mName = (string)fn; + } + + fn = fs[IMGCMP_STR_TRUE_SAMPLE_DIS_MEAN]; + if (!fn.empty()) + { + mTrueSampleDisMean = (double)fn; + } + fn = fs[IMGCMP_STR_TRUE_SAMPLE_DIS_STDDEV]; + if (!fn.empty()) + { + mTrueSampleDisStddev = (double)fn; + } + fn = fs[IMGCMP_STR_TRUE_SAMPLE_DIS_MIN]; + if (!fn.empty()) + { + mTrueSampleDisMin = (double)fn; + } + fn = fs[IMGCMP_STR_TRUE_SAMPLE_DIS_MAX]; + if (!fn.empty()) + { + mTrueSampleDisMax = (double)fn; + } + fn = fs[IMGCMP_STR_FALSE_SAMPLE_MIN_DIS]; + if (!fn.empty()) + { + setFalseSampleMinDis((double)fn); + } + fn = fs[IMGCMP_STR_DIS_THRE]; + if (!fn.empty()) + { + mDisThre = (double)fn; + } + + fn = fs[IMGCMP_STR_AVER_DIAMETER]; + if (!fn.empty()) + { + meanDiameter = (int)fn; + } + + fn = fs[IMGCMP_STR_INSIDE_RADIUS]; + if (!fn.empty()) + { + rInner = (float)fn; + } + + setFilePath(filePath); + + genMask(); + + return true; +} + +void ImageCompareModel::preProcessImage(Mat& img, Mat &insideImg) const +{ + preProcessImage(img, m8uMaskImg, mTargetMeanVal, mTargetStddevVal, mHighlightsThreshold); + if (insideImg.empty()) + { + return; + } + preProcessImage(insideImg, m8uInsideMaskImg, mTargetMeanVal, mTargetStddevVal, mHighlightsThreshold); +} + +void ImageCompareModel::preProcessImage(Mat& img, const Mat& mask, double tarMean, + double tarStddev, int highlightsThreshold) const +{ + if (img.channels() > 1) + { + img = getFirstChannel(img); + } + if (img.type() != CV_32FC1) + { + converToType(img, CV_32FC1); + } + + Mat gaussImg; + GaussianBlur(img, gaussImg, Size(3, 3), 5.0); + img = gaussImg; + + Mat dilatedMask; + dilate(mask, dilatedMask, Mat::ones(Size(3, 3), CV_32FC1)); + + Mat hightlightsMask = img < highlightsThreshold; + Mat imgMask = hightlightsMask & dilatedMask; + + Scalar meanScalar, stddevScalar; + meanStdDev(img, meanScalar, stddevScalar, imgMask); + img = (img - meanScalar.val[0]) * tarStddev / stddevScalar.val[0] + tarMean; + + converToType(imgMask, CV_32FC1); + imgMask /= 255.0; + Mat imgNorm = cocentricNorm(img, Point2f(img.cols / 2.0, img.rows / 2.0), + imgMask, 125); +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vImgNorm = imgNorm / 255.0; +#endif + img = imgNorm; +} + +void ImageCompareModel::preProcessImage(Mat& img, const Mat& mask, + const Mat& weightMat, double dstMean, double dstStddev, int highlightsThreshold) const +{ + if (img.channels() > 1) + { + img = getFirstChannel(img); + } + if (img.type() != CV_32FC1) + { + converToType(img, CV_32FC1); + } + + Mat gaussImg; + GaussianBlur(img, gaussImg, Size(3, 3), 5.0); + img = gaussImg; + + Mat hightlightsMask = img < highlightsThreshold; + Mat imgMask = hightlightsMask & mask & (weightMat > 0); + + Scalar meanScalar, stddevScalar; + meanStdDev(img, meanScalar, stddevScalar, imgMask); + img = (img - meanScalar.val[0]) * dstStddev / stddevScalar.val[0] + dstMean; + //img.setTo(0, img < 0); + converToType(hightlightsMask, CV_32FC1); + //converToType(imgMask, CV_32FC1); + Mat imgNorm = cocentricNorm(img, Point2f(img.cols / 2.0, img.rows / 2.0), + weightMat.mul(hightlightsMask), 125); +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vImgNorm = imgNorm / 255.0; +#endif + img = imgNorm; +} + +void ImageCompareModel::preProcessImage0(Mat& img) const +{ + +} + +double ImageCompareModel::compare(Mat img0, Mat img1, Mat* pRImg0, Mat* pRImg1) +{ + if (img0.size() != img1.size() && img0.size() != mAlignBaseImg.size()) + { + return -1; + } + + double bestMatchVal = DBL_MAX; + int bestAngle = -1; + Mat bestImg1; + double bestDD = 0; + + double w = 0; + + Point2f center((float)img1.cols / 2.0, (float)img1.rows / 2.0); + Mat rImg0 = rotateMatch(img0).mBestRImg; + Mat rImg1 = rotateMatch(img1).mBestRImg; + Mat dMat = rImg0 - rImg1; + converToType(dMat, CV_32FC1); + dMat = dMat.mul(mWeightMat); + + if (pRImg0) + { + *pRImg0 = rImg0; + } + if (pRImg1) + { + *pRImg1 = rImg1; + } + + return genMatchValue(dMat); +} + +double ImageCompareModel::compare(Mat srcImage, Mat* pRImg /*= NULL*/, int levelNum /*= 1*/, + bool isFilterSize /*= true*/, int flag, double md_diameter, double md_height) +{ + if (mIsEnableCache) + { + auto cacheIter = mDisCache.find(srcImage.data); + if (cacheIter != mDisCache.end()) + { + return cacheIter->second; + } + } +/* CircleDetector cd; + cd.setAlgoType(CircleDetector::PeakCircle); + cd.setEdgeWidth(3); + //cd.setPolarity(Polarity::White2Black); + cd.setPolarity(Polarity::Either); + cd.setFindBy(FindBy::Best); + //cd.setFindBy(FindBy::Last); + cd.setRadii(MIN_CIRCLE_RADII, MAX_CIRCLE_RADII); + cd.setACThres(5); + Vec3f bestCircle; + cd.detectBest(srcImage, Point2f(srcImage.cols / 2, srcImage.rows / 2), bestCircle, nullptr); + +#ifdef DEBUG_VIEW_INTERNAL_MAT + vector vecs; + vecs.push_back(srcImage); + vecs.push_back(srcImage); + vecs.push_back(srcImage); + Mat rr; + merge(vecs, rr); + + circle(rr, Point2f(bestCircle[0], bestCircle[1]), bestCircle[2], Scalar(255, 0, 0), 2); +#endif + + if (bestCircle[2] < 3 || bestCircle[0] == 0 || bestCircle[1] == 0) + return DBL_MAX; + */ +// int x_Axis = bestCircle[0] - MAX_CIRCLE_RADII; +// int y_Axis = bestCircle[1] - MAX_CIRCLE_RADII; + + int x_Axis = srcImage.cols / 2 - MAX_CIRCLE_RADII; + int y_Axis = srcImage.rows / 2 - MAX_CIRCLE_RADII; + + int r_Axis = 2 * MAX_CIRCLE_RADII; + + if (x_Axis <= 0 || y_Axis <= 0) + return DBL_MAX; + if (r_Axis <= 0) + return DBL_MAX; + if (r_Axis >= srcImage.cols || r_Axis >= srcImage.rows) + return DBL_MAX; +// if (srcImage.cols < (bestCircle[0] + MAX_CIRCLE_RADII) || srcImage.rows < (bestCircle[1] + MAX_CIRCLE_RADII)) +// return DBL_MAX; + + if (srcImage.cols < (srcImage.cols / 2 + MAX_CIRCLE_RADII) || srcImage.rows < (srcImage.rows / 2 + MAX_CIRCLE_RADII)) + return DBL_MAX; + + Rect rect(x_Axis, y_Axis, r_Axis, r_Axis); + Mat srcCenterMat; + srcImage(rect).copyTo(srcCenterMat); + Mat img, centerMat; + resizeMat(srcImage, img); + resizeMat(srcCenterMat, centerMat); + Mat rawImg = img.clone(); + int nRadiusDiff = 15; + if (mAlignBaseImg.size() != img.size() || mInSideBaseImg.size() !=centerMat.size()) + { + if (isFilterSize \ + && abs(mAlignBaseImg.size().width - img.size().width) > nRadiusDiff \ + || abs(mInSideBaseImg.size().width - centerMat.size().width) > 3) + { + return DBL_MAX; + } + resize(img, img, mAlignBaseImg.size()); + resize(centerMat, centerMat, mInSideBaseImg.size()); + } + + Mat matchImg = img.clone(); + Mat matchInsideImg = centerMat.clone(); + + preProcessImage(matchImg, matchInsideImg); + + if (!mpMultiScaleModel) + { + initMultiScaleModel(); + } + m_parallelFlag = 0; + RotateMatchResult rmr = rotateMatch(matchImg, levelNum); + Mat rImg = rmr.mBestRImg; + m_parallelFlag = 1; + RotateMatchResult rmmInside = rotateMatch(matchInsideImg, 1); + Mat rInsideImg = rmmInside.mBestRImg; + if (rImg.empty() || rInsideImg.empty()) + { + return DBL_MAX; + } + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vRImg = rImg / 255.0; + Mat vInsideMat = rInsideImg / 255.0; +#endif + + double bestAngle = rmr.mBestAngle; + Mat cmpImg = rotateImage(img, Point2f(img.cols / 2.0, img.rows / 2.0), + bestAngle); + + // remove highlights + Mat hightlightsMask = cmpImg < mHighlightsThreshold; + converToType(hightlightsMask, CV_32FC1); + hightlightsMask /= 255.0; + + Mat unifiedMask = m32fMaskImg.mul(hightlightsMask).mul(mWeightMat); + //Mat ii = mWeightMat / 255.0; + preProcessImage(cmpImg, m8uMaskImg, mWeightMat, mTargetMeanVal, + mTargetStddevVal, mHighlightsThreshold); + + cmpImg.setTo(0, cmpImg < 0); + + converToType(cmpImg, CV_32FC1); + normSectors_tarImg(cmpImg, unifiedMask, imgCen(cmpImg), 1, + mCompareBaseImg); + + double bestInsideAngle = rmmInside.mBestAngle; + Mat camInsideMat = rotateImage(centerMat, Point2f(centerMat.cols / 2.0, centerMat.rows / 2.0), + bestInsideAngle); + +// float offset = 5; +// float startX = mAlignBaseImg.cols / 2.0 - rInner - offset; +// float startY = mAlignBaseImg.rows / 2.0 - rInner - offset; +// Rect rect(startX, startY, 2*(rInner + offset), 2*(rInner + offset)); +// Mat innerPartMat = camInsideMat(rect); +// Mat rstMat; +// cv::matchTemplate(innerTempl, innerPartMat, rstMat, CV_TM_SQDIFF_NORMED); +// double minVal; +// Point minLoc; +// minMaxLoc(rstMat, &minVal, NULL, &minLoc, NULL); +//#ifdef DEBUG_VIEW_INTERNAL_MAT +// Mat test1 = innerPartMat.clone(); +// Mat test2 = innerPartMat.clone(); +// vector imgs; +// imgs.push_back(test2); +// imgs.push_back(test1); +// imgs.push_back(innerPartMat); +// Mat rst; +// merge(imgs, rst); +// cv::rectangle(rst, Rect(minLoc.x, minLoc.y, innerTempl.cols, innerTempl.rows), Scalar(0, 0, 255)); +//#endif +// //debug +// + Mat highLightInsideMask = camInsideMat < mHighlightsThreshold; + converToType(highLightInsideMask, CV_32FC1); + highLightInsideMask /= 255.0; + Mat unifiedInsideMask = m32fInsideMaskImg.mul(highLightInsideMask).mul(mInsideWeightMat); + preProcessImage(camInsideMat, m8uInsideMaskImg, mInsideWeightMat, mTargetMeanVal, + mTargetStddevVal, mHighlightsThreshold); + Mat vcamInside = camInsideMat / 255.0; + camInsideMat.setTo(0, camInsideMat < 0); + //mInsideCompareBaseImg.setTo(0, mInsideCompareBaseImg < 0); + normSectors_tarImg(camInsideMat, unifiedInsideMask, imgCen(camInsideMat), 1, mInsideCompareBaseImg); + // + mInsideCompareBaseImg.setTo(0, mInsideCompareBaseImg < 0); +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vRotatedImg0 = cmpImg / 255.0; + Mat vrotatedInsideImage = camInsideMat / 255.0; + cv::imwrite("F:\\temp\\insideAfterSector.png", camInsideMat); +#endif + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vRotatedImg1 = mCompareBaseImg / 255.0; + Mat vrotatedInsideBase = mInsideCompareBaseImg / 255.0; + cv::imwrite("F:\\temp\\insideAfterSectorCompareBase.png", mInsideCompareBaseImg); +#endif + + cmpImg.setTo(0, cmpImg < 10); + //camInsideMat.setTo(0, camInsideMat < 10); + if (pRImg) + { + *pRImg = cmpImg; + } + m_parallelFlag = 0; + double ret = genMatchValue(cmpImg, mCompareBaseImg, unifiedMask, flag, rawImg.rows, md_diameter, md_height);//µÃ³öÏàËÆÖµ + + m_parallelFlag = 1; + double retInside = genMatchValue(camInsideMat, mInsideCompareBaseImg, unifiedInsideMask, 0, rawImg.rows, md_diameter, md_height);//µÃ³öÏàËÆÖµ + + ret = 0.6*ret + 0.4*retInside; + + if (ret > mDisThre) + { + return DBL_MAX; + } + + if (mIsEnableCache) + { + mDisCache[img.data] = ret; + if (mDisCache.size() > IMGCMP_CACHE_MAX_SIZE) + { + mDisCache.clear(); + } + } + return ret; +} + +void ImageCompareModel::train(const vector& vec) +{ +#ifdef _DEBUG + std::cout << getName() << std::endl; +#endif _DEBUG + if (0 == vec.size()) { + return; + } + +// CircleDetector cd; +// cd.setAlgoType(CircleDetector::PeakCircle); +// cd.setEdgeWidth(3); +// //cd.setPolarity(Polarity::White2Black); +// cd.setPolarity(Polarity::Either); +// cd.setFindBy(FindBy::Best); +// //cd.setFindBy(FindBy::Last); +// cd.setRadii(MIN_CIRCLE_RADII, MAX_CIRCLE_RADII); +// cd.setACThres(5); + vector centerMatVec; + vector tmpVec; + for (int i = 0; i < vec.size(); i++) + { + Mat originalMat = vec[i]; + Vec3f bestCircle; + Point2f p = Point2f(originalMat.cols / 2.0, originalMat.rows / 2.0); +// cd.detectBest(originalMat, Point2f(originalMat.cols / 2.0, originalMat.rows / 2.0), bestCircle, nullptr); +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat img1 = vec.front().clone(); + Mat img2 = vec.front().clone(); + vector vecs; + vecs.push_back(originalMat); + vecs.push_back(originalMat); + vecs.push_back(originalMat); + Mat rr; + merge(vecs, rr); + + circle(rr, Point2f(bestCircle[0], bestCircle[1]), bestCircle[2], Scalar(255, 0, 0), 2); + vector imgVec; + resizeVecMat(vec, imgVec); + vector vecss; + vecss.push_back(originalMat); + vecss.push_back(originalMat); + vecss.push_back(originalMat); + Mat rrr; + merge(vecss, rrr); + + // circle(rrr, Point2f(bestCircle[0] / COLS_SCALE, bestCircle[1] / COLS_SCALE), rInner, Scalar(255, 0, 0), 1); +#endif // DEBUG_VIEW_INTERNAL_MAT + + //bob edit +// if (bestCircle[2] > 0.00000001) +// { +// float startX = bestCircle[0] - MAX_CIRCLE_RADII;//ÕÒ×î´óÔ²Íâ¾¶£¬¹Ì¶¨Ö±¾¶ +// float startY = bestCircle[1] - MAX_CIRCLE_RADII; +// Rect rect(startX, startY, MAX_CIRCLE_RADII * 2, MAX_CIRCLE_RADII * 2); +// Mat origianlCenterMat; +// originalMat(rect).copyTo(origianlCenterMat); +// centerMatVec.push_back(origianlCenterMat); +// tmpVec.push_back(vec[i]); +// } +// else +// { +// int a = 0; +// } + + float startX = originalMat.cols / 2.0 - MAX_CIRCLE_RADII;//ÕÒ×î´óÔ²Íâ¾¶£¬¹Ì¶¨Ö±¾¶ + float startY = originalMat.rows / 2.0 - MAX_CIRCLE_RADII; + Rect rect(startX, startY, MAX_CIRCLE_RADII * 2, MAX_CIRCLE_RADII * 2); + Mat origianlCenterMat; + originalMat(rect).copyTo(origianlCenterMat); + centerMatVec.push_back(origianlCenterMat); + tmpVec.push_back(vec[i]); + + /*bob end edit*/ + +// if (bestCircle[2] > 0.00000001) +// { +// float startX = bestCircle[0] - bestCircle[2]; +// float startY = bestCircle[1] - bestCircle[2]; +// Rect rect(startX, startY, bestCircle[2]*2, bestCircle[2]*2); +// Mat origianlCenterMat; +// originalMat(rect).copyTo(origianlCenterMat); +// centerMatVec.push_back(origianlCenterMat); +// } +// else +// { +// +// } + } + vector scaledCenterVec; + resizeVecMat(centerMatVec, scaledCenterVec); + if (scaledCenterVec.size() <= 0) + return; + if (tmpVec.size() <= 0) + return; + + vector imgVec; + resizeVecMat(tmpVec, imgVec); + mAlignBaseImg = imgVec.front(); + mInSideBaseImg = scaledCenterVec.front(); + genMask(); + //Mat baseTempl = imgVec.front(); + //float realR = rInner + 2; + //float hf_width = sqrt(realR*realR / 2.0); + //float startx = baseTempl.cols / 2 - hf_width; + //float starty = baseTempl.rows / 2 - hf_width; + //Rect rect(startx, starty, 2*hf_width, 2*hf_width); + //innerTempl = baseTempl(rect); + preProcessImage(mAlignBaseImg, mInSideBaseImg);//Ô¤´¦Àí ¹âÕÕ¾ùºâ + Mat sumOutsideMat = Mat::zeros(mAlignBaseImg.size(), CV_32FC1); + Mat sumInsideMat = Mat::zeros(mInSideBaseImg.size(), CV_32FC1); + Mat minOutsideMat(mAlignBaseImg.size(), mAlignBaseImg.type()); + Mat minInsideMat(mInSideBaseImg.size(), mInSideBaseImg.type()); + minOutsideMat.setTo(FLT_MAX); + minInsideMat.setTo(FLT_MAX); + vector rImgVec; + vector rInsideImgVec; + vector rmrVec; + vector rmrVecInside; + vector resizedImgVec; + vector resizedCenterVec; + //vector resizedWarp; + vector diametersVec; + for (int i = 0; i < imgVec.size(); ++i) + { + diametersVec.push_back(imgVec[i].rows); + Mat img = imgVec[i].clone(); + if (img.size() != mAlignBaseImg.size()) + { + Mat resizedImg; + resize(img, resizedImg, mAlignBaseImg.size()); + //resize(warpImage, warpImage, mAlignBaseImg.size()); + img = resizedImg; + } + + Mat centerMat = scaledCenterVec[i]; + if (centerMat.size() != mInSideBaseImg.size()) + { + Mat resizedCenterMat; + resize(centerMat, resizedCenterMat, minInsideMat.size()); + centerMat = resizedCenterMat; + } + resizedImgVec.push_back(img); + resizedCenterVec.push_back(centerMat); + //resizedWarp.push_back(warpImage); + //Mat insideImg = img.clone(); + preProcessImage(img, centerMat); + m_parallelFlag = 0; + RotateMatchResult rmr = rotateMatch(img);//ÐýתƥÅä Êä³öÆ¥ÅäÍê³ÉµÄ½Ç¶È¼°Í¼Æ¬ + rmrVec.push_back(rmr); + m_parallelFlag = 1; + RotateMatchResult rmrInside = rotateMatch(centerMat); + rmrVecInside.push_back(rmrInside); + + Mat rImg = rmr.mBestRImg; + //rImgVec.push_back(rImg); + + Mat rInsideImg = rmrInside.mBestRImg; + rInsideImgVec.push_back(rInsideImg); + //minMat = min(minMat, rImg); + minOutsideMat = min(minOutsideMat, rImg);//Éú³ÉÈ¨ÖØÍ¼ ȥë´Ì + sumOutsideMat += rImg; + + minInsideMat = min(minInsideMat, rInsideImg); + sumInsideMat += rInsideImg; + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat viewRImg = rImg / 255.0; + Mat viewRInsideImg = rInsideImg / 255.0; + Mat viewImage = minOutsideMat / 255.0; + Mat viewInside = minInsideMat / 255.0; +#endif + } + meanDiameter = mean(diametersVec)[0]; + + Mat avgMat = sumOutsideMat / imgVec.size(); + mAlignBaseImg = avgMat; + + Mat avgInsideMat = sumInsideMat / imgVec.size(); + mInSideBaseImg = avgInsideMat; +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vAvgMat = avgMat / 255.0; + Mat vAvgInsideMat = avgInsideMat / 255.0; +#endif + //mWeightMat = minMat; + mWeightMat = minOutsideMat; + mWeightMat /= 255.0; + mInsideWeightMat = minInsideMat; + mInsideWeightMat /= 255.0; + luffy_base::luffy_imageProc::meanvarnorm(mWeightMat, mWeightMat,//¶ÔÈ¨ÖØ½øÐÐ ¹éÒ»»¯ + mTargetMeanVal, 150, m8uMaskImg); + mWeightMat.setTo(0, mWeightMat < 10); + Scalar meanScalar, stddevScalar; + meanStdDev(mInsideWeightMat, meanScalar, stddevScalar, m8uInsideMaskImg); + mInsideWeightMat = mInsideWeightMat* (127 / meanScalar.val[0]); + //luffy_base::luffy_imageProc::meanvarnorm(mInsideWeightMat, mInsideWeightMat, + // mTargetMeanVal, 50, m8uInsideMaskImg); + mInsideWeightMat.setTo(0, mInsideWeightMat < 0); + + { + if(mRepeatNum <= 0) + { + mRepeatNum = computeRepeatNum(); + } + selfRotateMin(mWeightMat, mWeightMat, mRepeatNum); + } + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vWeightMat = mWeightMat / 255.0; + Mat vBaseMat = mAlignBaseImg / 255.0; + // Mat vMinMat = minMat / 255.0; + Mat vMinMat = minOutsideMat / 255.0; + + Mat vInsideWeight = mInsideWeightMat / 255.0; + Mat vInsidebaseMat = mInSideBaseImg / 255.0; + Mat vMinInsideMat = minInsideMat / 255.0; + +#endif + + sumOutsideMat.setTo(0); + sumInsideMat.setTo(0); + for (int i = 0; i < rmrVec.size(); ++i) + { + double bestAngle = rmrVec[i].mBestAngle; + Mat img = resizedImgVec[i]; + Mat rotatedImg = rotateImage(img, Point2f(img.cols/2.0, img.rows/2.0), + bestAngle); + + preProcessImage(rotatedImg, m8uMaskImg, mWeightMat, mTargetMeanVal, + mTargetStddevVal, mHighlightsThreshold); + + double bestInsidePart = rmrVecInside[i].mBestAngle; + //Mat i_Mat = resizedImgVec[i]; + Mat centerMat = resizedCenterVec[i]; + Mat rotatedInsideMat = rotateImage(centerMat, Point2f(centerMat.cols / 2.0, centerMat.rows / 2.0), bestInsidePart); + + preProcessImage(rotatedInsideMat, m8uInsideMaskImg, mInsideWeightMat, mTargetMeanVal, + mTargetStddevVal, mHighlightsThreshold); + + sumOutsideMat += rotatedImg; + //rotatedInsideMat.setTo(0, rotatedInsideMat < 0); + sumInsideMat += rotatedInsideMat; + } + mCompareBaseImg = sumOutsideMat / resizedImgVec.size();//µÃµ½×îÖÕÆ¥Åäͼ + mInsideCompareBaseImg = sumInsideMat / resizedImgVec.size(); +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vCompareBaseImg = mCompareBaseImg / 255.0; + vWeightMat = mWeightMat / 255.0; + Mat vcompareInsideBaseImg = mInsideCompareBaseImg / 255.0; + vInsideWeight = mInsideWeightMat / 255.0; + //cv::imwrite("F:\\temp\\insideWeight.png", mInsideWeightMat); + //cv::imwrite("F:\\temp\\insideBase.png", mInSideBaseImg); + //cv::imwrite("F:\\temp\\insideCompareBase.png", mInsideCompareBaseImg); +#endif + + trueSampleWeightRecon(resizedImgVec, resizedCenterVec, rmrVec, rmrVecInside); + mWeightMat = falseReConMat.clone(); + mInsideWeightMat = insideWeightRecon.clone(); +} + +void ImageCompareModel::trueSampleWeightRecon(const vector& resizedVec, + const vector& resizedCenterVec, + vector rmrVec, + vector rmrVecInside) +{ + falseReConMat = mWeightMat.clone(); + insideWeightRecon = mInsideWeightMat.clone(); + for (int i = 0; i < resizedVec.size(); i++) + { + const Mat &img = resizedVec[i]; + double bestAngle = rmrVec[i].mBestAngle; + //preProcessImage(processedImg, insideMatchImg); + Mat rotatedImg = rotateImage(img, Point2f(img.cols / 2.0, img.rows / 2.0), + bestAngle); + + Mat hightlightsMask = rotatedImg < mHighlightsThreshold; + converToType(hightlightsMask, CV_32FC1); + hightlightsMask /= 255.0; + + Mat unifiedMask = m32fMaskImg.mul(hightlightsMask).mul(mWeightMat); + + preProcessImage(rotatedImg, m8uMaskImg, mWeightMat, mTargetMeanVal, + mTargetStddevVal, mHighlightsThreshold); + + rotatedImg.setTo(0, rotatedImg < 0); + converToType(rotatedImg, CV_32FC1); + normSectors_tarImg(rotatedImg, unifiedMask, imgCen(rotatedImg), 1, mCompareBaseImg); + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vRotatedImg1 = rotatedImg / 255.0; +#endif + rotatedImg.setTo(0, rotatedImg < 10); + + ////// FOR INSIDE CIRCLE + double insideBestAngle = rmrVecInside[i].mBestAngle; + const Mat & centerMat = resizedCenterVec[i]; + Mat rotatedInsideImg = rotateImage(centerMat, Point2f(centerMat.cols / 2.0, centerMat.rows / 2.0), + insideBestAngle); + + Mat highLightInsideMask = rotatedInsideImg < mHighlightsThreshold; + converToType(highLightInsideMask, CV_32FC1); + highLightInsideMask /= 255.0; + Mat unifiedInsideMask = m32fInsideMaskImg.mul(highLightInsideMask).mul(mInsideWeightMat); + preProcessImage(rotatedInsideImg, m8uInsideMaskImg, mInsideWeightMat, mTargetMeanVal, + mTargetStddevVal, mHighlightsThreshold); + Mat vcamInside = rotatedInsideImg / 255.0; + rotatedInsideImg.setTo(0, rotatedInsideImg < 0); + //mInsideCompareBaseImg.setTo(0, mInsideCompareBaseImg < 0); + normSectors_tarImg(rotatedInsideImg, unifiedInsideMask, imgCen(rotatedInsideImg), 1, mInsideCompareBaseImg); + trueWeightRecon(rotatedImg, mCompareBaseImg, rotatedInsideImg, mInsideCompareBaseImg); + } +} + +void ImageCompareModel::trueWeightRecon(const Mat& rImg, const Mat& baseImg, + const Mat& insideRimg, const Mat& insideBaseImg) +{ + Mat dMat = abs(rImg - baseImg); +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat tt = dMat / 255.0; + Mat m1 = rImg / 255.0; + Mat b1 = baseImg / 255.0; +#endif // DEBUG_VIEW_INTERNAL_MAT + + //Mat mData = dMat.mul(m32fMaskImg).mul(mWeightMat); + Mat mData; + dMat.copyTo(mData, dMat > 1000); + + double outterVal = sum(mData).val[0]; + if (outterVal != 0) + { + mData = minMaxNorm(mData, 0, 255.0); + } + + + Mat i_dMat = abs(insideRimg - insideBaseImg); +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat i_tt = i_dMat / 255.0; + Mat i_m1 = insideRimg / 255.0; + Mat i_b1 = insideBaseImg / 255.0; + Mat i_maskR = i_m1.mul(m32fInsideMaskImg); + Mat i_masB = i_b1.mul(m32fInsideMaskImg); + +#endif // DEBUG_VIEW_INTERNAL_MAT + Mat i_mData; + i_mData.setTo(0); + //Mat test = i_dMat / 255; + //Mat imgtest = i_dMat.mul(m32fInsideMaskImg) / 255; + Mat innerDmat = i_dMat.mul(m32fInsideMaskImg).mul(mInsideWeightMat); + innerDmat = minMaxNorm(innerDmat, 0, 255.0); + + weightMapping(mData, 255.0, innerDmat, 255.0); +} + +void ImageCompareModel::weightMapping(const Mat& mData, double maxVal, const Mat& i_mData, double i_maxVal) +{ + for (int i = 0; i < mData.rows; i++) + { + float* pData = (float*)mData.row(i).data; + float*pWeight = (float*)falseReConMat.row(i).data; + + for (int j = 0; j < mData.cols; j++) + { + double pixVal = pData[j]; + + if (pixVal != 0) + { + double affineCoeff = descendFunction(pixVal, maxVal); + pWeight[j] *= affineCoeff; + } + + + + + } + } + + + for (int i = 0; i < i_mData.rows; i++) + { + float* i_pData = (float*)i_mData.row(i).data; + float*i_pWeight = (float*)insideWeightRecon.row(i).data; + + for (int j = 0; j < i_mData.cols; j++) + { + double i_pixVal = i_pData[j]; + + if (i_pixVal != 0) + { + double affineCoeff = descendFunction(i_pixVal, i_maxVal); + i_pWeight[j] *= affineCoeff; + } + } + } +} +double ImageCompareModel::descendFunction(double pixVal, double maxVal) +{ + return (-1.0 / maxVal)*pixVal + 1.0; + //return -1 * pow(pixVal, 2) + 1; +} + + +void ImageCompareModel::calculateAllParams(const vector& imgVec) +{ + mDisThre = DBL_MAX; + Mat disMat(1, imgVec.size(), CV_64FC1); + for (int i = 0; i < imgVec.size(); ++i) + { + const Mat& img = imgVec[i]; + double dis = compare(img, NULL, 1, false, 0); + disMat.at(i) = dis; + //disMat.copyTo(mDisMat); + } + + if (disMat.cols == 1) + { + mTrueSampleDisStddev = 0; + mTrueSampleDisMean = disMat.at(0); + } + else + { + Scalar m, s; + meanStdDev(disMat, m, s); + mTrueSampleDisStddev = s.val[0]; + mTrueSampleDisMean = m.val[0]; + } + disMat.copyTo(mDisMat); + minMaxIdx(disMat, &mTrueSampleDisMin, &mTrueSampleDisMax); +} + +void ImageCompareModel::computeDisThre(const vector& imgVec, double* pMinDis, double md_diameter, double md_height) +{ + if (imgVec.empty()) + { + return; + } + mDisThre = DBL_MAX; + vector disVec; + std::map imgs; + for_each(imgVec.begin(), imgVec.end(), [&](Mat img) + { + double dis = compare(img, NULL, 3, false, 1, md_diameter, md_height); + disVec.push_back(dis); + imgs[dis] = img; + }); + double minDis = *(min_element(disVec.begin(), disVec.end())); + setFalseSampleMinDis(minDis); + + if (pMinDis) + { + *pMinDis = minDis; + } + if (minDis > mTrueSampleDisMax) + { + mDisThre = mTrueSampleDisMax*0.3 + minDis*0.7; + //mDisThre = mTrueSampleDisMax*0.15 + minDis*0.85 + mTrueSampleDisStddev; + //mDisThre = minDis; + if (mDisThre > mTrueSampleDisMax*1.8) + { + mDisThre = mTrueSampleDisMax*1.8; + } + } + else + { + mDisThre = DBL_MAX; + std::cout << "max distance in training > min distance in testing" << std::endl; + } +} + +void ImageCompareModel::genUniformSepIdx(int num, int startIdx, int endIdx, vector& cenVec) +{ + float step = (endIdx - startIdx) / (num); + for (int i = 0; i < num; i++) + { + cenVec.push_back(floorf(step*i + startIdx)); + } +} + +void ImageCompareModel::genAngleRanges(vector cenVec, vector& rangeVec, int localRange) +{ + rangeVec.clear(); + for_each(cenVec.begin(), cenVec.end(), [&](int cen) + { + Range r; + r.start = cen - localRange; + r.end = cen + localRange + 1; + rangeVec.push_back(r); + }); +} + +void ImageCompareModel::genCandidateAngleRanges(vector disVec, + float angleStep, vector& rangeVec) +{ + auto minDisIter = min_element(disVec.begin(), disVec.end()); + int startIdx = minDisIter - disVec.begin(); + + rotate(disVec.begin(), minDisIter, disVec.end()); + + Mat disVecMat(1, disVec.size(), CV_32FC1, &(disVec[0])); + Mat normDisVecMat; + luffy_base::luffy_imageProc::meanvarnorm(disVecMat, normDisVecMat, 0, 1.0); + + set canNums; + genCandidateRepeatNums(canNums); + vector cenVec; + int repeatNum = recognizeLowerExtremes(normDisVecMat, canNums, 5, &cenVec); +#ifdef _TEST + if (repeatNum <= 0) + { + std::cout << "repeatNum < 0" << std::endl; + //waitKey(); + } +#endif + if (repeatNum < 0) + { + rangeVec.clear(); + return; + cenVec.clear(); + cenVec.push_back(disVec.size() / 2); + genAngleRanges(cenVec, rangeVec, disVec.size() / 2); + } + else + { + if (cenVec.size() >= 2 && diffAngleRaw(cenVec.front(), cenVec.back()) < 2) + { + cenVec.pop_back(); + } + + for_each(cenVec.begin(), cenVec.end(), [&](int& cen) + { + cen += startIdx; + cen *= angleStep; + cen = normAngle(cen); + }); + for (auto i = cenVec.begin(); i != cenVec.end(); ++i) + { + if (*i < startIdx*angleStep) + { + rotate(cenVec.begin(), i, cenVec.end()); + break; + } + } +#ifdef _TEST + for (int i = 0; i < cenVec.size() - 1; ++i) + { + float curVal = cenVec[i]; + float nxtVal = cenVec[i + 1]; + if (nxtVal <= curVal) + { + std::cout << "assert nxtVal <= curVal failed" << std::endl; + waitKey(); + } + } +#endif + genAngleRanges(cenVec, rangeVec, 10); + } + +} + +void ImageCompareModel::genCandidateAngleRanges(const Mat& disMat, + float angleStep, vector& rangeVec, float rangeScale /*= 1*/) +{ + for (int i = 0; i < disMat.rows; ++i) + { + Mat disVec = disMat.row(i); + double minVal, maxVal; + Point minPt, maxPt; + minMaxLoc(disVec, &minVal, &maxVal, &minPt, &maxPt); + Range &r = rangeVec[i]; + float cenAngle = r.start + angleStep*minPt.x; + float angleRangeWidth = r.end - r.start; + + r.start = floorf(cenAngle - angleRangeWidth * 0.5 * rangeScale); + r.end = floorf(cenAngle + angleRangeWidth * 0.5 * rangeScale); + } +} + +void ImageCompareModel::selfRotateMin(const Mat& img, Mat& weightMat, int repeatNum) +{ + Point2f cen(img.cols / 2.0, img.rows / 2.0); + float angleStep = 360.0 / repeatNum; + Mat dSumMat = Mat::ones(img.size(), img.type()) * 255; + //Mat dSumMat = Mat::ones(img.size(), img.type()); + for (int i = 1; i < repeatNum; ++i) + { + Mat rotatedImg = rotateImage(img, cen, i*angleStep); + Mat dilatedImg; + dilate(rotatedImg, dilatedImg, Mat::ones(3, 3, CV_32FC1)); + dSumMat = min(dSumMat, dilatedImg); + } + weightMat = dSumMat; +} + +double ImageCompareModel::genMatchValue(const Mat& dMat) const +{ + double v = norm(dMat, NORM_L2); + + double ret = scaleMatchValue(v); + + return ret; +} + +double ImageCompareModel::genMatchValue(const Mat& rImg, const Mat& baseImg, + const Mat& maskImg, int flag, int diameter, double md_diameter, double md_height) const +{ + Mat dMat = rImg - baseImg; + //Mat testMask = maskImg.mul(m32fMaskImg); + dMat = abs(dMat.mul(maskImg)); + //dMat = abs(dMat.mul(testMask)); +#ifdef DEBUG_VIEW_INTERNAL_MAT + //Mat vRImg = rImg.mul(m32fMaskImg) / 255.0; + Mat vDMat0 = abs(dMat) / 255.0; + //Mat vBase = baseImg.mul(m32fMaskImg) / 255.0; + Mat vDMat = abs(dMat) / 255.0 / 255.0; + + //Mat vinsideImg = rImg.mul(m32fInsideMaskImg) / 255.0; + //Mat vinsideBase = baseImg.mul(m32fInsideMaskImg) / 255.0; + + // Mat vMask = maskImg / 255.0; + //Mat vMask = testMask / 255.0; +#endif + Mat minmaxScaleMat; + Mat mMask; + if (m_parallelFlag != 1) + { + minmaxScaleMat = minMaxNorm(dMat, 0, 255, m8uMaskImg); + converToType(minmaxScaleMat, CV_8UC1); + mMask = lowerMajorityMask(minmaxScaleMat, m8uMaskImg, 0.98); + printLog("myLog.txt", "finish outside mMask Part"); + } + else + { + minmaxScaleMat = minMaxNorm(dMat, 0, 255, m8uInsideMaskImg); + converToType(minmaxScaleMat, CV_8UC1); + mMask = lowerMajorityMask(minmaxScaleMat, m8uInsideMaskImg, 0.97); + printLog("myLog.txt", "finish inside mMask Part"); + } + + converToType(mMask, CV_32FC1); + mMask /= 255.0; + + dMat = dMat.mul(mMask); + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vDMat1 = abs(dMat) / 255.0 / 255.0; +#endif + Mat vii = mMask.mul(maskImg); + Mat ii = mMask.mul(maskImg) / 255.0; + double s = sum(mMask.mul(maskImg)).val[0]; + double ret = genMatchValue(dMat) / s; + + if (flag == 1) + { + double matchedVal = ret; + ret = penltyCoeff(matchedVal, meanDiameter, diameter, md_diameter, md_height);//×ö³Í·£ + printf("the matched value %f", ret); + } + printLog("myLog.txt", "finish detect, return val"); + return ret; +} + +double ImageCompareModel::scaleMatchValue(double val) const +{ + return val*mMatchValScale; +} + + +ImageCompareModel* ImageCompareModel::scale(float s) +{ + Mat baseImage; + resize(mAlignBaseImg, baseImage, Size(), s, s, INTER_CUBIC); + Mat weightMat; + resize(mWeightMat, weightMat, Size(), s, s, INTER_CUBIC); + + ImageCompareModel* pRet = new ImageCompareModel; + *pRet = *this; + pRet->setBaseImg(baseImage); + pRet->setWeightMat(weightMat); + + //pRet->genMask(); + pRet->genOutterMask(); + +// Mat mask8uImg; +// resize(m8uMaskImg, mask8uImg, Size(), s, s, INTER_NEAREST); +// Mat mask32fImg; +// resize(m32fMaskImg, mask32fImg, Size(), s, s, INTER_NEAREST); +// +// pRet->set8uMaskImg(mask8uImg); +// pRet->set32fMaskImg(mask32fImg); + + return pRet; +} + +ImageCompareModel::RotateMatchResult ImageCompareModel::rotateMatch(const Mat& img, int levelNum /*= 1*/, float angleStep /*= 1.0*/, + float startAngle /*= 0*/, float endAngle /*= 360*/) const +{ +#ifdef _TEST + static int idx = 0; + std::cout << idx << " "; +#endif + RotateData* pData = new RotateData; + vector angleRangeVec; + if (levelNum > 1) + { + MultiScaleImage msi; + msi.setLevelNum(3); + msi.setBaseLevel(img); + msi.genMultiScale(); + ImageCompareData imgCmpData(&msi); + imgCmpData.setStartAngle(startAngle); + imgCmpData.setEndAngle(endAngle); + + mpMultiScaleModel->proc(&imgCmpData); + + *pData = *imgCmpData.getRotateData(); + } + else + { + rotateMatchData(img, pData, angleStep, startAngle, endAngle); + } + +#ifdef _TEST + if(1) + { + //if (idx == 15) + { + RotateData* pTestData = new RotateData; + rotateMatchData(img, pTestData, angleStep, startAngle, endAngle); + +// no need to code for plotting in visual studio later than 2017, +// install the ArrayPlotter extension to see the data distribution +#if (_MSC_VER < 1910) // vs2017 + stringstream ss; + ss << "multi_scale_comp_plot"; + CvPlot::clear(ss.str()); + CvPlot::plot(ss.str(), &(pTestData->mDisValVec[0]), + pTestData->mDisValVec.size()); +#endif + + float baseAngle = pTestData->bestAngle(); + float curAngle = pData->bestAngle(); + std::cout << baseAngle << " " << curAngle << " " << baseAngle - curAngle << std::endl; + delete pTestData; + if (abs(diffAngleRaw(baseAngle, curAngle)) > 1) + { + waitKey(); + } + } + idx++; + } +#endif + + RotateMatchResult rmr; + + if (pData->mDisValVec.empty()) + { + delete pData; + return rmr; + } + else + { + size_t bestIndex = min_element(pData->mDisValVec.begin(), pData->mDisValVec.end()) - pData->mDisValVec.begin(); + + Mat ret = pData->mRImgVec[bestIndex]; + rmr.mBestRImg = ret; + rmr.mBestAngle = pData->bestAngle(); + + delete pData; + + return rmr; + } +} + +void ImageCompareModel::rotateMatchData(const Mat& _img, RotateData* pData, + float angleStep /*= 1.0*/, float startAngle /*= 0*/, float endAngle /*= 360*/) const +{ + //Mat img = _img.clone(); + if (_img.empty()) + return; + Point2f center(_img.cols / 2.0, _img.rows / 2.0); + int nNum = (endAngle - startAngle) / angleStep; + RotateData& data = *pData; + data.init(_img.clone(), center, angleStep, nNum); + data.mStartAngle = startAngle; + data.mEndAngle = endAngle; + + parallel_for_(Range(0, nNum), ImageCompareModelInvoker(this, pData)); +} + +void ImageCompareModel::rotateMatchData(const Mat& img, RotateData* pData, + float angleStep /*= 1.0*/, const vector& angleRangeVec) const +{ + float minDis = FLT_MAX; + RotateData bestData; + for_each(angleRangeVec.begin(), angleRangeVec.end(), [&](const Range& r) + { + float startAngle = r.start; + float endAngle = r.end; + rotateMatchData(img, pData, angleStep, startAngle, endAngle); + float localMinDis = *min_element(pData->mDisValVec.begin(), pData->mDisValVec.end()); + if (localMinDis < minDis) + { + minDis = localMinDis; + bestData = *pData; + } + }); + *pData = bestData; +} + +void ImageCompareModel::rotateMatchData(const Mat& img, RotateData* pData, + const vector& angleRangeVec, float angleStep, Mat& disMat) const +{ + if (angleRangeVec.empty()) + { + return; + } + disMat.create(angleRangeVec.size(), angleRangeVec.front().size(), CV_32FC1); + pData->mRImgVec.resize(angleRangeVec.size()); + pData->mDisValVec.resize(angleRangeVec.size(), FLT_MAX); + double minDis = DBL_MAX; + for (int i = 0; i < angleRangeVec.size(); ++i) + { + Range r = angleRangeVec[i]; + Mat disVec = disMat.row(i); + RotateData rotateData; + float startAngle = r.start; + float endAngle = r.end; + rotateMatchData(img, &rotateData, angleStep, startAngle, endAngle); + std::copy(rotateData.mDisValVec.begin(), rotateData.mDisValVec.end(), + (float*)disVec.data); + double localMinDis, localMaxDis; + Point minPt, maxPt; + minMaxLoc(disVec, &localMinDis, &localMaxDis, &minPt, &maxPt); + if (localMinDis < minDis) + { + minDis = localMinDis; + *pData = rotateData; + } + } +} + +void ImageCompareModel::genCandidateRepeatNums(set& canNums) +{ + canNums.clear(); + + for (int i = 4; i < 25; ++i) + { + canNums.insert(i); + } +} + +bool isValidExtremas(const Mat& vec, const vector& lessExtremaIdxVec, + const vector& moreExtremaIdxVec) +{ + float* pVec = (float*)vec.data; + auto lessIdxIter = lessExtremaIdxVec.begin(); + auto moreIdxIter = moreExtremaIdxVec.begin(); + while (lessIdxIter != lessExtremaIdxVec.end()) + { + auto leftLessIdxIter = lessIdxIter; + auto leftMoreIdxIter = moreIdxIter; + auto rightLessIdxIter = lessIdxIter; + rightLessIdxIter++; + if (rightLessIdxIter == lessExtremaIdxVec.end()) + { + break; + } + auto rightMoreIdxIter = leftMoreIdxIter; + while (*rightLessIdxIter > *rightMoreIdxIter) + { + rightMoreIdxIter++; + } + + float leftLessExtrema = pVec[*leftLessIdxIter]; + float rightLessExtrema = pVec[*rightLessIdxIter]; + float localMaxVal = *std::max_element(pVec + *leftLessIdxIter, + pVec + *rightLessIdxIter + 1); + float valRange = localMaxVal - min(leftLessExtrema, rightLessExtrema); + float valTor = valRange*0.05; + float slope = (rightLessExtrema - leftLessExtrema) / (*rightLessIdxIter - *leftLessIdxIter); + + leftMoreIdxIter++; + while (leftMoreIdxIter != rightMoreIdxIter) + { + float interVal = leftLessExtrema + slope*(*leftMoreIdxIter - *leftLessIdxIter); + float curExtremaVal = pVec[*leftMoreIdxIter]; + if (curExtremaVal < interVal + valTor) + { + return false; + } + leftMoreIdxIter++; + } + + lessIdxIter++; + moreIdxIter = rightMoreIdxIter; + } + + return true; +} + + +int ImageCompareModel::recognizeLowerExtremes(const Mat& vec, + set canNums, int extremeRefineRange /*= 5*/, vector *pExtremaIdxVec /*= 0*/) +{ + unsigned int size = vec.cols; + set validNs; + float* pVec = (float*)vec.data; + + double vecMinVal, vecMaxVal; + minMaxIdx(vec, &vecMinVal, &vecMaxVal); + +// no need to code for plotting in visual studio later than 2017, +// install the ArrayPlotter extension to see the data distribution +#if (_MSC_VER < 1910) // vs2017 +#ifdef DEBUG_VIEW_PLOT + CvPlot::clear("recognizeLowerExtreams"); + CvPlot::plot("recognizeLowerExtreams", (float*)vec.data, vec.cols); +#endif +#endif + + map > repeatNumLocalExtremaIdxMap; + map repeatNumLocalExtremaValMap; + for (auto iter = canNums.rbegin(); iter != canNums.rend(); ++iter) + { + unsigned int n = *iter; + + Mat extremaValVec; + vector extremaIdxVec; + uniformLocalMinExtremas(vec, extremaValVec, extremaIdxVec, + n, extremeRefineRange); + + float* pExtremaVecVec = (float*)extremaValVec.data; + + float curVal, nxtVal; + bool isValid = true; + + Mat preLocalVec; + + for (unsigned int i = 0; i < extremaIdxVec.size() - 1; ++i) + { + unsigned int curIdx = extremaIdxVec[i]; + unsigned int nxtIdx = extremaIdxVec[i + 1]; + curVal = pVec[curIdx]; + nxtVal = pVec[nxtIdx]; + + // no smaller value between two neighbor extremes + if (anyInRange(pVec + curIdx + 1, pVec + nxtIdx, + -FLT_MAX, min(curVal, nxtVal))) + { + isValid = false; + break; + } + + if (!preLocalVec.empty()) + { + Mat curLocalVec = vec.colRange(curIdx, nxtIdx + 1); + if (curLocalVec.size() != preLocalVec.size()) + { + resize(curLocalVec, curLocalVec, preLocalVec.size()); + } + float sim = compareHist(curLocalVec, preLocalVec, CV_COMP_CORREL); + if (sim < 0) + { + isValid = false; + break; + } + } + + preLocalVec = vec.colRange(curIdx, nxtIdx + 1); + } + if (!isValid) + { + continue; + } + + set nToBeErased; + for_each(validNs.begin(), validNs.end(), [&](int preN) + { + if (preN % n != 0 || !isValid) + { + return; + } + vector preExtremaIdxVec = repeatNumLocalExtremaIdxMap[preN]; + if (isValidExtremas(vec, extremaIdxVec, preExtremaIdxVec)) + { + nToBeErased.insert(preN); + } + else + { + isValid = false; + } + }); + if (!isValid) + { + continue; + } + if (nToBeErased.size() == validNs.size()) + { + validNs.clear(); + } + else + { + for_each(nToBeErased.begin(), nToBeErased.end(), [&](int toBeErasedN) + { + validNs.erase(toBeErasedN); + }); + } + + validNs.insert(n); + repeatNumLocalExtremaIdxMap[n] = extremaIdxVec; + repeatNumLocalExtremaValMap[n] = extremaValVec; + if (pExtremaIdxVec) + { + *pExtremaIdxVec = extremaIdxVec; + } + } + if (validNs.size() == 0) + { + return -1; + } + if (validNs.size() > 1) + { + return -2; + } + + return *(validNs.begin()); +} + +int ImageCompareModel::recognizeRepeatedLocalExtremas(const Mat& vec, + const set& canNums, int refineRange /*= 5*/, + vector *pIdxVec /*= 0*/) +{ + assert(vec.type() == CV_32FC1); + float* pVecData = (float*)vec.data; + + for_each(canNums.begin(), canNums.end(), [&](int n) + { + Mat localMinExtremaValVec; + vector localMinExtremaIdxVec; + uniformLocalMinExtremas(vec, localMinExtremaValVec, localMinExtremaIdxVec, + n, refineRange); + + float* pExtremaVecVec = (float*)localMinExtremaValVec.data; + + // get locally top 10% values + + }); + + return -1; +} + +int ImageCompareModel::computeRepeatNum(const Mat& _img) +{ + Mat img; + cv::GaussianBlur(_img, img, Size(3, 3), 5.0); + vector vec; + selfRotationSimilarityFeature(img, vec, Point2f(img.cols / 2.0, img.rows / 2.0)); + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat viewImg = img / 255.0 /255.0; + Mat view_Img = _img / 255.0 /255.0; +#endif + + vector normVec(vec); + Mat vecMat(1, vec.size(), CV_32FC1, &(vec[0])); + Mat normVecMat(1, normVec.size(), CV_32FC1, &(normVec[0])); + luffy_base::luffy_imageProc::meanvarnorm(vecMat, normVecMat, 0, 1.0); + +#ifdef DEBUG_VIEW_PLOT + Mat dftVec; + dft(normVec, dftVec); + dftVec = abs(dftVec); + + Mat dVecMat = normVecMat.clone(); + genSobelImage(dVecMat); + Mat dDftVec; + dft(dVecMat, dDftVec); + + Mat ddVecMat = dVecMat.clone(); + genSobelImage(ddVecMat); + Mat ddDftVec; + dft(ddVecMat, ddDftVec); + +// no need to code for plotting in visual studio later than 2017, +// install the ArrayPlotter extension to see the data distribution +#if (_MSC_VER < 1910) // vs2017 + CvPlot::plot("normVec", (float*)&(normVec[0]), normVec.size()); + CvPlot::plot("dVec", (float*)dVecMat.data, dVecMat.cols); + CvPlot::plot("ddVec", (float*)ddVecMat.data, ddVecMat.cols); + CvPlot::plot("ddft", (float*)dDftVec.data, 50); + CvPlot::plot("dft", (float*)dftVec.data, 50); + CvPlot::plot("dddft", (float*)ddDftVec.data, 50); +#endif + + waitKey(); +#endif + + set canNums; + genCandidateRepeatNums(canNums); + return recognizeLowerExtremes(normVecMat, canNums); +} + +int ImageCompareModel::computeRepeatNum() +{ + if (mWeightMat.empty() || mAlignBaseImg.empty()) + { + return 0; + } + + Mat img = mAlignBaseImg.mul(mWeightMat); + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat viewImg = img / 255.0 / 255.0; +#endif + + return computeRepeatNum(img); +} + +void ImageCompareModel::initMultiScaleModel() +{ + if (mpMultiScaleModel) + { + delete mpMultiScaleModel; + } + mpMultiScaleModel = new MultiScaleImageCompareModel; + mpMultiScaleModel->setLevelNum(3); + mpMultiScaleModel->setBaseLevel(this); + mpMultiScaleModel->genMultiScale(); +} + +void ImageCompareModelInvoker::operator()(const cv::Range& range) const +{ + int i0 = range.start; + int i1 = range.end; + assert(abs(i1 - i0) == 1); + m_pModel->parallelDetect(i0, m_pData); +} + +void ImageCompareModel::parallelDetect(int index, void *p) const +{ + RotateData *pData = (RotateData *)p; + Mat t = getRotationMatrix2D(pData->mCenter, pData->angle(index), 1.0); + Mat rImg; + warpAffine(pData->mImgSrc, rImg, t, pData->mImgSrc.size()); + + // need add insideImage base image; + + // .... + Mat imgRes; + if (m_parallelFlag!=1) + { + if (rImg.size() != mAlignBaseImg.size()) + { + resize(rImg, rImg, mAlignBaseImg.size()); + } + imgRes = rImg - mAlignBaseImg; + if (!m32fMaskImg.empty()) + { + if (m32fMaskImg.size() != imgRes.size()) + resize(imgRes, imgRes, m32fMaskImg.size()); + imgRes = imgRes.mul(m32fMaskImg); + if (!mWeightMat.empty()) + { + if (mWeightMat.size() != imgRes.size()) + resize(imgRes, imgRes, mWeightMat.size()); + imgRes.mul(mWeightMat); + } + } + } + else + { + if (rImg.size() != mInSideBaseImg.size()) + { + resize(rImg, rImg, mInSideBaseImg.size()); + } + + imgRes = rImg - mInSideBaseImg; + if (!m32fInsideMaskImg.empty()) + { + if (m32fInsideMaskImg.size() != imgRes.size()) + resize(imgRes, imgRes, m32fInsideMaskImg.size()); + imgRes = imgRes.mul(m32fInsideMaskImg); + if (!mInsideWeightMat.empty()) + { + if (mInsideWeightMat.size() != imgRes.size()) + resize(imgRes, imgRes, mInsideWeightMat.size()); + imgRes.mul(mInsideWeightMat); + } + } + } + + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vRImg, vBaseImg; + vRImg = rImg / 255.0; + vBaseImg = mAlignBaseImg / 255.0; + Mat vImgRes = imgRes / 255.0; + Mat vWeightMat = mWeightMat / 255.0; + Mat viewInsideMat = mInSideBaseImg / 255.0; +#endif + double val = 0; + if(!imgRes.empty()) + val = norm(imgRes); + +#ifdef DEBUG_VIEW_INTERNAL_MAT + vImgRes = imgRes / 255.0; +#endif + pData->mDisValVec[index] = val; + pData->mRImgVec[index] = rImg; +} +double ImageCompareModel::filterTrainImage(const Mat&img) +{ + if (img.empty()) + { + return DBL_MAX; + } + mDisThre = DBL_MAX; + double dis = compare(img, NULL, 3, false); + /*if (dis < mTrueSampleDisMax) + { + dis = mTrueSampleDisMax; + } + double absDiff = abs(dis - mTrueSampleDisMax);*/ + return dis; +} +void ImageCompareModel::weightOptimization(const vector& falseSamples) +{ + if (falseSamples.size() <= 0) + return; + reConMat = mWeightMat.clone(); + vector falseVec; + resizeVecMat(falseSamples, falseVec); + for (int i = 0; i < falseVec.size(); i++) + { + Mat falseSample = falseVec[i].clone(); + preWeightReconstruction(falseSample, reConMat); + } + mWeightMat = reConMat; +} + +void ImageCompareModel::preWeightReconstruction(Mat img, Mat &reConImg) +{ + if (mAlignBaseImg.size().height <= 0 || mAlignBaseImg.size().width <= 0) + { + //mAlignBaseImg = img.clone(); + return; + } + if (mAlignBaseImg.size() != img.size()) + { + resize(img, img, mAlignBaseImg.size()); + } + + Mat matchImg = img.clone(); + preProcessImage(matchImg, Mat()); + + if (!mpMultiScaleModel) + { + initMultiScaleModel(); + } + RotateMatchResult rmr = rotateMatch(matchImg, 1); + Mat rImg = rmr.mBestRImg; + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat ttt = mAlignBaseImg / 255.0; + Mat vRImg = rImg / 255.0; +#endif + + double bestAngle = rmr.mBestAngle; + Mat cmpImg = rotateImage(img, Point2f(img.cols / 2.0, img.rows / 2.0), + bestAngle); + + // remove highlights + Mat hightlightsMask = cmpImg < mHighlightsThreshold; + converToType(hightlightsMask, CV_32FC1); + hightlightsMask /= 255.0; + + Mat unifiedMask = m32fMaskImg.mul(hightlightsMask).mul(mWeightMat); + + preProcessImage(cmpImg, m8uMaskImg, mWeightMat, mTargetMeanVal, + mTargetStddevVal, mHighlightsThreshold); + + cmpImg.setTo(0, cmpImg < 0); + converToType(cmpImg, CV_32FC1); + normSectors_tarImg(cmpImg, unifiedMask, imgCen(cmpImg), 1, + mCompareBaseImg); + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vRotatedImg0 = cmpImg / 255.0; +#endif + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat vRotatedImg1 = mCompareBaseImg / 255.0; +#endif + + cmpImg.setTo(0, cmpImg < 10); + + weightReconstruction(cmpImg, mCompareBaseImg, unifiedMask, reConImg); + +} + +void ImageCompareModel::weightReconstruction(const Mat& rImg, const Mat& baseImg, const Mat& maskImg, Mat &reConImg) +{ + Mat dMat = abs(rImg - baseImg); + Mat test = dMat / 255.0; + Mat norImg = Mat::ones(dMat.size(), dMat.type()) * 255; + dMat = min(dMat, norImg); + Mat w(dMat.size(), dMat.type(), Scalar::all(0)); + dMat.copyTo(w, dMat < 0.5); + w = minMaxNorm(w, 0, 1.0); + for (int i = 0; i < reConImg.rows; i++) + { + float* pData = (float*)reConImg.row(i).data; + float* pWeight = (float*)w.row(i).data; + for (int j = 0; j < reConImg.cols; j++) + { + if (pWeight[j]!=0) + { + //pData[j] *= pWeight[j]; + pWeight[j] = -1 * pow(pWeight[j] - 1, 2) + 1; + pData[j] *= pWeight[j]; + } + } + } +} +double ImageCompareModel::penltyCoeff(double matchedVal, int diameterMean, int targetDiameter, double modelDiamter, double modelHeight) const +{ + int diff = abs(diameterMean - targetDiameter); + double modelDiameterDiff = abs(modelDiamter - realWidth); + double modelHeightDiff = abs(modelHeight - realHeight); + + if (diff >=10) + { + return matchedVal*(diff / 100.0 + 1) * (modelHeightDiff / 100.0 + 1) * (modelDiameterDiff / 100.0 + 1); + } + else + { + return matchedVal; + } +} + +cv::Mat ImageCompareModel::genInsideMask(const Mat& img, Point2f center, float innerR /*= -1*/, float outterR /*= -1*/, int type /*= CV_32FC1*/) +{ + Mat mask(img.size(), CV_8UC1); + mask.setTo(0); + if (innerR == -1) + { + // default is 30 + innerR = img.rows*0.178; + //innerR = img.rows; + } + if (outterR == -1) + { + // default is max radius - 10 + outterR = img.rows*0.425; + } + //outterR = r; + /*Mat img1 = img.clone(); + + img1.convertTo(img1, CV_8UC1); + float r = allocateInnerRadius(img1); + rInner = r;*/ + circle(mask, center, outterR, Scalar(255), -1); + circle(mask, center, innerR, Scalar(0), -1); + if (type != CV_8UC1) + { + converToType(mask, type); + mask /= 255; + } + return mask; +} + +float ImageCompareModel::allocateInnerRadius(cv::Mat subImage) +{ + +// //// find global center +// const int &roughRadius = subImage.cols *0.08; +// const Point center(subImage.cols / 2.0, subImage.rows / 2.0); +// Rect rect(center.x - roughRadius, center.y - roughRadius, 2 * roughRadius, 2 * roughRadius); +// Mat centerMat = subImage(rect); +// Mat test = subImage(rect).clone(); +// genSobelImage(centerMat); +// centerMat.convertTo(centerMat, CV_8UC1); +// Mat bMat = centerMat > 100; +// //cv::threshold(centerMat, bMat, 0, 255, CV_THRESH_OTSU); +// Mat mask(bMat.size(), CV_8UC1); +// mask.setTo(0); +// //circle(mask, center, roughRadius, Scalar(255), -1); +// //cv::Mat bImage = subImage.mul(mask / 255) > 100; +// Mat dilatedImgBin; +// Mat erodeMat; +// dilate(bMat, dilatedImgBin, Mat::ones(3, 3, CV_32FC1)); +// erode(dilatedImgBin, erodeMat, Mat::ones(3, 3, CV_32FC1)); +// closeOper(erodeMat, Mat::ones(1, 3, CV_32FC1)); +// Mat vMat = erodeMat.clone(); +// vector> contours; +// findContours(erodeMat, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); +// for (int i = 0; i < contours.size(); i++) { +// const vector &pt = contours.at(i); +// if (pt.size() < 10) { +// continue; +// } +// Rect rt = boundingRect(pt); +// if (rt.width < 5 || rt.height < 5) { +// continue; +// } +// drawContours(mask, contours, i, Scalar::all(255), -1); +// } +// +// using namespace luffy_base; +// Mat hit; vector pts; +// luffy_hit::firstHit4Circle(mask, hit, pts, Point(mask.cols / 2, mask.rows / 2), 0, mask.cols / 2, 360, luffy_hit::emHitOut2In); +// luffy_imageProc::RansacParam rs(0.01, 1, 100, 80, 100);//0421 +// vector pts2 = luffy_imageProc::fitModelbyRansac(pts, luffy_imageProc::emModelCircle, &rs); +// float fRadius; +// Point2f ptCenter; +// bool bFind = luffy_imageProc::lsCircleFit(pts2, fRadius, ptCenter); +//#ifdef _DEBUG +// Mat insideMat(mask.size(), mask.type(), Scalar::all(0)); +// int offset = 2; +// cv::circle(insideMat, ptCenter, fRadius, Scalar(1), -1); +// Mat ddst = insideMat.mul(test); +//#endif +// const Point globalCenter(ptCenter.x + center.x - roughRadius, ptCenter.y + center.y - roughRadius); +// +// +// +// //// find minR +// cv::Mat binaryImg = subImage > 25; +// const int innerRadius = binaryImg.rows*0.05; +// const int outterRadius = binaryImg.cols*0.22; +// Mat mmask(binaryImg.size(), CV_8UC1); +// mmask.setTo(0); +// circle(mmask, globalCenter, outterRadius, Scalar(1), -1); +// Mat dilatedImgBin1; +// Mat erodeMat1; +// dilate(binaryImg, dilatedImgBin1, Mat::ones(9, 9, CV_32FC1)); +// erode(dilatedImgBin1, erodeMat1, Mat::ones(9, 9, CV_32FC1)); +// openOper(erodeMat1, Mat::ones(1, 13, CV_32FC1)); +// circle(erodeMat1, globalCenter, innerRadius, Scalar(255), -1); +// Mat tarMat = mmask.mul(erodeMat1) > 0; +// +// vector maxCon; +// vector> m_contours; +// findContours(tarMat, m_contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); +// maxCon = m_contours.front(); +// for (const vector& contour : m_contours) +// { +// if (contour.size() > maxCon.size()) +// { +// maxCon = contour; +// } +// } +// float minDis = FLT_MAX; +// Point initialPoint = maxCon.front(); +// for (int i = 0; i < maxCon.size(); i++) +// { +// const Point& p = maxCon[i]; +// float dis = pointDis(p, globalCenter); +// if (dis < minDis) +// { +// minDis = dis; +// initialPoint = p; +// } +// } +//#ifdef _DEBUG +// Mat m_Mat(tarMat.size(), tarMat.type(), Scalar::all(0)); +// //int offset = 2; +// cv::circle(m_Mat, globalCenter, minDis, Scalar(1), -1); +// Mat d_dst = m_Mat.mul(subImage); +//#endif +// vector rst; +// rst.resize(3); +// rst[0] = minDis - 3; +// rst[1] = globalCenter.x; +// rst[2] = globalCenter.y; +// return rst; + if (subImage.size() != mAlignBaseImg.size()) + { + resize(subImage, subImage, mAlignBaseImg.size()); + } + + cv::Mat binaryImg = subImage > 30; + const int innerRadius = binaryImg.rows*0.05; + const int outterRadius = binaryImg.cols*0.22; + const Point center(binaryImg.cols / 2.0, binaryImg.rows / 2.0); + Mat mask(binaryImg.size(), CV_8UC1); + mask.setTo(0); + circle(mask, center, outterRadius, Scalar(1), -1); + Mat dilatedImgBin; + Mat erodeMat; + dilate(binaryImg, dilatedImgBin, Mat::ones(9, 9, CV_32FC1)); + erode(dilatedImgBin, erodeMat, Mat::ones(9, 9, CV_32FC1)); + openOper(erodeMat, Mat::ones(1, 13, CV_32FC1)); + circle(erodeMat, center, innerRadius, Scalar(255), -1); + Mat tarMat = mask.mul(erodeMat) > 0; + + vector maxCon; + vector> contours; + findContours(tarMat, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); + maxCon = contours.front(); + for (const vector& contour : contours) + { + if (contour.size() > maxCon.size()) + { + maxCon = contour; + } + } + float minDis = FLT_MAX; + Point initialPoint = maxCon.front(); + for (int i = 0; i < maxCon.size(); i++) + { + const Point& p = maxCon[i]; + float dis = pointDis(p, center); + if (dis < minDis) + { + minDis = dis; + initialPoint = p; + } + } +#ifdef _DEBUG + Mat insideMat(tarMat.size(), tarMat.type(), Scalar::all(0)); + int offset = 2; + cv::circle(insideMat, center, minDis - offset, Scalar(1), -1); + Mat ddst = insideMat.mul(subImage); +#endif + return minDis - 2; +} + +float ImageCompareModel::allocateRadius(cv::Mat subImage) +{ + +// //// find global center +// const int &roughRadius = subImage.cols *0.08; +// const Point center(subImage.cols / 2.0, subImage.rows / 2.0); +// Rect rect(center.x - roughRadius, center.y - roughRadius, 2 * roughRadius, 2*roughRadius); +// Mat centerMat = subImage(rect); +// Mat test = subImage(rect).clone(); +// genSobelImage(centerMat); +// centerMat.convertTo(centerMat, CV_8UC1); +// Mat bMat = centerMat > 100; +// //cv::threshold(centerMat, bMat, 0, 255, CV_THRESH_OTSU); +// Mat mask(bMat.size(), CV_8UC1); +// mask.setTo(0); +// //circle(mask, center, roughRadius, Scalar(255), -1); +// //cv::Mat bImage = subImage.mul(mask / 255) > 100; +// Mat dilatedImgBin; +// Mat erodeMat; +// dilate(bMat, dilatedImgBin, Mat::ones(3, 3, CV_32FC1)); +// erode(dilatedImgBin, erodeMat, Mat::ones(3, 3, CV_32FC1)); +// closeOper(erodeMat, Mat::ones(1, 3, CV_32FC1)); +// Mat vMat = erodeMat.clone(); +// vector> contours; +// findContours(erodeMat, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); +// for (int i = 0; i < contours.size(); i++) { +// const vector &pt = contours.at(i); +// if (pt.size() < 5) { +// continue; +// } +// Rect rt = boundingRect(pt); +// if (rt.width < 5 || rt.height < 5) { +// continue; +// } +// drawContours(mask, contours, i, Scalar::all(255), -1); +// } +// +// using namespace luffy_base; +// Mat hit; vector pts; +// luffy_hit::firstHit4Circle(mask, hit, pts, Point(mask.cols / 2, mask.rows / 2), 0, mask.cols / 2, 360, luffy_hit::emHitOut2In); +// luffy_imageProc::RansacParam rs(0.01, 1, 100, 80, 100);//0421 +// vector pts2 = luffy_imageProc::fitModelbyRansac(pts, luffy_imageProc::emModelCircle, &rs); +// float fRadius; +// Point2f ptCenter; +// bool bFind = luffy_imageProc::lsCircleFit(pts2, fRadius, ptCenter); +//#ifdef _DEBUG +// Mat insideMat(mask.size(), mask.type(), Scalar::all(0)); +// //int offset = 2; +// cv::circle(insideMat, ptCenter, fRadius, Scalar(1), -1); +// Mat ddst = insideMat.mul(test); +//#endif +// const Point globalCenter(ptCenter.x + center.x - roughRadius, ptCenter.y + center.y - roughRadius); +// +// +// +// //// find minR +// cv::Mat binaryImg = subImage > 25; +// const int innerRadius = binaryImg.rows*0.05; +// const int outterRadius = binaryImg.cols*0.22; +// Mat mmask(binaryImg.size(), CV_8UC1); +// mmask.setTo(0); +// circle(mmask, globalCenter, outterRadius, Scalar(1), -1); +// Mat dilatedImgBin1; +// Mat erodeMat1; +// dilate(binaryImg, dilatedImgBin1, Mat::ones(9, 9, CV_32FC1)); +// erode(dilatedImgBin1, erodeMat1, Mat::ones(9, 9, CV_32FC1)); +// openOper(erodeMat1, Mat::ones(1, 13, CV_32FC1)); +// circle(erodeMat1, globalCenter, innerRadius, Scalar(255), -1); +// Mat tarMat =mmask.mul(erodeMat1) > 0; +// +// vector maxCon; +// vector> m_contours; +// findContours(tarMat, m_contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); +// maxCon = m_contours.front(); +// for (const vector& contour : m_contours) +// { +// if (contour.size() > maxCon.size()) +// { +// maxCon = contour; +// } +// } +// float minDis = FLT_MAX; +// Point initialPoint = maxCon.front(); +// for (int i = 0; i < maxCon.size(); i++) +// { +// const Point& p = maxCon[i]; +// float dis = pointDis(p, globalCenter); +// if (dis < minDis) +// { +// minDis = dis; +// initialPoint = p; +// } +// } +//#ifdef _DEBUG +// Mat m_Mat(tarMat.size(), tarMat.type(), Scalar::all(0)); +// //int offset = 2; +// cv::circle(m_Mat, globalCenter, minDis, Scalar(1), -1); +// Mat d_dst = m_Mat.mul(subImage); +//#endif +// //return minDis - 2; +// vector rst; +// rst.resize(3); +// rst[0] = minDis - 3; +// rst[1] = globalCenter.x; +// rst[2] = globalCenter.y; +// return rst; + + if (subImage.size() != mAlignBaseImg.size()) + { + resize(subImage, subImage, mAlignBaseImg.size()); + } + cv::Mat binaryImg = subImage > 30; + const int innerRadius = binaryImg.rows*0.05; + const int outterRadius = binaryImg.cols*0.22; + const Point center(binaryImg.cols / 2.0, binaryImg.rows / 2.0); + Mat mask(binaryImg.size(), CV_8UC1); + mask.setTo(0); + circle(mask, center, outterRadius, Scalar(1), -1); + Mat dilatedImgBin; + Mat erodeMat; + dilate(binaryImg, dilatedImgBin, Mat::ones(9, 9, CV_32FC1)); + erode(dilatedImgBin, erodeMat, Mat::ones(9, 9, CV_32FC1)); + openOper(erodeMat, Mat::ones(1, 13, CV_32FC1)); + circle(erodeMat, center, innerRadius, Scalar(255), -1); + Mat tarMat = mask.mul(erodeMat) > 0; + + vector maxCon; + vector> contours; + findContours(tarMat, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); + maxCon = contours.front(); + for (const vector& contour : contours) + { + if (contour.size() > maxCon.size()) + { + maxCon = contour; + } + } + float minDis = FLT_MAX; + Point initialPoint = maxCon.front(); + for (int i = 0; i < maxCon.size(); i++) + { + const Point& p = maxCon[i]; + float dis = pointDis(p, center); + if (dis < minDis) + { + minDis = dis; + initialPoint = p; + } + } + #ifdef _DEBUG + Mat insideMat(tarMat.size(), tarMat.type(), Scalar::all(0)); + int offset = 2; + cv::circle(insideMat, center, minDis - offset, Scalar(1), -1); + Mat ddst = insideMat.mul(subImage); + #endif + return minDis - 2; +} + +void ImageCompareModel::genOutterMask() +{ + m32fMaskImg = genMask(mAlignBaseImg, Point2f(mAlignBaseImg.cols / 2.0, mAlignBaseImg.rows / 2.0)); + m8uMaskImg = genMask(mAlignBaseImg, Point2f(mAlignBaseImg.cols / 2.0, mAlignBaseImg.rows / 2.0), + -1.0, -1.0, CV_8UC1); +} + +void ImageCompareModel::resizeVecMat(vector srcVec, vector &dstVec) +{ + if (srcVec.size() <= 0) + return; + dstVec.resize(srcVec.size()); + int nWidth = ((int)((float)srcVec.front().cols / COLS_SCALE / 4)) * 4; + for (int i = 0; i < srcVec.size(); i++) + { + const Mat& mat = srcVec[i]; + Mat dstMat; + cv::resize(mat, dstMat, cv::Size(nWidth, nWidth)); + dstVec[i] = dstMat; + } +} + +void ImageCompareModel::resizeMat(Mat src, Mat &dst) +{ + int nWidth = ((int)((float)src.cols / COLS_SCALE / 4)) * 4; + cv::resize(src, dst, cv::Size(nWidth, nWidth)); +} + +void ImageCompareModel::printLog(string root, string log, double n) const +{ +#ifdef DEBUG_VIEW_INTERNAL_MAT + ofstream write; + write.open(root, ios::app); + time_t seconds = time(NULL); + struct tm *p; + p = localtime(&seconds); + char strTime[100] = { 0 }; + sprintf(strTime, "%02d-%02d %02d:%02d:%02d:", 1 + p->tm_mon, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec); + std::ostringstream str; + str << log; + if (n != 0) + { + str << n; + } + write << "time:" << string(strTime); + write << str.str() << "\n"; + write.close(); +#endif +} + diff --git a/molunCar/ImageCompareModel.h b/molunCar/ImageCompareModel.h new file mode 100644 index 0000000..ed4e7a9 --- /dev/null +++ b/molunCar/ImageCompareModel.h @@ -0,0 +1,380 @@ +/*! \file ImageCompareModel.h + + Copyright (C) 2014 Hangzhou Leaper. + + Created by bang.jin at 2017/02/04. + +*/ + +#pragma once +#include +#include +#include +#include +#include +#include +#include "MultiScaleObj.h" +#include "CircleDetector.h" +#define COLS_SCALE (float)(1200.0/ 416.0) +using std::set; +using std::map; +using std::fstream; +using std::string; +using namespace cv; + +class MultiScaleImageCompareModel; +class ImageCompareModel +{ +public: + class RotateMatchResult + { + public: + Mat mBestRImg; + double mBestAngle; + }; + class RotateData + { + public: + RotateData() {}; + RotateData(const Mat &img, Point pt, double dAngleStep, int size) + : mImgSrc(img) + , mStartAngle(0) + , mEndAngle(360) + { + init(img, pt, dAngleStep, size); + }; + void init(const Mat& img, Point pt, double dAngleStep, int size) + { + mImgSrc = img; + mStartAngle = 0; + mEndAngle = 360.0; + mRImgVec.clear(); + mRImgVec.resize(size); + mDisValVec.clear(); + mDisValVec.resize(size, FLT_MAX); + mCenter = pt; + mAngleStep = dAngleStep; + } + double angle(int index) + { + return index*mAngleStep + mStartAngle; + } + double bestAngle() + { + if (mDisValVec.empty()) + { + return -DBL_MAX; + } + size_t bestIndex = min_element(mDisValVec.begin(), + mDisValVec.end()) - mDisValVec.begin(); + double bestAngle = angle(bestIndex); + return bestAngle; + } + Mat bestRImg() + { + if (mRImgVec.empty()) + { + return Mat(); + } + size_t bestIndex = min_element(mDisValVec.begin(), + mDisValVec.end()) - mDisValVec.begin(); + return mRImgVec[bestIndex]; + } + double mAngleStep; + Mat mImgSrc; + Point mCenter; + vector mRImgVec; + vector mDisValVec; + + float mStartAngle, mEndAngle; + }; + +public: + static cv::Point2f refineCircleCen(const Mat& img, Point2f cen); + static Mat genWeightImage(const Mat& img, Point2f center, float innerR = -1, + float outterR = -1); + static void selfRotationSimilarityFeature(const Mat& img, + vector& vec, Point2f cen = Point2f(-1, -1)); + Mat genMask(const Mat& img, Point2f center, float innerR = -1, + float outterR = -1, + int type = CV_32FC1); + + Mat genInsideMask (const Mat& img, Point2f center, float innerR = -1, + float outterR = -1, + int type = CV_32FC1); + + static void genCandidateRepeatNums(set& canNums); + static int computeRepeatNum(const Mat& img); + static int recognizeLowerExtremes(const Mat& vec, set canNums, int extremeRefineRange = 5, vector *pExtemeIdxVec = 0); + static int recognizeRepeatedLocalExtremas(const Mat& vec, const set& canNums, int extremeRefineRange = 5, vector *pExtremeIdxVec = 0); + static void genUniformSepIdx(int num, int startIdx, int endIdx, vector& cenVec); + static void genAngleRanges(vector cenVec, vector& rangeVec, int localRange); + static void genCandidateAngleRanges(vector disVec, + float angleStep, vector& rangeVec); + static void genCandidateAngleRanges(const Mat& disMat, float angleStep, + vector& rangeVec, float rangeScale = 1); + static void selfRotateMin(const Mat& img, Mat& weightMat, int repeatNum); + float allocateInnerRadius(cv::Mat subImage); + float allocateRadius(cv::Mat subImage); + void resizeVecMat(vector srcVec, vector &dstVec); + void resizeMat(Mat src, Mat &dst); +public: + ImageCompareModel() : + mMatchValScale(1.0), + mTargetMeanVal(127), + mTargetStddevVal(50), + mHighlightsThreshold(256), + mRepeatNum(0), + mName("unnamed"), + mDisThre(DBL_MAX), + mTrueSampleDisMin(DBL_MAX), + mTrueSampleDisMax(DBL_MIN), + mTrueSampleDisMean(-1), + mTrueSampleDisStddev(-1), + mIsEnableCache(false), + mpMultiScaleModel(NULL), + meanDiameter(0), + realWidth(0), + realHeight(0), + rInner(DBL_MAX) + {}; + ImageCompareModel(const ImageCompareModel& model) + : mAlignBaseImg(model.getBaseImg().clone()) + , mWeightMat(model.getWeightMat().clone()) + , mMatchValScale(model.getMatchValScale()) + , mInSideBaseImg(model.getInsideBaseImg().clone()) + , mInsideWeightMat(model.getInsideWeightImg().clone()) + { + genMask(); + } + ~ImageCompareModel() {}; + + void printInfo(); + void saveImages(string dirPath); + bool save2file(string filePath); + bool readFromFile(string filePath); + + void operator = (const ImageCompareModel& model) + { + mAlignBaseImg = model.getBaseImg().clone(); + mWeightMat = model.getWeightMat().clone(); + mInSideBaseImg = model.getInsideBaseImg().clone(); + mInsideWeightMat = model.getInsideWeightImg().clone(); + mMatchValScale = model.getMatchValScale(); + mTargetMeanVal = model.getTargetMeanVal(); + mTargetStddevVal = model.getTargetStddevVal(); + mName = model.getName(); + mTrueSampleDisStddev = model.getDisStdDev(); + mTrueSampleDisMean = model.getDisMean(); + mTrueSampleDisMax = model.getDisMax(); + mTrueSampleDisMin = model.getDisMin(); + meanDiameter = model.getMeanDiamter(); + rInner = model.getInnerR(); + } + + void genMask(); + void genOutterMask(); + void preProcessImage(Mat& img, Mat &insideImg) const; + void preProcessImage(Mat& img, const Mat& mask, double dstMean, double dstStddev, int highlightsThreshold) const; + void preProcessImage(Mat& img, const Mat& mask, const Mat& weightMat, double dstMean, + double dstStddev, int highlightsThreshold) const; + void preProcessImage0(Mat& img) const; + void weightOptimization(const vector& falseSamples); + //! ½«Ò»¸öͼÏñºÍ±ê׼ͼmBaseImg½øÐÐÐýתƥÅ䣬ÔÙÏà¼õÇóµÃ²îÒìÖµ + /*! + \param Mat img ÊäÈëͼÏñ + \param Mat * pRImg ÊäÈëͼÏñµÄÐýתƥÅä½á¹û + \param bool isFilterSize ÊÇ·ñ¸ù¾ÝͼÏñ³ß´ç¹ýÂË + \return double ²îÒìÖµ + \see compare(Mat, Mat, Mat*, Mat*) + */ + double compare(Mat srcImage, Mat* pRImg = NULL, int levelNum = 1, bool isFilterSize = true, int flag = 0, + double md_diameter = 0, double md_height = 0); + + //! ѵÁ·µÃµ½±ê׼ͼÏñmBaseImg + /*! + \param const vector & imgVec ÊôÓÚͬÀàµÄѵÁ·Ñù±¾Í¼ + \return void + */ + void train(const vector& vec); + void calculateAllParams(const vector& imgVec); + void trueSampleWeightRecon(const vector& resizedVec, const vector& resizedCenterVec, vector rmrVec, + vector rmrVecInside); + void trueWeightRecon(const Mat& rImg, const Mat& baseImg, const Mat& insideRimg, const Mat& insideBaseImg); + void weightMapping(const Mat& weight, double maxVal, const Mat& i_mData, double i_maxVal); + //void falseWeightmapping(Mat &img); + double descendFunction(double pixVal, double maxVal); + double penltyCoeff(double matchedVal, int diameterMean, int targetDiameterm, double modelDiamter, double modelHeight) const; + //! ¸ù¾Ý¸ºÑù±¾¼ÆËã²îÒìãÐÖµ£¬ÓÃÓÚÅжÏÈÎÒâÒ»¸öеÄͼÏñÊÇ·ñÊôÓÚ¸ÃÀà±ð¡£ + /*! + \param const vector & imgVec ²»ÊôÓÚ¸ÃÀàµÄѵÁ·Ñù±¾Í¼ + \return void + */ + void computeDisThre(const vector& imgVec, double* pMinDis = NULL, double md_diameter = 0, double md_height = 0); + + double filterTrainImage(const Mat &img); + + //! ²¢ÐмÆËãrotate + /*! + \param int index µ±Ç°²¢ÐÐindex + \param void *p Êý¾ÝÖ¸Õë + */ + void parallelDetect(int index, void *p) const; + + + //! »ñÈ¡±ê׼ͼÏñ + /*! + \return cv::Mat ±ê׼ͼÏñ + */ + Mat getInsideBaseImg() const{ return mInSideBaseImg; } + Mat getInsideWeightImg() const{ return mInsideWeightMat; } + float getInnerR() const { return rInner; } + + Mat getBaseImg() const { return mAlignBaseImg; } + + void setBaseImg(const Mat& img) { mAlignBaseImg = img; } + + Mat getWeightMat() const { return mWeightMat; } + void setWeightMat(Mat val) { mWeightMat = val; } + + double getMatchValScale() const { return mMatchValScale; } + void setMatchValScale(double val) { mMatchValScale = val; } + + int getTargetStddevVal() const { return mTargetStddevVal; } + void setTargetStddevVal(int val) { mTargetStddevVal = val; } + int getTargetMeanVal() const { return mTargetMeanVal; } + void setTargetMeanVal(int val) { mTargetMeanVal = val; } + + int getRepeatNum() const { return mRepeatNum; } + void setRepeatNum(int val) { mRepeatNum = val; } + + string getName() const { return mName; } + void setName(string val) { mName = val; } + + double getDisStdDev() const { return mTrueSampleDisStddev; } + double getDisMean() const { return mTrueSampleDisMean; } + double getDisMin() const { return mTrueSampleDisMin; } + double getDisMax() const { return mTrueSampleDisMax; } + + double getFalseSampleMinDis() const { return mFalseSampleMinDis; } + double getMeanDiamter() const { return meanDiameter; } + //! »ñÈ¡×î½üÒ»´ÎÖ´ÐÐreadFromFileʱÊäÈëµÄÎļþ·¾¶ + /*! + \return string Îļþ·¾¶ + */ + string getFilePath() const { return mFilePath; } + + double getDisThre() const { return mDisThre; } + void setDisThre(double val) { mDisThre = val; } + + //! ÆôÓûº´æ¡£»º´æ»á½«Ã¿´Îµ÷ÓÃcompare½Ó¿ÚÊäÈëµÄimg²ÎÊýµÄdataÖ¸Õë×÷Ϊkey´æ´¢ + // ²îÒìÖµ¡£ + /*! + \return void + */ + void setIsEnableCache(bool val) { mIsEnableCache = val; } + bool getIsEnableCache() const { return mIsEnableCache; } + + void clearDisCache() { mDisCache.clear(); } + + ImageCompareModel* scale(float s); + + RotateMatchResult rotateMatch(const Mat& img, int levelNum = 1, float angleStep = 1.0, + float startAngle = 0, float endAngle = 360) const; + void rotateMatchData(const Mat& img, RotateData* pData, const vector& angleRangeVec, float angleStep, + Mat& disMat) const; + void rotateMatchData(const Mat& img, RotateData* pData, float angleStep = 1.0, + float startAngle = 0, float endAngle = 360) const; + void rotateMatchData(const Mat& img, RotateData* pData, float angleStep, + const vector& angleRangeVec) const; + + cv::Mat getM8uMaskImg() const { return m8uMaskImg; } + void setM8uMaskImg(cv::Mat val) { m8uMaskImg = val; } + cv::Mat getM32fMaskImg() const { return m32fMaskImg; } + void setM32fMaskImg(cv::Mat val) { m32fMaskImg = val; } + + int computeRepeatNum(); + + void setRealWidth(int val){ realWidth = val; } + void setRealHeight(int val){ realHeight = val; } + int getRealWidth(){ return realWidth; } + int getRealHeight(){ return realHeight; } + void printLog(string root, string log, double n = 0) const; + +protected: + //! £¨ÉÐδÍê³É£©½«Á½¸öͼÏñ·Ö±ðºÍ±ê׼ͼmBaseImg½øÐÐÐýתƥÅ䣬ÔÙÏà¼õÇóµÃ²îÒìÖµ + /*! + \param Mat img0 ´ý±È½ÏµÄͼÏñimg0 + \param Mat img1 ´ø±È½ÏµÄͼÏñimg1 + \param Mat * pRImg0 ½«img0ºÍ±ê׼ͼmBaseImgÐýתƥÅäµÄ½á¹ûrimg0 + \param Mat * pRImg1 ½«img1ºÍ±ê׼ͼmBaseImgÐýתƥÅäµÄ½á¹ûrimg1 + \return double ²îÒìÖµ£¬È¡Öµ´óСΪmMatchValScale*norm(rimg0 - rimg1)/(cols*rows) + */ + double compare(Mat img0, Mat img1, Mat* pRImg0 = NULL, Mat* pRImg1 = NULL); + + void setFilePath(string val) { mFilePath = val; } + + double genMatchValue(const Mat& rImg, const Mat& baseImg, const Mat& maskImg, int flag, int diameter, double md_diameter, double md_height) const; + double genMatchValue(const Mat& dMat) const; + double scaleMatchValue(double val) const; + void weightReconstruction(const Mat& rImg, const Mat& baseImg, const Mat& maskImg, Mat &reConImg); + void preWeightReconstruction(Mat img, Mat &reConImg); + + void setFalseSampleMinDis(double iVal) { mFalseSampleMinDis = iVal; } + + double mMatchValScale; + int mTargetMeanVal, mTargetStddevVal, mHighlightsThreshold; + int mRepeatNum; + + Mat mAlignBaseImg, mCompareBaseImg; + Mat mWeightMat; + Mat m32fMaskImg, m8uMaskImg; + Mat mDisMat; + Mat reConMat; + Mat falseReConMat; + Mat insideWeightRecon; + Mat innerTempl; + double mDisThre{ DBL_MAX }; + string mName; + double falseMinDis; + int meanDiameter; + int realWidth; + int realHeight; + // INSIDE PART + Mat mInSideBaseImg, mInsideCompareBaseImg; + Mat mInsideWeightMat; + Mat m32fInsideMaskImg, m8uInsideMaskImg; + float rInner; + float x_aix; + float y_aix; + + + + // some training data + double mTrueSampleDisStddev, mTrueSampleDisMean, mTrueSampleDisMax{DBL_MAX}, mTrueSampleDisMin; + double mFalseSampleMinDis; + + // temp data + string mFilePath; + void initMultiScaleModel(); + MultiScaleImageCompareModel* mpMultiScaleModel; + + // cache + bool mIsEnableCache; + map mDisCache; +public: + static int m_parallelFlag; +private: +}; + +class ImageCompareModelInvoker : public cv::ParallelLoopBody +{ +public: + ImageCompareModelInvoker(const ImageCompareModel *pModel, void* pData) + : m_pModel(pModel), m_pData(pData) + {} +private: + const ImageCompareModel *m_pModel; + void *m_pData; + virtual void operator() (const cv::Range& range) const; +}; \ No newline at end of file diff --git a/molunCar/MultiScaleImageCompareModel.cpp b/molunCar/MultiScaleImageCompareModel.cpp new file mode 100644 index 0000000..12685c4 --- /dev/null +++ b/molunCar/MultiScaleImageCompareModel.cpp @@ -0,0 +1,146 @@ +#include "MultiScaleImageCompareModel.h" +#include "ImageCompareModel.h" +// no need to code for plotting in visual studio later than 2017, +// install the ArrayPlotter extension to see the data distribution +#if (_MSC_VER < 1910) // vs2017 +#include "../3rd/cvplot/cvplot.h" +#endif + + +void MultiScaleImageCompareModel::setBaseLevel(const ImageCompareModel* pModel) +{ + ImageCompareModel* p = new ImageCompareModel; + *p = *pModel; + p->genOutterMask(); + clear(); + mMultiScaleModels.resize(1); + mMultiScaleModels[0] = p; +} + +void MultiScaleImageCompareModel::genMultiScale() +{ + resetMaxLevel(); + while (getLevel() >= 1) + { + if (mMultiScaleModels.size()>1) + { + ImageCompareModel* pScaledModel = mMultiScaleModels.back()->scale(mScaleStep); + mMultiScaleModels.push_back(pScaledModel); + downLevel(); + } + else + downLevel(); + } +} + +const ImageCompareModel* MultiScaleImageCompareModel::getCmpModel(int level) +{ + if (level >= 0 && level < mLevelNum) + { + return mMultiScaleModels[level]; + } + else + { + return NULL; + } +} + +void MultiScaleImageCompareModel::clear() +{ + for_each(mMultiScaleModels.begin(), mMultiScaleModels.end(), [&](ImageCompareModel* p) + { + delete p; + }); +} + +void MultiScaleImageCompareModel::proc(void *pData) +{ + ImageCompareData* pCmpData = (ImageCompareData*)pData; + MultiScaleImage* pMSImg = pCmpData->getMSI(); + resetMaxLevel(); + float startAngle = pCmpData->getStartAngle(); + float endAngle = pCmpData->getEndAngle(); + float angleRange = (endAngle - startAngle) / 4.0; + vector angleRangeVec(1, Range(0, 360)); + + ImageCompareModel::RotateData* pRotateData = pCmpData->getRotateData(); + int nlevel = getLevel(); + while (nlevel >= 0) + { + nlevel = getLevel(); + if (nlevel<0) + break; + const ImageCompareModel* pModel = NULL; + if (nlevel >= mMultiScaleModels.size()) + pModel = mMultiScaleModels[0]; + else + pModel = mMultiScaleModels[nlevel]; + assert(pModel); + Mat img = pMSImg->getImg(nlevel); + assert(!img.empty()); + + +#ifdef DEBUG_VIEW_INTERNAL_MAT + Mat viewImg = img / 255.0; + imshow("img", viewImg); +#endif + + if (angleRangeVec.size() == 1 && angleRangeVec.front().size() >= 360) + { + pModel->rotateMatchData(img, pRotateData, 1.0, startAngle, endAngle); + ImageCompareModel::genCandidateAngleRanges(pRotateData->mDisValVec, 1, angleRangeVec); + if (angleRangeVec.empty()) + { + pRotateData->mDisValVec.clear(); + pRotateData->mRImgVec.clear(); + angleRangeVec.resize(1); + angleRangeVec[0] = Range(0, 360); + } + } + else + { + Mat disMat; + pModel->rotateMatchData(img, pRotateData, angleRangeVec, 1.0, disMat); + ImageCompareModel::genCandidateAngleRanges(disMat, 1, angleRangeVec, 0.5); +// pModel->rotateMatchData(img, pRotateData, 1.0, angleRangeVec); +// +// double bestAngle = pRotateData->bestAngle(); +// angleRangeVec.clear(); +// Range r; +// r.start = bestAngle - angleRange; +// r.end = bestAngle + angleRange; +// angleRangeVec.push_back(r); + } + + +#ifdef DEBUG_VIEW_INTERNAL_MAT +// Mat viewBestRImg = pRotateData->bestRImg() / 255.0; +// imshow("bestRImg", viewBestRImg); +#endif + +// no need to code for plotting in visual studio later than 2017, +// install the ArrayPlotter extension to see the data distribution +#if (_MSC_VER < 1910) // vs2017 +#ifdef DEBUG_VIEW_PLOT + { + stringstream ss; + ss << "multi_scale_comp_plot" << getLevel(); + CvPlot::clear(ss.str()); + CvPlot::plot(ss.str(), &(pRotateData->mDisValVec[0]), + pRotateData->mDisValVec.size()); + } + { + stringstream ss; + ss << "multi_scale_comp_plot_0_360_" << getLevel(); + CvPlot::clear(ss.str()); + ImageCompareModel::RotateData testRotateData = *pRotateData; + pModel->rotateMatchData(img, &testRotateData, 1.0, startAngle, endAngle); + CvPlot::plot(ss.str(), &(testRotateData.mDisValVec[0]), + testRotateData.mDisValVec.size()); + } +#endif +#endif + + downLevel(); + } +} diff --git a/molunCar/MultiScaleImageCompareModel.h b/molunCar/MultiScaleImageCompareModel.h new file mode 100644 index 0000000..a59c780 --- /dev/null +++ b/molunCar/MultiScaleImageCompareModel.h @@ -0,0 +1,74 @@ +/*! \file MultiScaleImageCompareModel.h + \brief A brief file description. + + A more elaborated file description. + + Created: 2017/02/24, author: bang.jin. +*/ + +#ifndef __MultiScaleImageCompareModel_h_ +#define __MultiScaleImageCompareModel_h_ + +#include "MultiScaleObj.h" +#include +#include "ImageCompareModel.h" + +using std::vector; + +class MultiScaleImageCompareModel : public MultiScaleObj +{ +public: + MultiScaleImageCompareModel(int levelNum = 2, float scaleStep = 0.5) + : MultiScaleObj(levelNum, scaleStep) + { + + } + ~MultiScaleImageCompareModel() + { + clear(); + } + void setBaseLevel(const ImageCompareModel* pModel); + void genMultiScale(); + const ImageCompareModel* getCmpModel(int level); + void clear(); + void proc(void *pData); + +protected: + vector mMultiScaleModels; + +private: +}; + + +class ImageCompareData +{ +public: + ImageCompareData(MultiScaleImage* pMSI) + : mpMSI(pMSI) + , mStartAngle(0) + , mEndAngle(360) + , mAngleRange(90) + {} + MultiScaleImage* getMSI() const { return mpMSI; } + void setMSI(MultiScaleImage* val) { mpMSI = val; } + + float getStartAngle() const { return mStartAngle; } + void setStartAngle(float val) { mStartAngle = val; } + float getEndAngle() const { return mEndAngle; } + void setEndAngle(float val) { mEndAngle = val; } + float getAngleRange() const { return mAngleRange; } + void setAngleRange(float val) { mAngleRange = val; } + + ImageCompareModel::RotateData* getRotateData() { return &mRotateData; } + +protected: + MultiScaleImage* mpMSI; + ImageCompareModel::RotateData mRotateData; + + float mStartAngle, mEndAngle; + float mAngleRange; +}; + + +#endif // __MultiScaleImageCompareModel_h_ + diff --git a/molunCar/MultiScaleObj.cpp b/molunCar/MultiScaleObj.cpp new file mode 100644 index 0000000..ec2ad1e --- /dev/null +++ b/molunCar/MultiScaleObj.cpp @@ -0,0 +1,64 @@ +#include "../include/MultiScaleObj.h" +#include "../include/CVUtils.h" + +MultiScaleObj::MultiScaleObj(int levelNum /*= 2*/, float scaleStep /*= 0.5*/) + : mLevelNum(levelNum) + , mScaleStep(scaleStep) +{ + +} + +MultiScaleObj::~MultiScaleObj() +{ + +} + +MultiScaleImage::MultiScaleImage() + : mpProc(NULL) +{ + +} + +MultiScaleImage::~MultiScaleImage() +{ + if (mpProc) + { + delete mpProc; + } +} + +void MultiScaleImage::setBaseLevel(const Mat& img) +{ + mMultiScaleImages.resize(1); + mMultiScaleImages[0] = img; +} + +void MultiScaleImage::genMultiScale() +{ + resetMaxLevel(); + while (getLevel() >= 1) + { + mMultiScaleImages.push_back(resize(mMultiScaleImages.back(), mScaleStep, INTER_CUBIC)); + downLevel(); + } +} + +void MultiScaleImage::proc(void *pData) +{ + resetMaxLevel(); + while (getLevel() >= 1) + { + const Mat& img = mMultiScaleImages[getLevel() - 1]; + (*mpProc)(img, this, pData); + downLevel(); + } +} + +cv::Mat MultiScaleImage::getImg(int level) +{ + if (level < 0 || level >= mLevelNum) + { + return Mat(); + } + return mMultiScaleImages[level]; +} diff --git a/molunCar/MultiScaleObj.h b/molunCar/MultiScaleObj.h new file mode 100644 index 0000000..b6d4dfe --- /dev/null +++ b/molunCar/MultiScaleObj.h @@ -0,0 +1,87 @@ +/*! \file MultiScaleObj.h + + Copyright (C) 2014 º¼ÖÝÀûçê¿Æ¼¼ÓÐÏÞ¹«Ë¾. + + Created by bang.jin at 2017/02/20. +*/ + +#ifndef __MultiScaleObj_h_ +#define __MultiScaleObj_h_ + +#include +#include +#include + +using std::vector; +using namespace cv; + +class MultiScaleObj; + +class MultiScaleImageProc +{ +public: + virtual void operator()(const Mat& img, MultiScaleObj* pMSObj, void* pData) = 0; +}; + +class MultiScaleObj +{ +public: + MultiScaleObj(int level = 2, float scaleStep = 0.5); + ~MultiScaleObj(); + + int getLevelNum() const { return mLevelNum; } + void setLevelNum(int val) { mLevelNum = val; } + + int getLevel() const { return mLevel; } + + float getScaleStep() const { return mScaleStep; } + void setScaleStep(float iVal) { mScaleStep = iVal; } + + int getMaxLevel() const { return getLevelNum() - 1; } + int resetMaxLevel() + { + setLevel(getMaxLevel()); + return getLevel(); + } + int downLevel() + { + mLevel--; + return mLevel; + } + + virtual void proc(void *pData) = 0; + +protected: + void setLevel(int val) { mLevel = val; } + + int mLevelNum; + int mLevel; + + float mScaleStep; + +private: +}; + +class MultiScaleImage : public MultiScaleObj +{ +public: + MultiScaleImage(); + ~MultiScaleImage(); + + void setBaseLevel(const Mat& img); + void genMultiScale(); + + virtual void proc(void *pData); + + MultiScaleImageProc* getProc() const { return mpProc; } + void setProc(MultiScaleImageProc* iVal) { mpProc = iVal; } + + Mat getImg(int level); + +protected: + vector mMultiScaleImages; + MultiScaleImageProc* mpProc; +private: +}; + +#endif // __MultiScaleObj_h_ diff --git a/molunCar/StdUtils.h b/molunCar/StdUtils.h new file mode 100644 index 0000000..0c7de7b --- /dev/null +++ b/molunCar/StdUtils.h @@ -0,0 +1,457 @@ +/*! \file StdUtils.h + \brief useful functions working with std functions. + + + + Created: 2015/06/22, author: Jin Bingwen. +*/ + +#ifndef __StdUtils_h_ +#define __StdUtils_h_ + +#if (defined(_MSC_VER) && _MSC_VER <= 1600) +#define LITTLE_CPP11 1 +#else +#include +#endif + +#if (defined _WINDOWS) || (defined WIN32) +#define USE_WIN_API 1 +#endif + +#include "CyclopsLock.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "Asserte.h" + +using std::string; +using std::vector; +using std::stringstream; + +#if defined(LITTLE_CPP11) +#define GET_SIGN(x) (x < 0 ? true : false) +#else +#define GET_SIGN(x) std::signbit(x) +#endif + +template +T sum(_Iter s, _Iter e) +{ + if (s == e) + { + return T(); + } + T ret = *s; + s++; + while (s != e) + { + ret += *s; + s++; + } + return ret; +} + +template +_Iter findTopNPercentEle(_Iter s, _Iter e, float nPersent) +{ + auto maxVal = std::max_element(s, e); + T threVal = (*maxVal)*(1.0f - nPersent); + while (s != e) + { + if (*s < threVal) + { + return s; + } + ++s; + } + return s; +} + +template +_Iter findSumTopNPercentEle(_Iter s, _Iter e, float nPersent) +{ + T sumVal = sum(s, e); + T threVal = sumVal*nPersent; + sumVal = 0; + while (s != e) + { + sumVal += *s; + if (sumVal > threVal) + { + s++; + return s; + } + s++; + } + return e; +} + +template +void clearAndResetVec(vector* vec, int n) +{ + if (vec) { + vec->clear(); + vec->reserve(n); + } +} + +template +void genIncVec(vector& vec, T start, int count, T step) +{ + for (int i = 0; i < count; ++i) + { + vec.push_back(start); + start += step; + } +} + +class SortEle +{ +public: + SortEle() : mSortVal(0), pEle(NULL) {} + SortEle(double val, const void* pData) : mSortVal(val), pEle(pData) {} + double mSortVal; + const void* pEle; + bool operator< (const SortEle& i) + { + return mSortVal < i.mSortVal; + } + bool operator>(const SortEle& i) + { + return mSortVal > i.mSortVal; + } + SortEle operator+ (const SortEle& i) + { + return SortEle(mSortVal + i.mSortVal, pEle); + } + void operator+= (const SortEle& i) + { + mSortVal += i.mSortVal; + } + SortEle operator- (const SortEle& i) + { + return SortEle(mSortVal - i.mSortVal, pEle); + } + operator float() + { + return (float)mSortVal; + } + operator double() + { + return mSortVal; + } + SortEle operator* (float f) + { + return SortEle(mSortVal*f, pEle); + } + void operator= (float f) + { + mSortVal = f; + } +}; + +template +_It findRange(_It s, _It e, const _Ty& val) +{ + if (s == e) + { + return s; + } + _It mi = (e - s) / 2 + s; + if (val < *mi) + { + return findRange(s, mi, val); + } + else if (val > *mi) + { + return findRange(mi + 1, e, val); + } + else + { + return mi; + } +} + +template +void add(_It s, _It e, const _Ty& val) +{ + while (s != e) + { + *s += val; + s++; + } +} + +template +bool allInRange(_It s, _It e, _Ty minVal, _Ty maxVal) +{ + while (s != e) + { + if (*s < minVal || *s > maxVal) + { + return false; + } + ++s; + } + return true; +} + +template +bool anyInRange(_It s, _It e, _Ty minVal, _Ty maxVal) +{ + while (s != e) + { + if (*s >= minVal && *s <= maxVal) + { + return true; + } + ++s; + } + return false; +} + +template +bool anyIn(_It s, _It e, _Ty v) +{ + while (s != e) + { + if (*s == v) + { + return true; + } + ++s; + } + return false; +} + +template +bool loadAValueFromFile(string filePath, _T& ret) +{ + std::fstream fs; + fs.open(filePath, std::fstream::in); + if (!fs.is_open()) + { + return false; + } + fs >> ret; + fs.close(); +} + +// search range is [si, ei), not include ei +template +_PairIter max_first_element(_PairIter si, _PairIter ei) +{ + if (si == ei) + { + return ei; + } + + // exclude ei + _PairIter ret = --ei; + ei++; + + auto maxVal = si->first; + si++; + + while (si != ei) + { + if (maxVal < si->first) + { + maxVal = si->first; + ret = si; + } + ++si; + } + return ret; +} + +template +string joinStr(_Ty0 s0, _Ty1 s1) +{ + stringstream ss; + ss << s0 << s1; + return ss.str(); +} +template +string joinStr(_Ty0 s0, _Ty1 s1, _Ty2 s2) +{ + stringstream ss; + ss << s0 << s1 << s2; + return ss.str(); +} +template +string joinStr(_Ty0 s0, _Ty1 s1, _Ty2 s2, _Ty3 s3) +{ + stringstream ss; + ss << s0 << s1 << s2 << s3; + return ss.str(); +} + +#define _DECLARE_PARAMETER_MEM(type, name)\ +protected:\ + type m##name; + +#define _DECLARE_PARAMETER_GETFUN(type, name)\ +public:\ + type get##name() const { return m##name; } + +#define _DECLARE_PARAMETER_SETFUN(type, name)\ +public:\ + void set##name(type val) { m##name = val; } + +#define _DECLARE_PARAMETER_SETFUN2(type, name, val1, val2)\ +public:\ + void set##name(type val) {\ + assert(val >= val1 && val <= val2);\ + if (val >= val1 && val <= val2) m##name = val; } + +#define _DECLARE_PARAMETER_SETENUM(type, name)\ +public:\ + void set##name(type val) { m##name = val; }\ + void set##name(int val) {\ + set##name(static_cast(val)); } + +#define _DECLARE_PARAMETER_SETENUM2(type, name, val1, val2)\ +public:\ + void set##name(type val) {\ + assert(val >= val1 && val <= val2);\ + if (val >= val1 && val <= val2) m##name = val; }\ + void set##name(int val) {\ + set##name(static_cast(val)); } + +#define _DECLARE_PARAMETER_SETPAIR(type, name)\ +public:\ + void set##name(type val1, type val2) {\ + if (val1 > val2) { m##name##Start = val2; m##name##End = val1; }\ + else { m##name##Start = val1; m##name##End = val2; }\ + } + +#define _DECLARE_PARAMETER_SETPAIR2(type, name, val1, val2)\ +public:\ + void set##name(type value1, type value2) {\ + assert(value1 >= val1 && value1 <= val2 && value2 >= val1 && value2 <= val2);\ + if (value1 >= val1 && value1 <= val2 && value2 >= val1 && value2 <= val2) {\ + if (value1 > value2) { m##name##Start = value2; m##name##End = value1; }\ + else { m##name##Start = value1; m##name##End = value2; }\ + }\ + } + +#define DECLARE_PARAMETER(type, name)\ + _DECLARE_PARAMETER_MEM(type, name)\ + _DECLARE_PARAMETER_GETFUN(type, name)\ + _DECLARE_PARAMETER_SETFUN(type, name) + +#define DECLARE_PARAMETER2(type, name, val1, val2)\ + _DECLARE_PARAMETER_MEM(type, name)\ + _DECLARE_PARAMETER_GETFUN(type, name)\ + _DECLARE_PARAMETER_SETFUN2(type, name, val1 , val2) + +#define DECLARE_PARAMETER_SET(type, name)\ + _DECLARE_PARAMETER_MEM(type, name)\ + _DECLARE_PARAMETER_SETFUN(type, name) + +#define DECLARE_PARAMETER_SET2(type, name, val1, val2)\ + _DECLARE_PARAMETER_MEM(type, name)\ + _DECLARE_PARAMETER_SETFUN2(type, name, val1, val2) + +#define DECLARE_PARAMETER_GET(type, name)\ + _DECLARE_PARAMETER_MEM(type, name)\ + _DECLARE_PARAMETER_GETFUN(type, name) + +#define DECLARE_PARAMETER_ENUM(type, name)\ + _DECLARE_PARAMETER_MEM(type, name)\ + _DECLARE_PARAMETER_GETFUN(type, name)\ + _DECLARE_PARAMETER_SETENUM(type, name) + +#define DECLARE_PARAMETER_ENUM2(type, name, val1, val2)\ + _DECLARE_PARAMETER_MEM(type, name)\ + _DECLARE_PARAMETER_GETFUN(type, name)\ + _DECLARE_PARAMETER_SETENUM2(type, name, val1, val2) + + +#define DECLARE_PARAMETER_PAIR(type, name)\ + _DECLARE_PARAMETER_MEM(type, name##Start)\ + _DECLARE_PARAMETER_MEM(type, name##End)\ + _DECLARE_PARAMETER_GETFUN(type, name##Start)\ + _DECLARE_PARAMETER_GETFUN(type, name##End)\ + _DECLARE_PARAMETER_SETPAIR(type, name) + +#define DECLARE_PARAMETER_PAIR2(type, name, val1, val2)\ + _DECLARE_PARAMETER_MEM(type, name##Start)\ + _DECLARE_PARAMETER_MEM(type, name##End)\ + _DECLARE_PARAMETER_GETFUN(type, name##Start)\ + _DECLARE_PARAMETER_GETFUN(type, name##End)\ + _DECLARE_PARAMETER_SETPAIR2(type, name, val1, val2) + + +// Declare the provide class as a singleton. +// Get instance via Class::getInstance(). +// +// Note: according to C++11 standard, static object initialization will +// be made only by one thread, other threads will wait till it complete. +// start from VS2014, this macro is thread-safe. +#define DECLARE_SINGLETON(type, ...)\ +public:\ + static type& getInstance() {\ + static type inst(__VA_ARGS__);\ + return inst;\ + } + +// Declare the provide class as a singleton. +// Get instance via Class::getInstance(). +// +// Note: according to C++11 standard, static object initialization will +// be made only by one thread, other threads will wait till it complete. +// start from VS2014, this macro is thread-safe. +#define DECLARE_SINGLETON_NOPARA(type)\ +public:\ + static type& getInstance() {\ + static type inst;\ + return inst;\ + } + +// A simple version of object factory that use object name as key and hold object instances. +// It's thread-safe. +// Note: factory own the object instance, aka. own the object instance's memory, which means it will +// deallocate the memory when it self is destroyed (when the who application is shutdown). +// You don't need to delete the object instance yourself, and even worse, it will cause the double-delete crash. +template, TPtr>::value>::type* = nullptr> +class ObjectFactory +{ + DECLARE_SINGLETON_NOPARA(ObjectFactory) + +public: + ~ObjectFactory() {} + TPtr getObject(const char* name) + { + CyclopsLockGuard guard(&mLock); + auto it = mLookupTable.find(name); + if (it == mLookupTable.end()) { + // create new + TPtr ptr = std::make_shared(); + it = mLookupTable.insert(std::make_pair(name, ptr)).first; + } + return it->second; + } + +private: + ObjectFactory() {} + std::map mLookupTable; + CyclopsLock mLock; +}; + +template +inline T minMax(T val, T minVal, T maxVal) { + return std::min(maxVal, std::max(minVal, val)); +} + +#endif // __StdUtils_h_ + diff --git a/molunCar/TransSolver.cpp b/molunCar/TransSolver.cpp new file mode 100644 index 0000000..f4d8852 --- /dev/null +++ b/molunCar/TransSolver.cpp @@ -0,0 +1,297 @@ +#include "TransSolver.h" +#include "CVUtils.h" + +#define M_LOW_TOLERANCE 0.000001 + +Matx33d affineTrans(const vector& src, const vector& dst) +{ + if (dst.empty() || src.empty()) { + return Mat(); + } + + Point2d pc, qc; + int smallerSize = src.size() < dst.size() ? src.size() : dst.size(); + + for (int i = 0; i < smallerSize; i++) { + pc += src[i]; + qc += dst[i]; + } + pc.x /= smallerSize; + pc.y /= smallerSize; + qc.x /= smallerSize; + qc.y /= smallerSize; + + Matx21d pit; + Matx12d pi; + Matx12d qi; + Matx22d spitpi = Matx22d::zeros(); + Matx22d pitpi; + Matx22d pitqi; + Matx22d spitqi = Matx22d::zeros(); + + for (int i = 0; i < src.size() && i < dst.size(); i++) { + Point2d qpi = src[i] - pc; + Point2d qqi = dst[i] - qc; + + pit(0) = qpi.x; + pit(1) = qpi.y; + pi(0) = qpi.x; + pi(1) = qpi.y; + qi(0) = qqi.x; + qi(1) = qqi.y; + pitpi = pit*pi; + spitpi = pitpi + spitpi; + pitqi = pit*qi; + spitqi = pitqi + spitqi; + } + Matx22d ispitpi; + ispitpi = spitpi.inv(); + + Matx22d M = ispitpi*spitqi; + + double m11 = M(0, 0); + double m21 = M(0, 1); + double m12 = M(1, 0); + double m22 = M(1, 1); + + Matx33d qm(m11, m12, 0, m21, m22, 0, 0, 0, 1); + Matx33d pcm(1.0, 0, -pc.x, 0, 1.0, -pc.y, 0, 0, 1); + Matx33d qcm(1.0, 0, qc.x, 0, 1.0, qc.y, 0, 0, 1); + + Matx33d ret = qcm*qm*pcm; + + return ret; +} + +Matx33d rigidTrans(const vector& src, const vector& dst) +{ + if (dst.empty() || src.empty()) { + return Mat(); + } + + vector weights(src.size(), 1.0 / src.size()); + Point2d pc, qc; + double wsum = 0; + + for (int i = 0; i < src.size(); i++) { + double w = 1.0 / src.size(); + weights[i] = w; + pc += src[i] * w; + qc += dst[i] * w; + wsum += w; + } + pc.x /= wsum; + pc.y /= wsum; + qc.x /= wsum; + qc.y /= wsum; + + double u = 0; + double u1, u2; + u1 = 0; + u2 = 0; + for (int i = 0; i < src.size() && i < dst.size(); i++) { + Point2d qpi = src[i] - pc; + Point2d qqi = dst[i] - qc; + Point2d pi(qpi.x, qpi.y); + Point2d qi(qqi.x, qqi.y); + u1 += pi.dot(qi)*weights[i]; + Point2d pi_(pi.y, -pi.x); + u2 += qi.dot(pi_)*weights[i]; + } + u = sqrt(u1*u1 + u2*u2); + if (u < M_LOW_TOLERANCE) { + u = M_LOW_TOLERANCE; + } + + Matx22d R = Matx22d::zeros(); + Matx22d r = Matx22d::zeros(); + + for (int i = 0; i < src.size() && i < dst.size(); i++) { + Point2d qpi = src[i] - pc; + Point2d qqi = dst[i] - qc; + Point2d pi(qpi.x, qpi.y); + Point2d qi(qqi.x, qqi.y); + Point2d pi_(pi.y, -pi.x); + Point2d qi_(qi.y, -qi.x); + + r(0, 0) = pi.dot(qi); + r(0, 1) = pi.dot(qi_); + r(1, 0) = pi_.dot(qi); + r(1, 1) = pi_.dot(qi_); + + R = R + r * (weights[i] / u); + } + + double m11 = R(0, 0); + double m21 = R(0, 1); + double m12 = R(1, 0); + double m22 = R(1, 1); + Matx33d qm(m11, m12, 0, m21, m22, 0, 0, 0, 1); + Matx33d pcm(1.0, 0, -pc.x, 0, 1.0, -pc.y, 0, 0, 1); + Matx33d qcm(1.0, 0, qc.x, 0, 1.0, qc.y, 0, 0, 1); + Matx33d ret = qcm*qm*pcm; + + return ret; +} + +bool cmpPointVec(const vector& vec0, const vector& vec1, const Matx33d& mat, double tor) +{ + int smallerSize = vec0.size() < vec1.size() ? vec0.size() : vec1.size(); + for (int i = 0; i < smallerSize; ++i) + { + Point2d p0, p1; + p0 = vec0[i]; + p1 = vec1[i]; + Point3d tp0 = mat*p0; + tp0.x /= tp0.z; + tp0.y /= tp0.z; + if (abs(p1.x - tp0.x) > tor || abs(p1.y - tp0.y) > tor) + { + return false; + } + } + return true; +} + +void testTransSolver() +{ + { + //rotation only + + vector vec0, vec1; + vec0.push_back(Point2d()); + vec0.push_back(Point2d(1, 0)); + vec0.push_back(Point2d(1, 1)); + vec0.push_back(Point2d(0, 1)); + + vec1.push_back(Point2d()); + vec1.push_back(Point2d(0, 1)); + vec1.push_back(Point2d(-1, 1)); + vec1.push_back(Point2d(-1, 0)); + + Matx33d ret = affineTrans(vec0, vec1); + assert(cmpPointVec(vec0, vec1, ret, M_LOW_TOLERANCE)); + + ret = rigidTrans(vec0, vec1); + assert(cmpPointVec(vec0, vec1, ret, M_LOW_TOLERANCE)); + } + { + // rotation and scale + + vector vec0, vec1; + vec0.push_back(Point2d()); + vec0.push_back(Point2d(1, 0)); + vec0.push_back(Point2d(1, 1)); + vec0.push_back(Point2d(0, 1)); + + vec1.push_back(Point2d()); + vec1.push_back(Point2d(1, 1)); + vec1.push_back(Point2d(0, 2)); + vec1.push_back(Point2d(-1, 1)); + + Matx33d ret = affineTrans(vec0, vec1); + assert(cmpPointVec(vec0, vec1, ret, M_LOW_TOLERANCE)); + } + { + // scale only + + vector vec0, vec1; + vec0.push_back(Point2d()); + vec0.push_back(Point2d(1, 0)); + vec0.push_back(Point2d(1, 1)); + vec0.push_back(Point2d(0, 1)); + + vec1.push_back(Point2d()); + vec1.push_back(Point2d(2, 0)); + vec1.push_back(Point2d(2, 2)); + vec1.push_back(Point2d(0, 2)); + + Matx33d ret = affineTrans(vec0, vec1); + assert(cmpPointVec(vec0, vec1, ret, M_LOW_TOLERANCE)); + } + { + // translation only + + vector vec0, vec1; + vec0.push_back(Point2d()); + vec0.push_back(Point2d(1, 0)); + vec0.push_back(Point2d(1, 1)); + vec0.push_back(Point2d(0, 1)); + + vec1.push_back(Point2d(1, 1)); + vec1.push_back(Point2d(2, 1)); + vec1.push_back(Point2d(2, 2)); + vec1.push_back(Point2d(1, 2)); + + Matx33d ret = affineTrans(vec0, vec1); + assert(cmpPointVec(vec0, vec1, ret, M_LOW_TOLERANCE)); + + ret = rigidTrans(vec0, vec1); + assert(cmpPointVec(vec0, vec1, ret, M_LOW_TOLERANCE)); + } + { + for (int i = 0; i < 10000; ++i) + { + // random rotation and translation + Mat mat23 = getRotationMatrix2D(Point2f(), rand() % 360, 1.0); + mat23.at(0, 2) = (rand() % 1000) / 1000.0; + mat23.at(1, 2) = (rand() % 1000) / 1000.0; + Matx33d matx = Matx33d::eye(); + Mat mat(3, 3, CV_64FC1, matx.val); + mat23.copyTo(mat.rowRange(0, 2)); + + vector vec0, vec1; + vec0.push_back(Point2d()); + vec0.push_back(Point2d(1, 0)); + vec0.push_back(Point2d(1, 1)); + vec0.push_back(Point2d(0, 1)); + + vec1 = vec0; + transPoints(vec1, matx); + + Matx33d ret = affineTrans(vec0, vec1); + assert(cmpPointVec(vec0, vec1, ret, M_LOW_TOLERANCE)); + ret = rigidTrans(vec0, vec1); + assert(cmpPointVec(vec0, vec1, ret, M_LOW_TOLERANCE)); + } + } + { + // skew only + + vector vec0, vec1; + vec0.push_back(Point2d()); + vec0.push_back(Point2d(1, 0)); + vec0.push_back(Point2d(1, 1)); + vec0.push_back(Point2d(0, 1)); + + vec1.push_back(Point2d()); + vec1.push_back(Point2d(1, 0)); + vec1.push_back(Point2d(2, 1)); + vec1.push_back(Point2d(1, 1)); + + Matx33d ret = affineTrans(vec0, vec1); + assert(cmpPointVec(vec0, vec1, ret, M_LOW_TOLERANCE)); + } + { + // random affine + + for (int i = 0; i < 10000; ++i) + { + vector vec0, vec1; + vec0.push_back(Point2d()); + vec0.push_back(Point2d(1, 0)); + vec0.push_back(Point2d(1, 1)); + vec0.push_back(Point2d(0, 1)); + + Matx33d trans = Matx33d::eye(); + Mat mat(3, 3, CV_64FC1, trans.val); + randu(mat.rowRange(0, 2), 0, 1.0); + + vec1 = vec0; + transPoints(vec1, trans); + + Matx33d ret = affineTrans(vec0, vec1); + assert(cmpPointVec(vec0, vec1, ret, M_LOW_TOLERANCE)); + } + } +} diff --git a/molunCar/TransSolver.h b/molunCar/TransSolver.h new file mode 100644 index 0000000..282a772 --- /dev/null +++ b/molunCar/TransSolver.h @@ -0,0 +1,26 @@ +/*! \file TransSolver.h + \brief A brief file description. + + A more elaborated file description. + + Created: 2015/11/15, author: bang.jin. +*/ + +#ifndef __TransSolver_h_ +#define __TransSolver_h_ + +#include +#include + +using namespace cv; +using std::vector; + +Matx33d affineTrans(const vector& src, const vector& dst); + +Matx33d rigidTrans(const vector& src, const vector& dst, + Mat* pCenRotScaleMat = NULL); + +void testTransSolver(); + +#endif // __TransSolver_h_ + diff --git a/molunCar/cvdrawutils.cpp b/molunCar/cvdrawutils.cpp new file mode 100644 index 0000000..03e0ff4 --- /dev/null +++ b/molunCar/cvdrawutils.cpp @@ -0,0 +1,182 @@ +#include "cvdrawutils.h" +#include "CVUtils.h" +#include "DetectRoi.h" + +cv::Mat drawLines(const Mat& img, const vector& segPointVec) +{ +#ifdef LP_DISABLE_DRAW + return Mat(); +#else + + Mat canvas = normCanvas(img); + for (size_t i = 0; i < segPointVec.size(); ++i) + { + const PointfPair& pointPair = segPointVec[i]; + cv::line(canvas, pointPair.first, pointPair.second, Scalar(255, 255, 255, 0.5)); + } + return canvas; +#endif +} + +cv::Mat drawLines(const Mat& img, const vector& segPointVec) +{ +#ifdef LP_DISABLE_DRAW + return Mat(); +#else + + Mat canvas = normCanvas(img); + for (size_t i = 0; i < segPointVec.size(); ++i) + { + const PointPair& pointPair = segPointVec[i]; + cv::line(canvas, pointPair.first, pointPair.second, Scalar(255, 255, 255, 0.5)); + } + return canvas; +#endif +} + +void drawLine(Mat& canvas, const Vec4f& l, const Scalar& color) +{ + line(canvas, Point(l.val[0], l.val[1]), Point(l.val[2], l.val[3]), color); +} + +void drawPoint(Mat& canvas, int x, int y, const Scalar& color, int size) +{ + int halfSize = size / 2; + Rect rect(x - halfSize, y - halfSize, size, size); +#if (CV_MAJOR_VERSION >= 3) + rectangle(canvas, rect, color, FILLED); +#else + rectangle(canvas, rect, color, CV_FILLED); +#endif +} + +Scalar getRandomColor() +{ + RNG& r = cv::theRNG(); + return Scalar(r.next() % 256, r.next() % 256, r.next() % 256, 255); +} + +Mat getColorCanvas(const Mat& img, float resizeFactor) +{ + Mat canvas; + if (resizeFactor == 1.) canvas = img.clone(); + else resize(img, canvas, Size(), resizeFactor, resizeFactor); + if (canvas.channels() == 1) cvtColor(canvas, canvas, CV_GRAY2BGR); + return canvas; +} + +cv::Mat drawPoints(const Mat& img, const vector& pointVec, int grayVal /*= 255*/, int xRadius /*= 0*/, int yRadius /*= 0*/) +{ +#ifdef LP_DISABLE_DRAW + return Mat(); +#else + Mat canvas = normCanvas(img); + for (size_t i = 0; i < pointVec.size(); ++i) + { + Point pt(pointVec[i]); + if (xRadius == 0 && yRadius == 0) + { + canvas.at(pt.y, pt.x) = grayVal; + } + else + { + Rect rect(pt.x - xRadius, pt.y - yRadius, xRadius * 2 + 1, yRadius * 2 + 1); + rectangle(canvas, rect, Scalar(grayVal, grayVal, grayVal)); + } + + } + return canvas; +#endif +} + +cv::Mat drawPointsWithKeyPoints(const Mat& img, const vector& pointVec) +{ +#ifdef LP_DISABLE_DRAW + return Mat(); +#else + vector keypointVec; + for (size_t i = 0; i < pointVec.size(); ++i) + { + Point pt = pointVec[i]; + keypointVec.push_back(KeyPoint(Point2f((float)pt.x, (float)pt.y), 1)); + } + Mat canvas = normCanvas(img); + drawKeypoints(canvas, keypointVec, canvas); + + return canvas; +#endif +} + +void drawRotateRect(Mat& canvas, const RotatedRect& rr, const Scalar& color, int angleLen /*= 30*/) +{ + // center + drawPoint(canvas, rr.center.x, rr.center.y, color); + + // draw pose rect + Point2f pts[4]; + rr.points(pts); + for (int i = 0; i < 3; ++i) + line(canvas, pts[i], pts[i + 1], color); + line(canvas, pts[3], pts[0], color); + + // draw angle + int xshift = cos(rr.angle / 180 * CV_PI) * angleLen; + int yshift = sin(rr.angle / 180 * CV_PI) * angleLen; + Point2f p(rr.center.x + xshift, rr.center.y + yshift); + line(canvas, Point(rr.center.x, rr.center.y), p, color); +} + +#if (CV_MAJOR_VERSION >= 3) +Mat highlightRoi(const Mat& roiImg, const Rect& r, const vector& roiVertexes) +{ + assert(r.width == roiImg.cols && r.height == roiImg.rows); + + Mat mask = DetectRoi::genMask(r, roiVertexes); + Mat canvas(roiImg.rows, roiImg.cols, CV_8UC4); + + // bgr + static int from_to1_gay[] = { 0,0,0,1,0,2 }; + static int from_to1_color[] = { 0,0,1,1,2,2 }; + int* from_to1 = nullptr; + int channelCount = roiImg.channels(); + if (channelCount == 1) + from_to1 = from_to1_gay; + else if (channelCount == 3 || channelCount == 4) + from_to1 = from_to1_color; + else { + assert(false && "highlightRoi: unexpected channel."); + return gDummyMat; + } + + mixChannels(roiImg, canvas, from_to1, 3); + + // a + static int from_to2[] = { 0,3 }; + mixChannels(mask, canvas, from_to2, 1); + + return canvas; +} +#endif + +void drawPointDir(Mat& canvas, const Point& p, float angle, const Scalar& color, const Scalar& centerColor, int len, int thick) +{ + float halfLen = len / 2.f; + float yStep = sin((90 + angle) / 180 * CV_PI) * halfLen; + float xStep = cos((90 + angle) / 180 * CV_PI) * halfLen; + line(canvas, Point(p.x - xStep, p.y - yStep), Point(p.x + xStep, p.y + yStep), color, thick, CV_AA); +#if (CV_MAJOR_VERSION >= 3) + rectangle(canvas, p, p, centerColor, FILLED); +#else + rectangle(canvas, p, p, centerColor, CV_FILLED); +#endif +} + +void drawPatternPose(Mat& canvas, const Size& templateSize, const Vec4f& pose, const Scalar& color) +{ + int cols = templateSize.width; + int rows = templateSize.height; + RotatedRect rr(Point2f(pose[0], pose[1]), Size2f(pose[3] * cols, pose[3] * cols), pose[2]); + + drawRotateRect(canvas, rr, color); +} + diff --git a/molunCar/cvdrawutils.h b/molunCar/cvdrawutils.h new file mode 100644 index 0000000..0f81521 --- /dev/null +++ b/molunCar/cvdrawutils.h @@ -0,0 +1,38 @@ +#ifndef cvdrawutils_h__ +#define cvdrawutils_h__ + +#include +#include +#include +#include "pointpair.h" + +using std::vector; +using namespace cv; + +Scalar getRandomColor(); + +Mat getColorCanvas(const Mat& img, float resizeFactor = 1.); + +Mat drawPoints(const Mat& img, const vector& pointVec, int grayVal = 255, int xRadius = 0, int yRadius = 0); + +Mat drawPointsWithKeyPoints(const Mat& img, const vector& pointVec); + +Mat drawLines(const Mat& img, const vector& segPointVec); + +Mat drawLines(const Mat& img, const vector& segPointVec); + +void drawLine(Mat& canvas, const Vec4f& l, const Scalar& color); + +void drawPoint(Mat& canvas, int x, int y, const Scalar& color, int size = 1); + +void drawRotateRect(Mat& canvas, const RotatedRect& rr, const Scalar& color, int angleLen = 30); + +Mat highlightRoi(const Mat& roiImg, const Rect& r, const vector& roiVertexes); + +void drawPointDir(Mat& canvas, const Point& p, float angle, const Scalar& color, + const Scalar& centerColor, int len = 5, int thick = 1); + +void drawPatternPose(Mat& canvas, const Size& templateSize, const Vec4f& pose, const Scalar& color); + + +#endif // cvdrawutils_h__ diff --git a/molunCar/cvmatutils.cpp b/molunCar/cvmatutils.cpp new file mode 100644 index 0000000..f0ba267 --- /dev/null +++ b/molunCar/cvmatutils.cpp @@ -0,0 +1,217 @@ +#include "cvmatutils.h" +#include +#include + +using std::list; +using std::iostream; + +cv::Mat getTranslateMatrix2D(float dx, float dy) +{ + Mat ret(3, 2, CV_32FC1); + ret.setTo(0); + float* p = (float*)ret.data; + p[0] = 1.0; + p[2] = dx; + p[4] = 1.0; + p[5] = dy; + + return ret; +} + +void cutMargin(Mat& img, int w) +{ + Rect r(w, w, img.cols - 2 * w, img.rows - 2 * w); + img = Mat(img, r); +} + +cv::Mat duplicateChannels(const Mat& mat, int n) +{ + assert(mat.channels() == 1); + + vector matVec(n, mat); + + Mat ret; + merge(matVec, ret); + + return ret; +} + +cv::Scalar sum(const Mat& mat, const Mat& mask) +{ +// double minVal, maxVal; +// minMaxIdx(mask, &minVal, &maxVal); +// mask /= maxVal; + Mat mask3ch = duplicateChannels(mask, 3); + Mat maskedMat = mask3ch & mat; + return sum(maskedMat); +} + +string toStr(const Mat& m) +{ + stringstream ss; + switch (m.type()) + { + case CV_8UC1: + for (int i = 0; i < m.rows; ++i) + { + uchar* pRowData = m.row(i).data; + for (int j = 0; j < m.cols; ++j) + { + ss << (int)pRowData[j] << " "; + } + ss << "\n"; + } + break; + case CV_32FC1: + for (int i = 0; i < m.rows; ++i) + { + float* pRowData = (float*)m.row(i).data; + for (int j = 0; j < m.cols; ++j) + { + ss << (float)pRowData[j] << " "; + } + ss << "\n"; + } + break; + case CV_8UC4: + for (int i = 0; i < m.rows; ++i) + { + uchar* pRowData = m.row(i).data; + for (int j = 0; j < m.cols * 4; ++j) + { + ss << (int)pRowData[j] << " "; + } + ss << "\n"; + } + break; + case CV_8UC3: + for (int i = 0; i < m.rows; ++i) + { + uchar* pRowData = m.row(i).data; + for (int j = 0; j < m.cols * 3; ++j) + { + ss << (int)pRowData[j] << " "; + } + ss << "\n"; + } + break; + } + + return ss.str(); +} + +cv::Mat fromStr(const string& str, int rows, int cols, int type) +{ + Mat ret; + stringstream ss(str); + int count = 0; + switch (type) + { + case CV_8UC1: + ret.create(rows, cols, CV_8UC1); + for (int i = 0; i < rows; ++i) + { + uchar* pRowData = ret.row(i).data; + for (int j = 0; j < cols; ++j) + { + if (ss.eof()) + { + return Mat(); + } + int v; + ss >> v; + (*pRowData++) = v; + } + } + break; + case CV_32FC1: + ret.create(rows, cols, CV_32FC1); + for (int i = 0; i < rows; ++i) + { + float* pRowData = (float*)ret.row(i).data; + for (int j = 0; j < cols; ++j) + { + if (ss.eof()) + { + return Mat(); + } + float v; + ss >> v; + (*pRowData++) = v; + } + } + break; + case CV_32FC3: + ret.create(rows, cols / 3, CV_32FC3); + for (int i = 0; i < rows; ++i) + { + float* pRowData = (float*)ret.row(i).data; + for (int j = 0; j < cols; ++j) + { + if (ss.eof()) + { + return Mat(); + } + float v; + ss >> v; + (*pRowData++) = v; + } + } + break; + case CV_32FC4: + ret.create(rows, cols / 4, CV_32FC4); + for (int i = 0; i < rows; ++i) + { + float* pRowData = (float*)ret.row(i).data; + for (int j = 0; j < cols; ++j) + { + if (ss.eof()) + { + return Mat(); + } + float v; + ss >> v; + (*pRowData++) = v; + } + } + break; + case CV_8UC4: + ret.create(rows, cols / 4, CV_8UC4); + for (int i = 0; i < rows; ++i) + { + uchar* pRowData = ret.row(i).data; + for (int j = 0; j < cols; ++j) + { + if (ss.eof()) + { + return Mat(); + } + int v; + ss >> v; + (*pRowData++) = v; + } + } + break; + case CV_8UC3: + ret.create(rows, cols / 3, CV_8UC3); + for (int i = 0; i < rows; ++i) + { + uchar* pRowData = ret.row(i).data; + for (int j = 0; j < cols; ++j) + { + if (ss.eof()) + { + return Mat(); + } + int v; + ss >> v; + (*pRowData++) = v; + } + } + break; + default: + return Mat(); + break; + } + return ret; +} diff --git a/molunCar/cvmatutils.h b/molunCar/cvmatutils.h new file mode 100644 index 0000000..3f52934 --- /dev/null +++ b/molunCar/cvmatutils.h @@ -0,0 +1,26 @@ +#ifndef _cvmatutils_h_ +#define _cvmatutils_h_ + +#include +#include +#include +#include +#include + +using namespace cv; +using std::vector; +using std::string; +using std::stringstream; + +Mat getTranslateMatrix2D(float dx, float dy); + +void cutMargin(Mat& img, int w); + +Mat duplicateChannels(const Mat& mat, int n); + +Scalar sum(const Mat& mat, const Mat& mask); + +string toStr(const Mat& m); +Mat fromStr(const string& str, int rows, int cols, int type); + +#endif // _cvmatutils_h_ diff --git a/molunCar/pointpair.h b/molunCar/pointpair.h new file mode 100644 index 0000000..4d52f32 --- /dev/null +++ b/molunCar/pointpair.h @@ -0,0 +1,78 @@ +#ifndef pointpair_h__ +#define pointpair_h__ + +#include +#include +#include + +using std::vector; +using std::pair; +using namespace cv; + +template +class _PointPair : public pair<_Point, _Point> +{ +public: + _PointPair() + : pair<_Point, _Point>(), mAver(0) {} + + _PointPair(const _Point& p0, const _Point& p1) + : pair<_Point, _Point>(p0, p1), mAver(0) {} + + _PointPair(const _PointPair<_Point>& pointPair) + : pair<_Point, _Point>(pointPair.first, pointPair.second) + , mAver(pointPair.aver()) + , mStr(pointPair.getStr()) + {} + + void setAver(float val) { mAver = val; } + float aver() const { return mAver; } + + std::string getStr() const { return mStr; } + void setStr(std::string iVal) { mStr = iVal; } + +protected: + float mAver; + std::string mStr; + +private: +}; + +typedef _PointPair PointPair; +typedef _PointPair PointfPair; + +void convertPointPair2PointfPair(const vector& vec0, + vector& vec1); + +template +double pointDis(const _Point& i, const _Point& j) +{ + return norm(i - j); +} + +template +double pointPairXDis(const _PointPair& i, const _PointPair& j) +{ + return (abs(i.first.x - j.first.x) + + abs(i.second.x - j.second.x)) / 2.0; +} + +double pointPairDis(const PointPair& i, const PointPair& j); + +template +double pointPairLen(const _PointPair& p) +{ + return pointDis(p.first, p.second); +} + +template +void addYToPointPairs(vector<_PointPair>& vec, int y) +{ + for (auto i = vec.begin(); i != vec.end(); ++i) + { + i->first.y += y; + i->second.y += y; + } +} + +#endif // pointpair_h__