用OpenCV实现Photoshop曲线功能的'在图像中取样设置黑场'

来源:互联网 发布:网络销售项目计划书 编辑:程序博客网 时间:2024/06/08 19:32
某位朋友在实验中遇到一个任务,具体来说,就是给定一张含有细胞组织采样的显微图片,手动用photoshop去除这张图片的灰色背景,从而获得背景比较干净的图片方便细胞计数 。原始实验图片如下:

这张图片中图片有明显的灰蒙蒙的背景噪声,我们的目的就是想把背景的灰色信息扔掉。

方法一,分别对RGB各个图层的每个像素进行操作,统一的减少背景色像素点的值,让背景色接近0. 
这种方式有2个问题:所有的像素值都减少的话,图像的对比度会降低(因为获得的图片像素值范围小于0到255);另一方面图片的整体亮度会降低;
方法三中我们会改进它,让像素值空间重新映射到0到255.

方法二,对每个图层进行阈值化。
经过这种方式处理后,非噪声点的像素值没有变化,但是因为像素值的断点导致图片颜色不再平滑,会出现非常多的噪点,结果十分不理想。
如下图,方法二抛弃。


方法三,使用Photoshop曲线功能的'在图像中取样设置黑场'。
这个功能的文字描述看起来让人摸不着头脑。实际上操作可以看成方法一减背景法的改良版。通过曲线窗口的结果我们可以看出,这个功能就是帮我们将减去背景色的像素进行了一个重新映射。
具体来说就是,点击一个像素点后,记录这个像素点的RGB值,对全图进行处理。三通道分别操作,每个低于这个点相应值的,就设成0;其他的点等比例拉伸成0~255范围。
从数学上来看,默认的曲线是 y = x, 所以曲线是一条对角线;进行了这个操作之后,我们变化了这条曲线的斜率,但依然让值域扩展到0到255。注意,进行曲线调整后,大部分像素值还是会降低,所以获得的结果依然比输入的图片要暗一些。


有了上述分析,编写代码的方法就很清晰了,代码附录。为了方便朋友使用,加入了一些用户交互操作的代码。
这些功能包括,
  • 循环读取当前目录中的.tif图片
  • 显示当前图片,支持手动选择背景像素点
  • 在另一个窗口加入3个跟踪控制条,用来手动调节各通道背景像素的值
  • 7个操作按键,分别是:
    • q: 退出程序
    • r: 重置当前图片,包括控制条的位置和背景像素值
    • 1, 2, 3, 4: 显示RGB图像,或者是单独显示单图层图像。注意,显示单图层图像时,图像是黑白的。
    • 空格: 保存当前结果到result目录,并跳转到下一张图片。
Jan 10, 2013
Peng

#include <stdio.h>#include <iostream>#include <windows.h>#include <tchar.h>#include <stdio.h> #include "opencv2/imgproc/imgproc.hpp"#include "opencv2/highgui/highgui.hpp" using namespace cv;using namespace std; const string winName = "Image", winCtrl = "control panel", rBar = "R", gBar = "G", bBar = "B"; Mat oimg, img, smat, lut;const int default_bpos = 100;int sR = default_bpos, sG = default_bpos, sB = default_bpos;int thresR = 0, thresG = 0, thresB = 0;float scale = 0.5f; // reset trackbar positions and background pixel thresholdsvoid reset(){    thresR = 0;    thresG = 0;    thresB = 0;    setTrackbarPos(rBar, winCtrl, default_bpos);    setTrackbarPos(gBar, winCtrl, default_bpos);    setTrackbarPos(bBar, winCtrl, default_bpos);} // construct lookup table for input r, g, b value void makeLUT(Mat& lut, int r, int g, int b){    printf("r: %d g: %d b: %d\n", r, g, b);    lut.create(1, 256, CV_8UC3);    for(int i = 0; i < 256; i ++)    {        Vec3b& bgr = lut.at<Vec3b>(i);        if(i < b)        {            bgr[0] = 0;        }        else        {            bgr[0] = saturate_cast<uchar>((i - b) * 255 / (255 - b));        }        if(i < g)        {            bgr[1] = 0;        }        else        {            bgr[1] = saturate_cast<uchar>((i - g) * 255 / (255 - g));        }        if(i < r)        {            bgr[2] = 0;        }        else        {            bgr[2] = saturate_cast<uchar>((i - r) * 255 / (255 - r));        }    }}static void renew_window(int value = 0, void * ptr = 0); // handle trackbar position changestatic void renew_window(int, void *){    makeLUT(lut, thresR - sR + default_bpos, thresG - sG + default_bpos, thresB - sB + default_bpos);    LUT(oimg, lut, img);    resize(img, smat, Size(), scale, scale);    imshow(winName, smat);} static void onMouse( int event, int x, int y, int, void* ){    if( event != CV_EVENT_LBUTTONDOWN )        return;    // input image is scaled down for ease of usage, but we need to scale up to get the actual point position    float fx = 1.0f / scale * x;    float fy = 1.0f / scale * y;    Point point = Point((int)fx,(int)fy);    Vec3b bgr = oimg.at<Vec3b>(point);    thresR = bgr[2];    thresG = bgr[1];    thresB = bgr[0];    renew_window();} // use mixChannels to extract the interest image planevoid show_plane(int key){    int ch[] = {0, 0};    Mat plane(smat.size(), CV_8U);    string txt;    switch(key)    {    case '1':        smat.copyTo(plane);        txt = "RGB image";        break;    case '2': //r        ch[0] = 2;        mixChannels(&smat, 1, &plane, 1, ch, 1);        txt = "R plane";        break;    case '3': //g        ch[0] = 1;        mixChannels(&smat, 1, &plane, 1, ch, 1);        txt = "G plane";        break;    case '4': //b        ch[0] = 0;        mixChannels(&smat, 1, &plane, 1, ch, 1);        txt = "B plane";        break;    }    putText(plane, txt, Point(50, 50), FONT_HERSHEY_COMPLEX, 0.5, Scalar::all(255), 1, CV_AA);    imshow(winName, plane);} void help(){    printf ("==================================================\n");    printf ("[Background remover]\n");    printf ("All input images are scaled down to %2.1f%%\n", scale * 100);    printf ("Hint:\n");    printf ("  q: quit\n");    printf ("  r: reset current image\n");    printf ("  1: show orignal image\n");    printf ("  2: show 'R' plane\n");    printf ("  3: show 'G' plane\n");    printf ("  4: show 'B' plane\n");    printf ("  spacebar: save current and proceed to the next image\n");    printf ("==================================================\n\n");} void main(int , char **){    help();    WIN32_FIND_DATA FindFileData;    HANDLE hFind;     hFind = FindFirstFile("./*.tif", &FindFileData);    if (hFind == INVALID_HANDLE_VALUE)     {        printf ("No tif file found!\n");        return;    }     namedWindow( winCtrl, CV_WINDOW_NORMAL );    namedWindow( winName, CV_GUI_NORMAL | CV_WINDOW_AUTOSIZE );    CreateDirectory("./result", NULL);     setMouseCallback( winName, onMouse, 0 );    Mat dummy_img(1, 300, CV_8U, Scalar::all(255));      createTrackbar( rBar, winCtrl, &sR, 200, renew_window);     createTrackbar( gBar, winCtrl, &sG, 200, renew_window);     createTrackbar( bBar, winCtrl, &sB, 200, renew_window);     imshow(winCtrl, dummy_img);     moveWindow(winName, 0, 0);    moveWindow(winCtrl, 0, 0);    int key = 0;    int i = 0;    do    {        _tprintf (TEXT("Attemp %d: %s\n"),             i + 1, FindFileData.cFileName);        oimg = imread(FindFileData.cFileName);        if( oimg.empty() )        {            cout << "Failed to load " << FindFileData.cFileName << endl;            continue;        }_reset:        reset();        oimg.copyTo(img); // we need this if the user press spacebar straightaway        renew_window();        key = 0;_wait:        switch(key)        {        case ' ':            break;        case 'r':            goto _reset;        case 'q':            goto _close;        case '1':        case '2':        case '3':        case '4':            show_plane(key);        default:            key = waitKey();            goto _wait;        }        string save_name = string("./result/") + string(FindFileData.cFileName) + ".jpg";        imwrite(save_name, img);        i ++;    } while( FindNextFile(hFind, &FindFileData) );_close:    FindClose(hFind);}


原创粉丝点击