dcv icon indicating copy to clipboard operation
dcv copied to clipboard

Is it's possible to remove background with dcv?

Open bubnenkoff opened this issue 8 years ago • 7 comments

I have got a lot of images in jpg format with black background. Is it's possible to find background and remove it (make transparent) with current version of DCV? PNG as output would be enough for me (in ideally wepb, but D do not have native encoder/decoder :( )

If not DCV, maybe there is any better tools for this task?

101_005329_2_0_03

bubnenkoff avatar Aug 09 '16 08:08 bubnenkoff

There are no background extraction methods implemented, such as this, but seems like it would be pretty trivial to remove black background in the example image you've given with DCV.

I cannot test it out at the moment, but I believe basic thresholding would work:

Image image = imread("your_image.png");

auto threshValue = 10; // some low value, experiment on this...
auto alpha = image
             .sliced[0 .. $, 0 .. $, 1] // take green channel (most probably will work fine)
             .threshold!ubyte(10); // pixel with value below the 10 will be 0, above will be 255.

// if your image is RGB, convert to RGBA and apply alpha
Image rgba = new Image(image.width, image.height, ImageFormat.IF_RGBA, BitDepth.BD_8);
rbga.sliced[0 .. $, 0 .. $, 0 .. 3][] = image.sliced[]; // copy pixel values
rgba.sliced[0 .. $, 0 .. $, 3][] = alpha[]; // copy alpha values

Note that threshValue can be adjusted - I only gave it value of 10 as a guess, but you can sample your background, and use values more close to it. Also, you can experiment with ranged threshold, where you give lower and upper threshold value.

This is basic idea I would use to solve your problem. Hope I'm of any help, and please let me know if there's anything other that I may help you with.

ljubobratovicrelja avatar Aug 09 '16 09:08 ljubobratovicrelja

(in ideally wepb, but D do not have native encoder/decoder :( )

Regarding this matter, my plan is to bind C libraries for image i/o, so if there's a nice wepb C library out there, we should support this format in future.

ljubobratovicrelja avatar Aug 09 '16 09:08 ljubobratovicrelja

Hey, one more note- since there's a certain amount of color channel shift in the example image, I suppose taking green channel for thresholding might not work well (it would most probably cut out blue and red regions on borders). Consider using rgb2gray with Rgb2GrayConvertion.MEAN, in the case green channel does not give you desirable results.

ljubobratovicrelja avatar Aug 09 '16 09:08 ljubobratovicrelja

Just found some time, and wanted to try out the solution I proposed above. I got following result (changed only the threshValue, and used rgb2gray):

As said, you may tweak params a bit, but that's about it. Hope this solves your problem.

Cheers!

ljubobratovicrelja avatar Aug 09 '16 17:08 ljubobratovicrelja

@ljubobratovicrelja very cool! Big thanks! I will try it when finish other work. Could you add this code to example? I think a lot of people have similar task.

Later I will test it on this image: 201_000606_0_0_12

bubnenkoff avatar Aug 10 '16 07:08 bubnenkoff

Could you add this code to example?

Sure! Thanks for suggestion.

Well, this next example image looks bit more challenging. It has lot of underexposed areas in its foreground. I'm quite sure thresholding method won't work that well- you'd have holes in those dark areas in the middle. But you could try using closing morphological operation iteratively (with smaller box kernel, say with size 5) to stitch them up:

// ... thesholding code shown above

foreach(iter; 0 .. iterations) // chose number of iterations empirically (easiest solution)
{
    alpha = alpha.close(boxKernel!ubyte(5, 5));
}

Again, I'm can't test this out at the moment, but will do later today.

ljubobratovicrelja avatar Aug 10 '16 08:08 ljubobratovicrelja

Well, seems like the closing op won't do much good either. Here is the alpha result from only threshold on the second image:

alpha1

And, here is with closing:

alpha2

In cases like this, maybe taking a convex hull of all white regions would solve the case- it's the first thing that comes to mind. You can also try with segmentation algorithms like watershed, but since those dark regions are so large, I'm afraid you'd get similar artifacts. Do these images maybe come in 16bit? It would help significantly if you could use 16 bit, over 8 bit for this task.

Btw, here is the code I've been getting these results with:

    Image image = imread("5c9e2f14-5ee4-11e6-961c-eb8198d09c21.jpg");

    auto alpha = image
        .sliced
        .rgb2gray(emptySlice!(2, ubyte), Rgb2GrayConvertion.MEAN)
        .asType!float
        .conv(gaussian!float(3, 5, 5))
        .threshold!ubyte(1); // pixel with value below the 10 will be 0, above will be 255.

    alpha.imwrite(ImageFormat.IF_MONO, "alpha1.png");

    alpha = alpha.close(radialKernel!ubyte(51));

    alpha.imwrite(ImageFormat.IF_MONO, "alpha2.png");

    // apply alpha
    Image rgba = new Image(image.width, image.height, ImageFormat.IF_RGB_ALPHA, BitDepth.BD_8);
    rgba.sliced[0 .. $, 0 .. $, 0 .. 3][] = image.sliced[]; // copy pixel values
    rgba.sliced[0 .. $, 0 .. $, 3][] = alpha[]; // copy alpha values

    rgba.imwrite("im.png");

ljubobratovicrelja avatar Aug 10 '16 21:08 ljubobratovicrelja