Ask Your Question

Revision history [back]

How do I eliminate inner filling of letters?

Hi,

I am working on my first ever OpenCV project of 'reading text from image'. The sample image I'm using for testing is here -

image description

And the code,

cv::Mat image;
cv::Mat canny_output;
UIImageToMat(input, image, false);

    //Equalizing color
double alpha = 2.2; //1.0 to 3.0
double beta = 50.0; //1.0 to 100.0
Mat img = Mat::zeros( image.size(), image.type() );
image.convertTo(img, -1, alpha, beta);

    //Reduce noise and sharpen image
int kernel_length = 3;//blur reduces noise
blur(img, img, cv::Size(kernel_length, kernel_length), cv::Point(-1, -1));
GaussianBlur(img, img, cv::Size(kernel_length, kernel_length), 0, 0);
medianBlur(img, img, kernel_length);
    //sharpen
cv::addWeighted(img, 3.0, image, -0.5, 0, image);

    //Histogram Equalization
cvtColor(img, img, COLOR_BGR2GRAY);
adaptiveThreshold(~img, img, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 255, -10);
equalizeHist( img, img );
cvtColor(img, img, COLOR_GRAY2BGR);

    //Local threshold
double maxVal, minVal;
cv::Mat output;
int top, bottom, left , right;
int borderType = cv::BORDER_CONSTANT;
cv::Scalar value;
top = (int) (4); bottom = (int) (4);
left = (int) (4); right = (int) (4);
output = img;
value = 0;
cv::copyMakeBorder(img,output,top,bottom,left,right,borderType,value);

for(int y = 4; y < output.rows - 4; y++) {
    for(int x = 4; x < output.cols - 4; x ++) {
            // apply local ROI
        cv::Mat ROI = output(cv::Rect(cv::Point(x-4,y-4),cv::Size(9,9)));
        cv::minMaxLoc(ROI,&minVal,&maxVal);    // extract max intensity values in the ROI
        if(img.at<uchar>(cv::Point(x-4,y-4)) >= 0.5*maxVal){    // apply local threshold w.r.t highest intensity level
            output.at<uchar>(cv::Point(x-4,y-4)) = 255;            // change pixel value in mask if true
        }else{
            output.at<uchar>(cv::Point(x-4,y-4)) = 0;
        }
    }
}

    //Canny edge
Canny(img, canny_output, 50, 200, 3);
cv::dilate(canny_output, canny_output, cv::Mat(), cv::Point(1, 1));

Mat structKernel = cv::getStructuringElement(MORPH_RECT, cv::Size(1, 1));
Mat closed;
cv::morphologyEx(canny_output, closed, MORPH_CLOSE, structKernel);
canny_output.convertTo(canny_output, CV_32F);
cv::divide(canny_output, closed, canny_output, 1, CV_32F);
cv::normalize(canny_output, canny_output, 0, 255, NORM_MINMAX);
canny_output.convertTo(canny_output, CV_8UC1);
cv::Size s = canny_output.size();
int rows = s.height;
int cols = s.width;
for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++) {
        Mat block = canny_output.rowRange(i, (i+1)).colRange(j, (j+1));
        threshold(block, block, -1, 255, THRESH_BINARY_INV | THRESH_OTSU);
    }
}
adaptiveThreshold(canny_output, canny_output, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY,75,10);
bitwise_not(canny_output, canny_output);

int divider = 1;
if (canny_output.rows > canny_output.cols) {
    divider = (canny_output.rows / canny_output.cols) * 4.3;
} else {
    divider = (canny_output.cols / canny_output.rows) * 4.3;
}

    //Remove unwanted horizontal, vertical lines
Mat horizontal = canny_output.clone();
int horizontalsize = horizontal.cols / divider;
Mat horizontalStructure = getStructuringElement(MORPH_RECT, cv::Size(horizontalsize,1));
erode(horizontal, horizontal, horizontalStructure, cv::Point(-1, -1));
dilate(horizontal, horizontal, horizontalStructure, cv::Point(-1, -1));
cv::bitwise_xor(canny_output, horizontal, canny_output);

Mat vertical = canny_output.clone();
int verticalsize = vertical.rows / divider;
Mat verticalStructure = getStructuringElement(MORPH_RECT, cv::Size( 1,verticalsize));
erode(vertical, vertical, verticalStructure, cv::Point(-1, -1));
dilate(vertical, vertical, verticalStructure, cv::Point(-1, -1));
cv::bitwise_xor(canny_output, vertical, canny_output);
cv::bitwise_not(canny_output, canny_output);
    // Step 1
Mat edges;
adaptiveThreshold(canny_output, edges, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, -2);
    // Step 2
Mat kernel = Mat::ones(2, 2, CV_8UC1);
dilate(edges, edges, kernel);
    // Step 3
Mat smooth;
canny_output.copyTo(smooth);
    // Step 4
blur(smooth, smooth, cv::Size(2, 2));
    // Step 5
smooth.copyTo(canny_output, edges);

vector<vector<cv::Point> > contours;
vector<Vec4i> hierarchy;
    //Find contours
findContours( canny_output, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE, cv::Point(0, 0) );

Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3 );
RNG rng = 12345;
for( int i = 0; i< contours.size(); i++ ) {
    if ( hierarchy[i][3] != -1 ) {
        double length = arcLength(contours[i], YES);
        Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
        drawContours( drawing, contours, i, color, -1, 8, vector<Vec4i>(), 0, cv::Point() );
    }
}
return MatToUIImage(drawing);

And the result,

image description

As you can see, the holes in letters such as O, B, etc are also getting filled. How can I prevent it? I believe it is something related to filling contours. But I quite couldn't get it right. Please advice me a fix for this.

Thanks.

How do I eliminate inner filling of letters?

Hi,

I am working on my first ever OpenCV project of 'reading text from image'. The sample image I'm using for testing is here -

image description

And the code,

cv::Mat image;
cv::Mat canny_output;
UIImageToMat(input, image, false);

    //Equalizing color
double alpha = 2.2; //1.0 to 3.0
double beta = 50.0; //1.0 to 100.0
Mat img = Mat::zeros( image.size(), image.type() );
image.convertTo(img, -1, alpha, beta);

    //Reduce noise and sharpen image
int kernel_length = 3;//blur reduces noise
blur(img, img, cv::Size(kernel_length, kernel_length), cv::Point(-1, -1));
GaussianBlur(img, img, cv::Size(kernel_length, kernel_length), 0, 0);
medianBlur(img, img, kernel_length);
    //sharpen
cv::addWeighted(img, 3.0, image, -0.5, 0, image);

    //Histogram Equalization
cvtColor(img, img, COLOR_BGR2GRAY);
adaptiveThreshold(~img, img, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 255, -10);
equalizeHist( img, img );
cvtColor(img, img, COLOR_GRAY2BGR);

    //Local threshold
double maxVal, minVal;
cv::Mat output;
int top, bottom, left , right;
int borderType = cv::BORDER_CONSTANT;
cv::Scalar value;
top = (int) (4); bottom = (int) (4);
left = (int) (4); right = (int) (4);
output = img;
value = 0;
cv::copyMakeBorder(img,output,top,bottom,left,right,borderType,value);

for(int y = 4; y < output.rows - 4; y++) {
    for(int x = 4; x < output.cols - 4; x ++) {
            // apply local ROI
        cv::Mat ROI = output(cv::Rect(cv::Point(x-4,y-4),cv::Size(9,9)));
        cv::minMaxLoc(ROI,&minVal,&maxVal);    // extract max intensity values in the ROI
        if(img.at<uchar>(cv::Point(x-4,y-4)) >= 0.5*maxVal){    // apply local threshold w.r.t highest intensity level
            output.at<uchar>(cv::Point(x-4,y-4)) = 255;            // change pixel value in mask if true
        }else{
            output.at<uchar>(cv::Point(x-4,y-4)) = 0;
        }
    }
}

    //Canny edge
Canny(img, canny_output, 50, 200, 3);
cv::dilate(canny_output, canny_output, cv::Mat(), cv::Point(1, 1));

Mat structKernel = cv::getStructuringElement(MORPH_RECT, cv::Size(1, 1));
Mat closed;
cv::morphologyEx(canny_output, closed, MORPH_CLOSE, structKernel);
canny_output.convertTo(canny_output, CV_32F);
cv::divide(canny_output, closed, canny_output, 1, CV_32F);
cv::normalize(canny_output, canny_output, 0, 255, NORM_MINMAX);
canny_output.convertTo(canny_output, CV_8UC1);
cv::Size s = canny_output.size();
int rows = s.height;
int cols = s.width;
for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++) {
        Mat block = canny_output.rowRange(i, (i+1)).colRange(j, (j+1));
        threshold(block, block, -1, 255, THRESH_BINARY_INV | THRESH_OTSU);
    }
}
adaptiveThreshold(canny_output, canny_output, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY,75,10);
bitwise_not(canny_output, canny_output);

int divider = 1;
if (canny_output.rows > canny_output.cols) {
    divider = (canny_output.rows / canny_output.cols) * 4.3;
} else {
    divider = (canny_output.cols / canny_output.rows) * 4.3;
}

    //Remove unwanted horizontal, vertical lines
Mat horizontal = canny_output.clone();
int horizontalsize = horizontal.cols / divider;
Mat horizontalStructure = getStructuringElement(MORPH_RECT, cv::Size(horizontalsize,1));
erode(horizontal, horizontal, horizontalStructure, cv::Point(-1, -1));
dilate(horizontal, horizontal, horizontalStructure, cv::Point(-1, -1));
cv::bitwise_xor(canny_output, horizontal, canny_output);

Mat vertical = canny_output.clone();
int verticalsize = vertical.rows / divider;
Mat verticalStructure = getStructuringElement(MORPH_RECT, cv::Size( 1,verticalsize));
erode(vertical, vertical, verticalStructure, cv::Point(-1, -1));
dilate(vertical, vertical, verticalStructure, cv::Point(-1, -1));
cv::bitwise_xor(canny_output, vertical, canny_output);
cv::bitwise_not(canny_output, canny_output);
    // Step 1
Mat edges;
adaptiveThreshold(canny_output, edges, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, -2);
    // Step 2
Mat kernel = Mat::ones(2, 2, CV_8UC1);
dilate(edges, edges, kernel);
    // Step 3
Mat smooth;
canny_output.copyTo(smooth);
    // Step 4
blur(smooth, smooth, cv::Size(2, 2));
    // Step 5
smooth.copyTo(canny_output, edges);

vector<vector<cv::Point> > contours;
vector<Vec4i> hierarchy;
    //Find contours
findContours( canny_output, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE, cv::Point(0, 0) );

Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3 );
RNG rng = 12345;
for( int i = 0; i< contours.size(); i++ ) {
    if ( hierarchy[i][3] != -1 ) {
        double length = arcLength(contours[i], YES);
        Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
        drawContours( drawing, contours, i, color, -1, 8, vector<Vec4i>(), 0, cv::Point() );
    }
}
return MatToUIImage(drawing);

And the result,

image description

As you can see, the holes in letters such as O, B, etc are also getting filled. How can I prevent it? I believe it is something related to filling contours. But I quite couldn't get it right. Please advice me a fix for this.

Thanks.