#include "ImageApplyOutHole.h" #include "ImageProcess_Public.h" //#include #ifdef LOG #include "Device/filetools.h" #endif // LOG //#define DRAW_PIC CImageApplyOutHole::CImageApplyOutHole(void) : CImageApply() , m_borderSize(20) , m_edgeScale(0.1f, 0.1f, 0.1f, 0.1f) , m_threshold(50) { } CImageApplyOutHole::CImageApplyOutHole(float borderSize, cv::Vec4f edgeScale, double threshold) : CImageApply() , m_borderSize(borderSize) , m_edgeScale(edgeScale) , m_threshold(threshold) { } CImageApplyOutHole::~CImageApplyOutHole(void) { } void CImageApplyOutHole::apply(cv::Mat& pDib, int side) { (void)pDib; (void)side; } #define MIN_CONTOUR_SIZE 10 #define RESIZE_FIXED_WIDTH 2448.0 #define LINE_WIDTH 18 #define DILATE_SIZE 16 void CImageApplyOutHole::apply(std::vector& mats, bool isTwoSide) { #ifdef LOG FileTools::write_log("imgprc.txt", "enter ImageOutHole apply"); #endif // LOG if (mats.size() < 2) { #ifdef LOG FileTools::write_log("imgprc.txt", "exit ImageOutHole apply"); #endif // LOG return; } if (mats[0].empty() || mats[1].empty()) { #ifdef LOG FileTools::write_log("imgprc.txt", "exit ImageOutHole apply"); #endif // LOG return; } double resize_scale = cv::min(RESIZE_FIXED_WIDTH / static_cast(mats[0].cols), 1.0); //二值化正反面图像 cv::Mat front, back; if (resize_scale != 1.0) { cv::resize(mats[0], front, cv::Size(), resize_scale, resize_scale); cv::resize(mats[1], back, cv::Size(), resize_scale, resize_scale); } else { front = mats[0]; back = mats[1]; } cv::Mat front_thre, back_thre; hg::threshold_Mat(front, front_thre, m_threshold); hg::threshold_Mat(back, back_thre, m_threshold); #ifdef DRAW_PIC cv::imwrite("front_thre.jpg", front_thre); cv::imwrite("back_thre.jpg", back_thre); #endif cv::Mat element = getStructuringElement(cv::MORPH_RECT, cv::Size(5 * resize_scale, 1)); cv::morphologyEx(front_thre, front_thre, cv::MORPH_OPEN, element, cv::Point(-1, -1), 1, cv::BORDER_CONSTANT, cv::Scalar::all(0)); cv::morphologyEx(back_thre, back_thre, cv::MORPH_OPEN, element, cv::Point(-1, -1), 1, cv::BORDER_CONSTANT, cv::Scalar::all(0)); #ifdef DRAW_PIC cv::imwrite("front_thre2.jpg", front_thre); cv::imwrite("back_thre2.jpg", back_thre); #endif //反面二值化图像水平翻转 cv::flip(back_thre, back_thre, 1); //1:Horizontal //正反面图像寻边 std::vector> contours_front, contours_back; std::vector b1_front, b1_back; hg::findContours(front_thre.clone(), contours_front, b1_front, cv::RETR_CCOMP); hg::findContours(back_thre.clone(), contours_back, b1_back, cv::RETR_CCOMP); ////提取正反面图像最大轮廓 //for (size_t i = 0; i < contours_front.size(); i++) // if (contours_front[i].size() < MIN_CONTOUR_SIZE) // { // contours_front.erase(contours_front.begin() + i); // b1_front.erase(b1_front.begin() + i); // i--; // } //for (size_t i = 0; i < contours_back.size(); i++) // if (contours_back[i].size() < MIN_CONTOUR_SIZE) // { // contours_back.erase(contours_back.begin() + i); // b1_back.erase(b1_back.begin() + i); // i--; // } std::vector maxContour_front = hg::getMaxContour(contours_front, b1_front); std::vector maxContour_back = hg::getMaxContour(contours_back, b1_back); if (maxContour_front.empty() || maxContour_back.empty()) return; cv::RotatedRect rrect_front = hg::getBoundingRect(maxContour_front); //提取正面最大轮廓的最小外接矩形 cv::RotatedRect rrect_back = hg::getBoundingRect(maxContour_back); //提取反面最大轮廓的最小外接矩形 //如果正反面图像尺寸差异超过20个像素,直接放弃处理 if (cv::abs(rrect_front.size.width - rrect_back.size.width) > 20 || cv::abs(rrect_front.size.height - rrect_back.size.height) > 20) return; //提取正反面图像重叠部分区域 cv::Rect roi_front, roi_back; cv::RotatedRect mask_rotatedRect; getRoi(rrect_front, rrect_back, front.size(), back.size(), roi_front, roi_back, mask_rotatedRect); cv::Mat roiMat_front(front_thre, roi_front); //在正面二值图像中截取重叠部分 cv::Mat roiMat_back(back_thre, roi_back); //在反面二值图像中截取重叠部分 #ifdef DRAW_PIC cv::imwrite("roiMat_front.jpg", roiMat_front); cv::imwrite("roiMat_back.jpg", roiMat_back); #endif //正反面二值图像做或运算,真正镂空区域保留0,其他地方填充为255 cv::Mat mask; cv::bitwise_or(roiMat_front, roiMat_back, mask); //或运算,正反面二值图像重叠 #ifdef DRAW_PIC cv::imwrite("mask1.jpg", mask); #endif //二值图像重叠图像颜色取反,膨胀,提取轮廓 cv::bitwise_not(mask, mask); #ifdef DRAW_PIC cv::imwrite("mask2.jpg", mask); //反色 #endif //为了避免孔洞彻底贯穿纸边,人为绘制纸张轮廓,确保所有孔洞为封闭图形,不会与背景粘连 cv::polylines(mask, hg::getVertices(mask_rotatedRect), true, cv::Scalar(0), LINE_WIDTH * resize_scale); //绘制纸张矩形边缘 #ifdef DRAW_PIC cv::imwrite("mask3.jpg", mask); #endif //膨胀算法,增大孔洞连通区域面积 element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(DILATE_SIZE * resize_scale, DILATE_SIZE * resize_scale)); cv::dilate(mask, mask, element, cv::Point(-1, -1), 1, cv::BORDER_CONSTANT, cv::Scalar(255)); #ifdef DRAW_PIC cv::imwrite("mask4.jpg", mask); #endif //提取重叠图像轮廓 std::vector> contours_mask; std::vector b1_mask; hg::findContours(mask, contours_mask, b1_mask, cv::RETR_TREE); //过滤非孔洞的联通区域 std::vector> hole_contours = filterPoly(contours_mask, b1_mask, mask_rotatedRect, m_edgeScale, m_borderSize * resize_scale); for (size_t i = 0; i < hole_contours.size(); i++) for (size_t j = 0; j < hole_contours[i].size(); j++) hole_contours[i][j] /= resize_scale; cv::Scalar color = getBackGroudColor(front(roi_front), rrect_front.size.area()); roi_front.x /= resize_scale; roi_front.y /= resize_scale; roi_front.width /= resize_scale; roi_front.height /= resize_scale; for (size_t i = 0; i < hole_contours.size(); i++) { std::vector> contourss_temp; contourss_temp.push_back(hole_contours[i]); cv::Mat front_temp = mats[0](roi_front); hg::fillPolys(front_temp, contourss_temp, color); } if (isTwoSide) { int width_ = roi_back.width; roi_back.x = back.cols - roi_back.width - roi_back.x; //因为之前反面图像翻转,所以现在ROI也要进行相应翻转 color = getBackGroudColor(back(roi_back), rrect_back.size.area()); roi_back.x /= resize_scale; roi_back.y /= resize_scale; roi_back.width /= resize_scale; roi_back.height /= resize_scale; hole_contours.clear(); hole_contours = filterPoly(contours_mask, b1_mask, mask_rotatedRect, cv::Vec4f(m_edgeScale[0], m_edgeScale[1], m_edgeScale[2], m_edgeScale[3]), m_borderSize * resize_scale); for (size_t i = 0; i < hole_contours.size(); i++) { std::vector hole_contour; for (size_t j = 0; j < hole_contours[i].size(); j++) { hole_contour.push_back(cv::Point(width_ - hole_contours[i][j].x - 1, hole_contours[i][j].y)); hole_contour[j] /= resize_scale; } std::vector> contours_temp; contours_temp.push_back(hole_contour); cv::Mat back_temp = mats[1](roi_back); hg::fillPolys(back_temp, contours_temp, color); } } #ifdef LOG FileTools::write_log("imgprc.txt", "exit ImageOutHole apply"); #endif // LOG } void CImageApplyOutHole::getRoi(cv::RotatedRect rrect_front, cv::RotatedRect rrect_back, const cv::Size& srcSize_front, const cv::Size& srcSize_back, cv::Rect& roi_front, cv::Rect& roi_back, cv::RotatedRect& mask_rotatedRect) { cv::Rect roi_front_ = rrect_front.boundingRect(); cv::Rect roi_back_ = rrect_back.boundingRect(); cv::Size meanSize = (roi_front_.size() + roi_back_.size()) / 2; roi_front_.x += (roi_front_.width - meanSize.width) / 2; roi_front_.width = meanSize.width; roi_front_.y += (roi_front_.height - meanSize.height) / 2; roi_front_.height = meanSize.height; roi_back_.x += (roi_back_.width - meanSize.width) / 2; roi_back_.width = meanSize.width; roi_back_.y += (roi_back_.height - meanSize.height) / 2; roi_back_.height = meanSize.height; mask_rotatedRect.angle = (rrect_front.angle + rrect_back.angle) / 2; mask_rotatedRect.size = (rrect_front.size + rrect_back.size) / 2.0f; mask_rotatedRect.center = cv::Point2f(roi_front_.size().width + roi_back_.size().width, roi_front_.size().height + roi_back_.size().height) / 4.0f; roi_front = roi_front_ & cv::Rect(cv::Point(0, 0), srcSize_front); roi_back = roi_back_ & cv::Rect(cv::Point(0, 0), srcSize_back); int offset_left_f = roi_front.x - roi_front_.x; int offset_left_b = roi_back.x - roi_back_.x; int offset_top_f = roi_front.y - roi_front_.y; int offset_top_b = roi_back.y - roi_back_.y; int offset_right_f = roi_front_.br().x - roi_front.br().x; int offset_right_b = roi_back_.br().x - roi_back.br().x; int offset_bottom_f = roi_front_.br().y - roi_front.br().y; int offset_bottom_b = roi_back_.br().y - roi_back.br().y; if (offset_left_f > offset_left_b) { roi_back.x += offset_left_f - offset_left_b; roi_back.width -= offset_left_f - offset_left_b; mask_rotatedRect.center.x -= offset_left_f - offset_left_b; } else { roi_front.x += offset_left_b - offset_left_f; roi_front.width -= offset_left_b - offset_left_f; mask_rotatedRect.center.x -= offset_left_b - offset_left_f; } if (offset_top_f > offset_top_b) { roi_back.y += offset_top_f - offset_top_b; roi_back.height -= offset_top_f - offset_top_b; mask_rotatedRect.center.y -= offset_top_f - offset_top_b; } else { roi_front.y += offset_top_b - offset_top_f; roi_front.height -= offset_top_b - offset_top_f; mask_rotatedRect.center.y -= offset_top_b - offset_top_f; } if (offset_right_f > offset_right_b) roi_back.width -= offset_right_f - offset_right_b; else roi_front.width -= offset_right_b - offset_right_f; if (offset_bottom_f > offset_bottom_b) roi_back.height -= offset_bottom_f - offset_bottom_b; else roi_front.height -= offset_bottom_b - offset_bottom_f; } #define SIDE_LENGTH_UP_SCALE 6 #define PI 3.14159265359f std::vector> CImageApplyOutHole::filterPoly(std::vector>& contours, std::vector& m, cv::RotatedRect roi, cv::Vec4f edgeScale, float sideLengthLow) { cv::RotatedRect roi2(roi.center, cv::Size2f(roi.size.width * (1 - edgeScale[2] - edgeScale[3]), roi.size.height * (1 - edgeScale[0] - edgeScale[1])), roi.angle); cv::Point2f offset_0(roi.size.width * (edgeScale[2] - edgeScale[3]) / 2 + roi.center.x, roi.size.height * (edgeScale[0] - edgeScale[1]) / 2 + roi.center.y); roi2.center.x = (offset_0.x - roi.center.x) * cos(roi.angle * PI / 180.0f) - (offset_0.y - roi.center.y) * sin(roi2.angle * PI / 180.0f) + roi.center.x; roi2.center.y = (offset_0.x - roi.center.x) * sin(roi.angle * PI / 180.0f) + (offset_0.y - roi.center.y) * cos(roi2.angle * PI / 180.0f) + roi.center.y; std::vector vertices_roi1 = hg::getVertices(roi); std::vector vertices_roi2 = hg::getVertices(roi2); std::vector> hole_contours; for (size_t i = 0; i < contours.size(); i++) { if (m[i][2] != -1) { contours.erase(contours.begin() + i); m.erase(m.begin() + i); i--; continue; } cv::RotatedRect rrect = hg::getBoundingRect(contours[i]); if (rrect.size.width < sideLengthLow || rrect.size.height < sideLengthLow || rrect.size.width > sideLengthLow * SIDE_LENGTH_UP_SCALE || rrect.size.height > sideLengthLow * SIDE_LENGTH_UP_SCALE) { contours.erase(contours.begin() + i); m.erase(m.begin() + i); i--; continue; } bool enabled = true; for (size_t j = 0, count = contours[i].size(); j < count; j++) { cv::Point p(contours[i][j]); double temp1 = pointPolygonTest(vertices_roi1, p, false); //判断是否在纸张内 1:内;0:上;-1:外 double temp2 = pointPolygonTest(vertices_roi2, p, false); //判断是否在边缘区域内 1:内;0:上;-1:外 //如果在纸张外,或者边缘内,视为非孔洞 if (temp1 <= 0 || temp2 >= 0) { enabled = false; //qDebug() << rrect.center.x << ":" << rrect.center.y << "::::" << rrect.size.width << " - " << rrect.size.height; break; } } if (enabled) hole_contours.push_back(contours[i]); } return hole_contours; } cv::Scalar CImageApplyOutHole::getBackGroudColor(const cv::Mat& image, const std::vector pixelPoints) { if (pixelPoints.empty()) return cv::Scalar(255, 255, 255); int channels = image.channels(); int temp[3] = { 0 }; for (size_t i = 0, length = pixelPoints.size(); i < length; ++i) { int x = cv::min(cv::max(0, pixelPoints[i].x), image.cols - 1); int y = cv::min(cv::max(0, pixelPoints[i].y), image.rows - 1); const unsigned char* ptr = image.ptr(y, x); for (int j = 0; j < channels; ++j) temp[j] += ptr[j]; } return cv::Scalar(temp[0] / static_cast(pixelPoints.size()), temp[1] / static_cast(pixelPoints.size()), temp[2] / static_cast(pixelPoints.size())); } cv::Scalar CImageApplyOutHole::getBackGroudColor(const cv::Mat& image, int total) { if (image.channels() == 3) { cv::Mat image_bgr[3]; cv::split(image, image_bgr); uchar bgr[3]; for (size_t i = 0; i < 3; i++) bgr[i] = getBackGroudChannelMean(image_bgr[i], total); return cv::Scalar(bgr[0], bgr[1], bgr[2]); } else return cv::Scalar::all(getBackGroudChannelMean(image, total)); } uchar CImageApplyOutHole::getBackGroudChannelMean(const cv::Mat& gray, int total) { cv::Mat image_clone; cv::resize(gray, image_clone, cv::Size(), 0.25, 0.25); int threnshold = total / 32; int channels[] = { 0 }; int nHistSize[] = { 256 }; float range[] = { 0, 256 }; const float* fHistRanges[] = { range }; cv::Mat hist; cv::calcHist(&image_clone, 1, channels, cv::Mat(), hist, 1, nHistSize, fHistRanges, true, false); int hist_array[256]; for (int i = 0; i < 256; i++) hist_array[i] = hist.at(i, 0); int length = 1; const int length_max = 255; while (length < length_max) { for (size_t i = 1; i < 256 - length; i++) { int count = 0; uint pixSum = 0; for (size_t j = 0; j < length; j++) { count += hist_array[j + i]; pixSum += hist_array[j + i] * (i + j); } if (count >= threnshold) return pixSum / count; } length++; } return 255; }