0

How can I change a slide in photo to become better automatically using OpenCV or suitable image processing tool? I imagine it needs to detect the projector, auto-contrast the color, perspective warp it to become better.

I use photoshop to manually transform from

enter image description here

to

enter image description here

by using 1)Auto-contrast 2)Perspective Warp in Photoshop

william007
  • 17,375
  • 25
  • 118
  • 194
  • Do you also need to automatically segment the poster, or you can provide the for vertices as input? – Miki Sep 22 '15 at 13:13
  • If you coding you can do it using OpenCV, there is also warp function and you can get lots of tutorials online. Contrast and image adjustment is piece of cake in OpenCV. If you don't know much of coding, go for OpenCV using python. – Pervez Alam Sep 22 '15 at 13:28
  • @Miki, yes I need to automatically segment the poster, I feel that is the most difficult part, how should I achieve that? – william007 Sep 23 '15 at 02:06
  • @PervezAlam What is the specific functions that I need to use to segment out the poster, and perform auto contrast? I can code if needed. – william007 Sep 23 '15 at 02:07
  • Just google, there are plenty of tutorials available. eg http://www.swarthmore.edu/NatSci/mzucker1/opencv-2.4.10-docs/doc/tutorials/imgproc/imgtrans/warp_affine/warp_affine.html To get vertices of slide, you can use hough or edge detection and then find intersection point. Same for contrast adjustment. – Pervez Alam Sep 23 '15 at 07:34
  • You can get the mask of the poster using converting the image to hsv, and then using `inRange` to keep only almost blue values. I already tried that and it worked quite good, but I cannot give you the code right now. Then, after a little processing, Hough transform and compute line intersections, see [here](http://stackoverflow.com/a/32590355/5008845). Then you get the four corners and apply the transformation. I'll give you more details (and code) asap – Miki Sep 23 '15 at 11:43
  • @Miki Really appreciate for useful information, and looking forward to the details and code :) – william007 Sep 24 '15 at 00:27

1 Answers1

1

You can:

1) threshold the HSV image on almost blue colors to get the poster mask:

enter image description here

2) Find the external lines, and their intersections:

enter image description here

3) Apply a perspective transform:

enter image description here

4) Apply some color enhancement. Here I used the equivalent of Matlab imadjust. See here for the porting in OpenCV.

enter image description here

Here the full code. The comments should clarify each step. Please let me know if something is not clear.

#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


void imadjust(const Mat1b& src, Mat1b& dst, int tol = 1, Vec2i in = Vec2i(0, 255), Vec2i out = Vec2i(0, 255))
{
    // src : input CV_8UC1 image
    // dst : output CV_8UC1 imge
    // tol : tolerance, from 0 to 100.
    // in  : src image bounds
    // out : dst image buonds

    dst = src.clone();

    tol = max(0, min(100, tol));

    if (tol > 0)
    {
        // Compute in and out limits

        // Histogram
        vector<int> hist(256, 0);
        for (int r = 0; r < src.rows; ++r) {
            for (int c = 0; c < src.cols; ++c) {
                hist[src(r, c)]++;
            }
        }

        // Cumulative histogram
        vector<int> cum = hist;
        for (int i = 1; i < hist.size(); ++i) {
            cum[i] = cum[i - 1] + hist[i];
        }

        // Compute bounds
        int total = src.rows * src.cols;
        int low_bound = total * tol / 100;
        int upp_bound = total * (100 - tol) / 100;
        in[0] = distance(cum.begin(), lower_bound(cum.begin(), cum.end(), low_bound));
        in[1] = distance(cum.begin(), lower_bound(cum.begin(), cum.end(), upp_bound));

    }

    // Stretching
    float scale = float(out[1] - out[0]) / float(in[1] - in[0]);
    for (int r = 0; r < dst.rows; ++r)
    {
        for (int c = 0; c < dst.cols; ++c)
        {
            int vs = max(src(r, c) - in[0], 0);
            int vd = min(int(vs * scale + 0.5f) + out[0], out[1]);
            dst(r, c) = saturate_cast<uchar>(vd);
        }
    }
}


int main()
{
    // Load image
    Mat3b img = imread("path_to_image");
    Mat3b dbg = img.clone();    // Debug image

    // Convert to HSV
    Mat3b hsv;
    cvtColor(img, hsv, COLOR_BGR2HSV);

    // Threshold on HSV values
    Mat1b mask;
    inRange(hsv, Scalar(100, 140, 120), Scalar(110, 170, 200), mask);

    // Get the external boundaries
    Mat1b top(mask.rows, mask.cols, uchar(0));
    Mat1b bottom(mask.rows, mask.cols, uchar(0));
    Mat1b left(mask.rows, mask.cols, uchar(0));
    Mat1b right(mask.rows, mask.cols, uchar(0));

    for (int r = 0; r < mask.rows; ++r)
    {
        // Find first in row
        for (int c = 0; c < mask.cols; ++c)
        {
            if (mask(r, c))
            {
                left(r, c) = 255;
                break;
            }
        }

        // Find last in row
        for (int c = mask.cols - 1; c >= 0; --c)
        {
            if (mask(r, c))
            {
                right(r, c) = 255;
                break;
            }
        }
    }

    for (int c = 0; c < mask.cols; ++c)
    {
        // Find first in col
        for (int r = 0; r < mask.rows; ++r)
        {
            if (mask(r, c))
            {
                top(r, c) = 255;
                break;
            }
        }

        // Find last in col
        for (int r = mask.rows - 1; r >= 0; --r)
        {
            if (mask(r, c))
            {
                bottom(r, c) = 255;
                break;
            }
        }
    }

    // Find lines
    vector<Vec2f> linesTop, linesBottom, linesLeft, linesRight;
    HoughLines(top, linesTop, 1, CV_PI / 180.0, 100);
    HoughLines(bottom, linesBottom, 1, CV_PI / 180.0, 100);
    HoughLines(left, linesLeft, 1, CV_PI / 180.0, 100);
    HoughLines(right, linesRight, 1, CV_PI / 180.0, 100);


    // Find intersections

    Mat1b maskLines(mask.rows, mask.cols, uchar(0));

    if (linesTop.empty() || linesBottom.empty() || linesLeft.empty() || linesRight.empty())
    {
        cout << "No enough lines detected" << endl;
        return -1;
    }

    // Keep only the first line detected for each side
    vector<Vec2f> lines{ linesTop[0], linesBottom[0], linesLeft[0], linesRight[0] };

    for (size_t i = 0; i < lines.size(); i++)
    {
        float rho = lines[i][0], theta = lines[i][1];

        // Get 2 points on each line
        Point pt1, pt2;
        double a = cos(theta), b = sin(theta);
        double x0 = a*rho, y0 = b*rho;

        pt1.x = cvRound(x0 + 1000 * (-b));
        pt1.y = cvRound(y0 + 1000 * (a));
        pt2.x = cvRound(x0 - 1000 * (-b));
        pt2.y = cvRound(y0 - 1000 * (a));

        // Draw lines
        Mat1b maskCurrentLine(mask.rows, mask.cols, uchar(0));
        line(maskCurrentLine, pt1, pt2, Scalar(1), 1);
        maskLines += maskCurrentLine;

        line(dbg, pt1, pt2, Scalar(0, 0, 255), 3, CV_AA);
    }

    // Keep only intersections
    maskLines = maskLines > 1;

    // Get ordered set of vertices
    vector<Point2f> vertices;

    // Top left
    Mat1b tl(maskLines(Rect(0, 0, mask.cols / 2, mask.rows / 2)));
    for (int r = 0; r < tl.rows; ++r)
    {
        for (int c = 0; c < tl.cols; ++c)
        {
            if (tl(r, c))
            {
                vertices.push_back(Point2f(c, r));
            }
        }
    }

    // Top right
    Mat1b tr(maskLines(Rect(mask.cols / 2, 0, mask.cols / 2, mask.rows / 2)));
    for (int r = 0; r < tr.rows; ++r)
    {
        for (int c = 0; c < tr.cols; ++c)
        {
            if (tr(r, c))
            {
                vertices.push_back(Point2f(mask.cols / 2 + c, r));
            }
        }
    }

    // Bottom right
    Mat1b br(maskLines(Rect(mask.cols / 2, mask.rows / 2, mask.cols / 2, mask.rows / 2)));
    for (int r = 0; r < br.rows; ++r)
    {
        for (int c = 0; c < br.cols; ++c)
        {
            if (br(r, c))
            {
                vertices.push_back(Point2f(mask.cols / 2 + c, mask.rows / 2 + r));
            }
        }
    }

    // Bottom left
    Mat1b bl(maskLines(Rect(0, mask.rows / 2, mask.cols / 2, mask.rows / 2)));
    for (int r = 0; r < bl.rows; ++r)
    {
        for (int c = 0; c < bl.cols; ++c)
        {
            if (bl(r, c))
            {
                vertices.push_back(Point2f(c, mask.rows / 2 + r));
            }
        }
    }

    // Draw vertices
    for (int i = 0; i < vertices.size(); ++i)
    {
        circle(dbg, vertices[i], 7, Scalar(0,255,0), CV_FILLED);
    }


    // Init output image
    Mat3b result(img.rows, img.cols, Vec3b(0, 0, 0));

    // Output vertices
    vector<Point2f> verticesOut = { Point2f(0, 0), Point2f(img.cols, 0), Point2f(img.cols, img.rows), Point2f(0, img.rows) };

    // Get transformation
    Mat M = getPerspectiveTransform(vertices, verticesOut);
    warpPerspective(img, result, M, result.size());

    // Imadjust
    vector<Mat1b> planes;
    split(result, planes);

    for (int i = 0; i < planes.size(); ++i)
    {
        imadjust(planes[i], planes[i]);
    }

    Mat3b adjusted;
    merge(planes, adjusted);

    imshow("Result", result);
    imshow("Adjusted", adjusted);
    waitKey();

    return 0;
}
Community
  • 1
  • 1
Miki
  • 40,887
  • 13
  • 123
  • 202