#pragma once #include "stdafx.h" #include #include #include #include "OTSImageProcess.h" using namespace cv; using namespace std; namespace OTSIMGPROC { namespace { /***** 求两点间距离*****/ float getDistance(Point pointO, Point pointA) { float distance; distance = powf((pointO.x - pointA.x), 2) + powf((pointO.y - pointA.y), 2); distance = sqrtf(distance); return distance; } /***** 点到直线的距离:P到AB的距离*****/ //P为线外一点,AB为线段两个端点 float getDist_P2L(Point pointP, Point pointA, Point pointB) { //求直线方程 int A = 0, B = 0, C = 0; A = pointA.y - pointB.y; B = pointB.x - pointA.x; C = pointA.x*pointB.y - pointA.y*pointB.x; //代入点到直线距离公式 float distance = 0; distance = ((float)abs(A*pointP.x + B * pointP.y + C)) / ((float)sqrtf(A*A + B * B)); return distance; } int Side(Point P1, Point P2, Point point) { /*Point P1 = line.P1; Point P2 = line.P2;*/ return ((P2.y - P1.y) * point.x + (P1.x - P2.x) * point.y + (P2.x*P1.y - P1.x*P2.y)); } void FindInnerCircleInContour(vector contour, Point ¢er, int &radius) { Rect r = boundingRect(contour); int nL = r.x, nR = r.br().x; //轮廓左右边界 int nT = r.y, nB = r.br().y; //轮廓上下边界 double dist = 0; double maxdist = 0; for (int i = nL; i < nR; i++) //列 { for (int j = nT; j < nB; j++) //行 { //计算轮廓内部各点到最近轮廓点的距离 dist = pointPolygonTest(contour, Point(i, j), true); if (dist > maxdist) { //求最大距离,只有轮廓最中心的点才距离最大 maxdist = dist; center = Point(i, j); } } } radius = maxdist; //圆半径 } BOOL GetParticleAverageChord(std::vector listEdge, double a_PixelSize, double &dPartFTD) { // safety check double nx = 0, ny = 0; Moments mu; mu = moments(listEdge, false); nx = mu.m10 / mu.m00; ny = mu.m01 / mu.m00; //circle(cvcopyImg, Point(nx, ny), 1, (255), 1); Point ptCenter = Point((int)nx, (int)ny); // coordinate transformation Point ptPosition; int radiusNum = 0; // get ferret diameter double sumFltDiameter = 0; int interval; int edgePointNum = listEdge.size(); if (edgePointNum > 100) { interval = edgePointNum / 100;//get one line per 10 degree aproxemately } else { interval = 1; } for (int i = 0; i < edgePointNum; i++) { Point pt = listEdge[i]; ptPosition.x = abs(pt.x - ptCenter.x); ptPosition.y = abs(pt.y - ptCenter.y); if (i % interval == 0)//calculate one line per 10 point ,so to speed up.don't calculate all the diameter. { double r1 = sqrt(pow(ptPosition.x, 2) + pow(ptPosition.y, 2)); sumFltDiameter += r1; radiusNum += 1; //line(cvImageData, ptCenter, pt, Scalar(nBlackColor), nThickness, nLineType); } } if (radiusNum == 0) { dPartFTD = 0; } else { dPartFTD = a_PixelSize * sumFltDiameter / radiusNum * 2; } //imshow("feret center", cvImageData); return TRUE; } } COTSImageProcess::COTSImageProcess() { } COTSImageProcess::~COTSImageProcess() { } // use verticl line of 3 pixel to erode a image void COTSImageProcess::BErodeVertical3(LPBYTE source, LPBYTE target, WORD rows, WORD columns) { WORD x, y, wcounts; if (rows <= 2 || columns <= 2)return; // top line for (x = 0; x < columns; x++) { *(target + x) = 0; } // bottom line for (x = 0; x < columns; x++) { *(target + (DWORD)(rows - 1)*columns + x) = 0; } for (y = 1; y= wDegree) *(target + (DWORD)y*columns + x) = 0; else *(target + (DWORD)y*columns + x) = 0xff; } } // top line for (x = 1; x= wDegree * 5 / 8) *(target + x) = 0; else *(target + x) = 0xff; } // bottom line for (x = 1; x= wDegree * 5 / 8) *(target + (DWORD)(rows - 1)*columns + x) = 0; else *(target + (DWORD)(rows - 1)*columns + x) = 0xff; } // left line for (y = 1; y= wDegree * 5 / 8) *(target + (DWORD)y*columns) = 0; else *(target + (DWORD)y*columns) = 0xff; } // right line for (y = 1; y= wDegree * 5 / 8) *(target + (DWORD)y*columns + columns - 1) = 0; else *(target + (DWORD)y*columns + columns - 1) = 0xff; } return; } void COTSImageProcess::BDilate3(LPBYTE source, LPBYTE target, WORD wDegree, WORD rows, WORD columns) { WORD x, y, i, j, wcounts; for (y = 1; y= wDegree) *(target + (DWORD)y*columns + x) = 0xff; else *(target + (DWORD)y*columns + x) = 0; } } // top line for (x = 1; x= wDegree * 5 / 8) // but does not mater, as we have border of 2 now { *(target + x) = 0xff; } else { *(target + x) = 0; } } // bottom line for (x = 1; x wDegree * 5 / 8) { *(target + (DWORD)(rows - 1)*columns + x) = 0xff; } else { *(target + (DWORD)(rows - 1)*columns + x) = 0; } } // left line for (y = 1; y= wDegree * 5 / 8) { *(target + (DWORD)y*columns) = 0xff; } else { *(target + (DWORD)y*columns) = 0; } } // right line for (y = 1; y= wDegree * 5 / 8) { *(target + (DWORD)y*columns + columns - 1) = 0xff; } else { *(target + (DWORD)y*columns + columns - 1) = 0; } } // four cornor points treated separately here // top-left if (*(source) != 0) { *target = 0xff; } else { wcounts = 0; if (*(source + 1) != 0) wcounts++; if (*(source + columns) != 0) wcounts++; if (*(source + columns + 1) != 0) wcounts++; // if (wcounts >= wDegree*3/8) // this is a bug here - interger division if (wcounts * 8 >= wDegree * 3) { *target = 0xff; } else { *target = 0; } } //top-right if (*(source + columns - 1) != 0) { *(target + columns - 1) = 0xff; } else { wcounts = 0; if (*(source + columns - 2) != 0) wcounts++; if (*(source + columns * 2 - 1) != 0) wcounts++; if (*(source + columns * 2 - 2) != 0) wcounts++; // if (wcounts >= wDegree*3/8) // this is a bug here - interger division if (wcounts * 8 >= wDegree * 3) { *(target + columns - 1) = 0xff; } else { *(target + columns - 1) = 0; } } //bottom-left if (*(source + (DWORD)columns * (rows - 1)) != 0) { *(target + (DWORD)columns * (rows - 1)) = 0xff; } else { wcounts = 0; if (*(source + (DWORD)columns * (rows - 1) + 1) != 0) wcounts++; if (*(source + (DWORD)columns * (rows - 2)) != 0) wcounts++; if (*(source + (DWORD)columns * (rows - 2) + 1) != 0) wcounts++; // if (wcounts >= wDegree*3/8) // this is a bug here - interger division if (wcounts * 8 >= wDegree * 3) { *(target + (DWORD)columns * (rows - 1)) = 0xff; } else { *(target + (DWORD)columns * (rows - 1)) = 0; } } //bottom-right if (*(source + (DWORD)columns * rows - 1) != 0) { *(target + (DWORD)columns * rows - 1) = 0xff; } else { wcounts = 0; if (*(source + (DWORD)columns * rows - 2) != 0) wcounts++; if (*(source + (DWORD)columns * (rows - 1) - 2) != 0) wcounts++; if (*(source + (DWORD)columns * (rows - 1) - 1) != 0) wcounts++; // if (wcounts >= wDegree*3/8) // this is a bug here - interger division if (wcounts * 8 >= wDegree * 3) { *(target + (DWORD)columns * rows - 1) = 0xff; } else { *(target + (DWORD)columns * rows - 1) = 0; } } return; } // ReZoom the picture with re-magnification BOOL COTSImageProcess::ReZoom(CString InPutPath, CString OutPutPath) { Mat cvSrcImg; string strInputPath; strInputPath = CStringA(InPutPath); // Pictures loop in folder std::vector ImageFolder; cv::glob(strInputPath, ImageFolder); if (ImageFolder.size() == 0) { return FALSE; } for (unsigned int nImgNum = 0; nImgNum < ImageFolder.size(); ++nImgNum) { cvSrcImg = cv::imread(ImageFolder[nImgNum], CV_LOAD_IMAGE_GRAYSCALE); // Image convolution operation //// convolution kernel float kernel[] = { -1, -1 , -1, -1 , 0, -1, -1 , -1 , -1 }; cv::Mat ker = cv::Mat(nImage_Size, nImage_Size, CV_32F, &kernel); cv::Mat cvDstImg = cv::Mat(cvSrcImg.size(), cvSrcImg.type()); // anchor of the kernel cv::Point anchor(-1, -1); cv::filter2D(cvSrcImg, cvDstImg, CV_32F, ker, anchor, delta, cv::THRESH_TRUNC); // Maximum Pixel Value cvDstImg = abs(cvDstImg); double minVal, maxVal; minMaxLoc(cvDstImg, &minVal, &maxVal); // Grayscale image int nReduce; Mat onesImg = Mat::ones(cvDstImg.rows, cvDstImg.cols, CV_32F) * (int)minVal; absdiff(cvDstImg, onesImg, cvDstImg); nReduce = (int)maxVal - minVal; cvDstImg = cvDstImg * nBlackColor / nReduce; // Output image convert data to int cvDstImg.convertTo(cvDstImg, CV_8U); // Process the picture to 128 pixels resize(cvDstImg, cvDstImg, Size(nPictureSize, nPictureSize)); threshold(cvDstImg, cvDstImg, nProcessParam, nBlackColor, CV_THRESH_BINARY); string strOutPutPath; strOutPutPath = CStringA(OutPutPath); imwrite(strOutPutPath , cvDstImg); } return TRUE; } BOOL COTSImageProcess::CalcuParticleImagePropertes(COTSParticlePtr a_pOTSPart, double a_PixelSize) { //--------- convert this particle data to image data,construct an image only with this particle.------ const int nExpand_Size = 3; const int nWhiteColor = 0; const int nThickness = 1; // lineType Type of the line const int nLineType = 8; // get rectangle of the particle CRect rect = a_pOTSPart->GetParticleRect(); if (a_pOTSPart->GetArea() < 80 * a_PixelSize)// the particle is too small that openCV can't calculate a width value of it. Then we take the upright rect of the particle as it's minArea rect. { double w = 0, h = 0; w = (double)rect.Width()*a_PixelSize; h = (double)rect.Height()*a_PixelSize; a_pOTSPart->SetDMax(MAX(w, h)); a_pOTSPart->SetDMin(MIN(w, h)); a_pOTSPart->SetDMean((w + h) / 2); a_pOTSPart->SetFeretDiameter((w + h) / 2); a_pOTSPart->SetDElong(MAX(w, h)); a_pOTSPart->SetPerimeter((w+h)*2); a_pOTSPart->SetDPerp(MIN(w, h)); a_pOTSPart->SetDInscr(MIN(w, h)); return true; } // calculate the particle image data size, expand 3 pixel at the edge Mat particleImage = Mat::zeros(rect.Height() + nExpand_Size , rect.Width() + nExpand_Size , CV_8U); // get the segment list COTSSegmentsList listSegment = a_pOTSPart->GetFeature()->GetSegmentsList(); for (auto pSegment : listSegment) { int nStart = pSegment->GetStart() - rect.left + nExpand_Size; int nEnd = pSegment->GetStart() + pSegment->GetLength() - rect.left - 1 + nExpand_Size; int nHeight = pSegment->GetHeight() - rect.top + nExpand_Size; line(particleImage, Point(nStart, nHeight), Point(nEnd, nHeight), Scalar(nBlackColor), nThickness, nLineType); } //--------abstract the contour of the particle. Mat cvcopyImg; medianBlur(particleImage, cvcopyImg, 7);//smooth the edge Mat cvContourImg = Mat::zeros(rect.Height() + nExpand_Size, rect.Width() + nExpand_Size, CV_8U); vector>contours; Canny(cvcopyImg, cvcopyImg, 20, 20 * 2, 3); findContours(cvcopyImg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); if (contours.size()==0)// the particle is too odd that openCV can't find a contour of it. Then we take the upright rect of the particle as it's minArea rect. { double w = 0, h = 0; w = (double)rect.Width()*a_PixelSize; h = (double)rect.Height()*a_PixelSize; a_pOTSPart->SetDMax(MAX(w, h)); a_pOTSPart->SetDMin(MIN(w, h)); a_pOTSPart->SetDMean((w + h) / 2); a_pOTSPart->SetFeretDiameter((w + h) / 2); a_pOTSPart->SetDElong(MAX(w, h)); a_pOTSPart->SetPerimeter((w + h) * 2); a_pOTSPart->SetDPerp(MIN(w, h)); a_pOTSPart->SetDInscr(MIN(w, h)); return true; } int imaxcontour = 0, imax = 0; for (unsigned int i = 0; i < contours.size(); i++) { int itmp = contourArea(contours[i]); if (imaxcontour < itmp) { imax = i; imaxcontour = itmp; } } vector listEdge = contours[imax]; vector>Outcontours; Outcontours.push_back(listEdge); //---------calculate the minimium rectangle auto rRect = cv::minAreaRect(listEdge); Point2f p[4]; rRect.points(p); int D_MIN =getDistance(p[0], p[1]); int D_MinRecLen = 0;//minareaRect's length(the lenger side). for (int j = 0; j <= 2; j++) { //line(cvContourImg, p[j], p[(j + 1) % 4], Scalar(100, 100, 0), 2); int d = getDistance(p[j], p[j + 1]); if (d < D_MIN) { D_MIN = d; } if (d > D_MinRecLen) { D_MinRecLen = d; } } a_pOTSPart->SetDMin(D_MIN*a_PixelSize); a_pOTSPart->SetOrientation(rRect.angle); //----------calculate the perimeter double d = arcLength(listEdge, true); a_pOTSPart->SetPerimeter(d*a_PixelSize); //-----------calculate the Max Diameter. Find the min enclosing circle first ,then find the two farthest circle connected point. Point2f center; float radius; minEnclosingCircle(listEdge, center, radius); //circle(cvContourImg, center, radius, Scalar(100), 2); std::vector outContour = listEdge; std::vector rst; for (unsigned int k = 0; k < outContour.size(); k++) { Point p = outContour[k]; double d = sqrt(pow((p.x - center.x), 2) + pow((p.y - center.y), 2)); if (fabs(d - radius) < 0.01) { rst.push_back(p); } } double D_MAX = 0; Point lineDmax[2]; for (unsigned int m = 0; m < rst.size(); m++) { Point p = rst[m]; for (unsigned int n = m + 1; n < rst.size(); n++) { Point p1 = rst[n]; double d = sqrt(powf((p.x - p1.x), 2) + powf((p.y - p1.y), 2)); if (d > D_MAX) { D_MAX = d; lineDmax[0] = p; lineDmax[1] = p1; } } } a_pOTSPart->SetDMax(D_MAX*a_PixelSize); //--------calculate the D_PERP property using the D_MAX's two endpoints. std::vector curve1; std::vector curve2; for (unsigned int i = 0; i < outContour.size(); i++) { Point pt = outContour[i]; bool start = false; int clockwise = Side(lineDmax[0], lineDmax[1], pt);// devide these points into two group ,separate into the two sides. if (clockwise > 0) { curve1.push_back(pt); } else { curve2.push_back(pt); } } double d_perp1 = 0, d_perp2 = 0; for (unsigned int i = 0; i < curve1.size(); i++) { double d = getDist_P2L(curve1[i], lineDmax[0], lineDmax[1]); if (d > d_perp1) { d_perp1 = d; } } for (unsigned int i = 0; i < curve2.size(); i++) { double d = getDist_P2L(curve2[i], lineDmax[0], lineDmax[1]); if (d > d_perp2) { d_perp2 = d; } } a_pOTSPart->SetDPerp((d_perp1 + d_perp2)*a_PixelSize); //----------find the diameter of max inscribed circle int r; Point inscribeCirclecenter; FindInnerCircleInContour(outContour, inscribeCirclecenter, r); //circle(cvContourImg, inscribeCirclecenter, r, Scalar(200)); a_pOTSPart->SetDInscr(r * 2 * a_PixelSize); //---------------calculate the image other caracater: length/width realArea/minRectangeArea etc. we can use these propertes to do forward process. double minRectArea = D_MIN * D_MinRecLen*a_PixelSize*a_PixelSize;//最小外接矩形面积 double fillRatio = a_pOTSPart->GetArea() / minRectArea;//实际面积与最小外接矩形面积比,that's the fill rate. double lengthWidthRatio; lengthWidthRatio = (double)D_MinRecLen / D_MIN;//长宽比 //decide if this shape is a strip shape :if the lenthWidthRatio>2 then it is. if the lengthWidthRatio<2 and the areaRatio<0.5 then it is. bool isStripShape = false; double curveLength = 0; double D_MEAN=0; Moments mu; mu = moments(listEdge, false); int nx = mu.m10 / mu.m00; int ny = mu.m01 / mu.m00; //circle(cvcopyImg, Point(nx, ny), 1, (255), 1); Point ptCenter = Point((int)nx, (int)ny); if (pointPolygonTest(outContour, ptCenter, false) != 1)// the center point doesn't contain in the contour, we think it as curve shape. { isStripShape = true; } /*if (lengthWidthRatio >= 2 )// in PartA software this is true,but IncA because of the GB definition the everage feret diameter is always the mean value of all the chord. { isStripShape = true; }*/ if (fillRatio <= 0.4)// only when the fill rate is very low,we think it as a curve shape,then we choose the mean width as the feret diameter. { isStripShape = true; } if (isStripShape) { curveLength = a_pOTSPart->GetPerimeter()/2 - a_pOTSPart->GetDInscr()/2;// thinking this particle as a strip rectangle.the width is the max inscribe circle diameter/2. if (curveLength < D_MAX) { curveLength = D_MAX; } if (curveLength < MIN_DOUBLE_VALUE || a_pOTSPart->GetArea()GetArea() / curveLength; } a_pOTSPart->SetDMean(D_MEAN*a_PixelSize); a_pOTSPart->SetFeretDiameter(D_MEAN*a_PixelSize); a_pOTSPart->SetDElong (curveLength*a_PixelSize); } else//it's a ball shape particle { curveLength = D_MAX; double ftd = 0, maxD = 0, minD = 0, dratio = 0; GetParticleAverageChord(outContour, a_PixelSize, ftd); a_pOTSPart->SetDMean(ftd); a_pOTSPart->SetFeretDiameter(ftd); a_pOTSPart->SetDElong(curveLength*a_PixelSize); } return true; } }