Re-coloring image preserving luminance - opencv

Given an input image, I was thinking about how the image could be re-colored to a single new color keeping the luminance of the image similar to what it was earlier.
So I wrote a naive code:
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <bits/stdc++.h>
using namespace cv;
using namespace std;
int main() {
Mat img = imread("test2.png", 1);
Mat hsv; cvtColor(img, hsv, CV_BGR2HSV);
vector<Mat > channels;split(hsv, channels);
Mat luminance; channels[2].copyTo(luminance);
Mat res; img.copyTo(res);
channels.clear(); split(res, channels);
for (int i = 0; i<res.rows; i++) {
for (int j = 0; j<res.cols; j++) {
channels[0].at<uchar>(i, j) = 0;
channels[1].at<uchar>(i, j) = 0;
channels[2].at<uchar>(i, j) = 255;
}
}
merge(channels, res);
cvtColor(res, hsv, CV_BGR2HSV);
channels.clear(); split(hsv, channels);
luminance.copyTo(channels[2]);
merge(channels, res);
cvtColor(res, res, CV_HSV2BGR);
imwrite("result.png", res);
return 0;
}
What I actually did is just extracted the luminance map of the original image, then created an image with the color I want it to be in, and replaced the luminance map of this output image with the luminance map of input image.
But resultant image seems to be darker in shade. Is there any better way to do this?
Input image:
Resulting image:

I think you are looking for "tinting". I don't have any references for how you to do it with OpenCV but there is a description in Anthony Thyssen's excellent ImageMagick notes here - search for the word "somehow". Maybe you can adapt it to OpenCV, if the effect is what you seek.
At the command-line, with ImageMagick, I did this:
convert drop.png -fill red -tint 50% result.jpg

Here is another way in Imagemagick.
convert \( input.png -colorspace gray \) \( -clone 0 -fill red -colorize 100 \) \( -clone 0 \) -compose colorize -composite result1.png
convert \( input.png -colorspace lab -channel red -separate \) \( -clone 0 -fill red -colorize 100 \) \( -clone 0 \) -compose colorize -composite result2.png
convert \( input.png -colorspace hsi -channel blue -separate \) \( -clone 0 -fill red -colorize 100 \) \( -clone 0 \) -compose colorize -composite result3.png
Choose what colorspace represents the intensity/luminance you want to use. See my script, color2gray at http://www.fmwconcepts.com/imagemagick/color2gray/index.php to see what different colorspace intensity/luminance show as gray.

Related

Combining 3 imagemagick cli parameters into one ImageMagick.NET code

Hey all I have these 3 imageMagick scripts (command line arguments) that I am trying to combine into Imagemagick.NET code.
First (merging 2 images together):
convert ^
( testingl.jpg -resize 610x440^^ -gravity West -extent 1080x440 ) ^
( testingr.jpg -resize 610x440^^ -gravity East -extent 1080x440 ) ^
blend_mask.png -blur 0x7 ^
-composite bothImagesMerged.jpg
Second (Create 2 round objects with photo inside):
convert lisa.jpg -resize 100x100! ^
null: ( -size 100x100 xc:black -fill white -draw "circle 50,50 50,88" ) ^
-alpha off -compose copy_opacity -layers composite ^
null: ( -size 100x100 xc:"graya(100%,0)" -fill black -draw "circle 50,50 50,90" -blur 0x5 ) ^
-compose dstover -layers composite ^
-background none -gravity center +smush -25+0 ^
roundImageLisa.png
convert homer.jpg -resize 100x100! ^
null: ( -size 100x100 xc:black -fill white -draw "circle 50,50 50,88" ) ^
-alpha off -compose copy_opacity -layers composite ^
null: ( -size 100x100 xc:"graya(100%,0)" -fill black -draw "circle 50,50 50,90" -blur 0x5 ) ^
-compose dstover -layers composite ^
-background none -gravity center +smush -25+0 ^
roundImageHomer.png
Third (Write text on top of photo):
convert -size 1080x440 xc:none -gravity center ^
-font arial -pointsize 40 ^
-stroke black -strokewidth 2 -annotate +-330+-150 "Lisa Simpson" ^
-stroke black -strokewidth 2 -annotate +330+-150 "Homer Simpson" ^
-background none -shadow 520x3+0+0 +repage ^
-stroke none -fill white -annotate +-330+-150 "Lisa Simpson" ^
-stroke none -fill white -annotate +330+-150 "Homer Simpson" ^
bothImagesMerged.jpg +swap -gravity center -geometry +0-3 ^
-composite textOverImg.jpg
If I was able to combine all 3 of those then the output would look something like this:
I've tried to put all of them into a one-liner but can not seem to find the correct way (order mainly) into doing so.
I do have some code that produces the round images in C#:
Bitmap bitmap = new Bitmap("lisa.jpg");
MagickImageCollection images = new MagickImageCollection();
IMagickImage roundImg = null;
IMagickImage mask = new MagickImage("xc:black", 100, 100);
mask.Settings.FillColor = MagickColors.White;
mask.Draw(new DrawableCircle(50, 50, 50, 90));
mask.HasAlpha = false;
roundImg = new MagickImage(bitmap);
roundImg.Resize(100, 100);
roundImg.Composite(mask, CompositeOperator.CopyAlpha);
roundImg.Draw(new DrawableStrokeColor(MagickColors.Black),
new DrawableStrokeWidth(1),
new DrawableFillColor(MagickColors.None),
new DrawableCircle(50, 50, 50, 90));
IMagickImage shadow = new MagickImage("xc:none", 100, 100);
shadow.Settings.FillColor = MagickColors.Black;
shadow.Draw(new DrawableCircle(50, 50, 50, 90));
shadow.Blur(0, 5);
roundImg.Composite(shadow, CompositeOperator.DstOver);
images.Add(roundImg);
images.First().BackgroundColor = MagickColors.None;
IMagickImage result = new MagickImage();
result = images.SmushHorizontal(-35);
result.Write("lisa_round.png");
mask.Dispose();
shadow.Dispose();
result.Dispose();
images.Dispose();
Assistance would be great! #fmw42

ImageMagick Ouput GIF

How can I make this output a GIF instead of a PNG:
convert GIF.gif ( +clone -alpha extract -virtual-pixel black -spread 10 -blur 0x3 -threshold 50% -spread 1 -blur 0x.7 ) -alpha off -compose Copy_Opacity -composite torn_paper.png

Imagemagick Spotify-like duotone overlay

Is it possible to achieve this degree of color manipulation using pure imagemagick commands?
I suppose it might be done with level-color and a special transparent png for shapes.
Also is it required to change original colors to b/w before any manipulations with color levels?
This is one way to do it in Imagemagick. Yes, you need mask images. You may or may not want to convert your input to grayscale. But in this case, I suspect you want to start with grayscale.
Here is my input:
Here I create 4 simple non-overlapping rectangular region binary masks. But in your example it looks like part of the yellow overlaps with the green to make the orange color
convert -size 100x299 xc:white -size 300x299 xc:black +append mask1.png
convert -size 100x299 xc:black -size 100x299 xc:white -size 200x299 xc:black +append mask2.png
convert -size 200x299 xc:black -size 100x299 xc:white -size 100x299 xc:black +append mask3.png
convert -size 300x299 xc:black -size 100x299 xc:white +append mask4.png
Then I create 4 different color images the same size as the input and composite them successively with the one each of the masks saving over the in-memory image (mpr:img) that I created from the grayscale image.
compose method: blend (50%-50%)
(Note other blend ratios can be applied if desired using -define compose:args=50,50 by changing the two numbers, but keep the total = 100)
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img -fill red -colorize 100 \) mask1.png -compose blend -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill green1 -colorize 100 \) mask2.png -compose blend -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill blue -colorize 100 \) mask3.png -compose blend -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill yellow -colorize 100 \) mask4.png -compose blend -composite \
result1.jpg
compose method: multiply
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img -fill red -colorize 100 \) mask1.png -compose multiply -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill green1 -colorize 100 \) mask2.png -compose multiply -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill blue -colorize 100 \) mask3.png -compose multiply -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill yellow -colorize 100 \) mask4.png -compose multiply -composite \
result2.jpg
compose method: overlay
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img -fill red -colorize 100 \) mask1.png -compose overlay -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill green1 -colorize 100 \) mask2.png -compose overlay -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill blue -colorize 100 \) mask3.png -compose overlay -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill yellow -colorize 100 \) mask4.png -compose overlay -composite \
result3.jpg
compose method: colorize
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img -fill red -colorize 100 \) mask1.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill green1 -colorize 100 \) mask2.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill blue -colorize 100 \) mask3.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill yellow -colorize 100 \) mask4.png -compose colorize -composite \
result4.jpg
There are many other compose methods that you could try. See https://imagemagick.org/Usage/compose/
You can also do the same using +level-colors with either black or white as the second color.
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,red \) mask1.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,green1 \) mask2.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,blue \) mask3.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,yellow \) mask4.png -compose colorize -composite \
result5.jpg
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img +level-colors red,white \) mask1.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors green1,white \) mask2.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors blue,white \) mask3.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors yellow,white \) mask4.png -compose colorize -composite \
result6.jpg
You can do it also with -tint.
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img -fill red -tint 100 \) mask1.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill green1 -tint 100 \) mask2.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill blue -tint 100 \) mask3.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill yellow -tint 100 \) mask4.png -compose colorize -composite \
result7.jpg
The results differ with different compose methods. Here are that last 3 with compose over rather than compose colorize:
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,red \) mask1.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,green1 \) mask2.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,blue \) mask3.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,yellow \) mask4.png -compose over -composite \
result5b.jpg
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img +level-colors red,white \) mask1.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors green1,white \) mask2.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors blue,white \) mask3.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors yellow,white \) mask4.png -compose over -composite \
result6b.jpg
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img -fill red -tint 100 \) mask1.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill green1 -tint 100 \) mask2.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill blue -tint 100 \) mask3.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill yellow -tint 100 \) mask4.png -compose over -composite \
result7b.jpg
See also https://imagemagick.org/Usage/color_mods/#duotone for a method using a custom colored look-up table image with the -clut function. Here is that method:
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img \( -size 1x1 xc:black xc:red xc:white +append -size 1x256 gradient: -rotate 90 +swap -interpolate Bicubic -clut \) -interpolate Bicubic -clut \) mask1.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img \( -size 1x1 xc:black xc:green1 xc:white +append -size 1x256 gradient: -rotate 90 +swap -interpolate Bicubic -clut \) -interpolate Bicubic -clut \) mask2.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img \( -size 1x1 xc:black xc:blue xc:white +append -size 1x256 gradient: -rotate 90 +swap -interpolate Bicubic -clut \) -interpolate Bicubic -clut \) mask3.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img \( -size 1x1 xc:black xc:yellow xc:white +append -size 1x256 gradient: -rotate 90 +swap -interpolate Bicubic -clut \) -interpolate Bicubic -clut \) mask4.png -compose over -composite \
result8.jpg
This appears to me to be the closest colorization result to that which was used in your original example.

Imagemagick processes hang forever on Loopbackjs

I got this code to check if an image file contains blue pixels with Imagemagick and counting them - then saving the result.
It works well, but it seems like many processes of Imagemagick hang forever on the server and are making it very slow.
Is there a way to improve this code and avoid this trouble?
module.exports = function (File) {
File.observe('after save', function countPixels(ctx, next) {
if (ctx.instance && !ctx.instance.blue_pixels) {
var exec = require('child_process').exec;
// Convert file to retrieve only blue pixels:
exec('convert ' + ctx.instance.path + ' -fx "u.b>(u.g+0.2)&&u.b>(u.r+0.2)&&saturation>0.6" -format "%[fx:mean*w*h]" info:',
function (error, stdout, stderr) {
if (error !== null) {
return next(error);
} else {
ctx.instance.blue_pixels = stdout;
File.upsert(ctx.instance);
}
});
}
next();
});
};
The -fx operator that you are using is notoriously slow - especially for large images. I had a try at casting the same formula using faster methods which may help you. So, I made a sample image:
convert xc:red xc:lime -append \( xc:blue xc:cyan -append \) +append -resize 256x256! input.png
And then rewrote your expression like this:
convert input.png \
\( -clone 0 -separate -delete 0 -evaluate-sequence subtract -threshold 20% -write BG.png \) \
\( -clone 0 -separate -delete 1 -evaluate-sequence subtract -threshold 20% -write BR.png \) \
\( -clone 0 -colorspace hsl -separate -delete 0,2 -threshold 60% -write S.png \) \
-delete 0 \
-evaluate-sequence min result.png
Note that the -write XYZ.png are just debug statements that can be removed.
Basically, I am building a mask of all pixels that meet your criteria and making them white, and making all the ones that don't match your criteria black and at the end, I run -evaluate-sequence min to find the minimum of each pixel so that all three of your conditions must effectively be met:
that blue exceeds green by 20%
that blue exceeds red by 20%
that the saturation exceeds 60%
The -separate -delete N splits your image into RGB channels and then deletes one of the resulting channels, so if I -delete 1 (that is the Green channel) I am left with Red and Blue. Here are the intermediate, debug images. The first one is the condition Blue exceeds Red by 20%:
Then that Blue exceeds Green by 20%:
And finally that the Saturation exceeds 60%:
And then the result:
You'll need to put your -format "%[fx:mean*w*h]" info: back on the end in place of the output image name to get the count of saturated blue pixels.
If I run your command:
convert input.png -fx "u.b>(u.g+0.2)&&u.b>(u.r+0.2)&&saturation>0.6" result.png
My brain is not quite right today, so please run some checks - I may have something back-to-front somewhere!
As a benchmark, on a 10,000x10,000 pixel PNG, my code runs in 30 seconds, whereas the -fx equivalent takes nearly 7 minutes.
I don't know imagelagick part. But for node part I see that you call next non regarding to imagemgick opertion.
module.exports = function (File) {
File.observe('after save', function countPixels(ctx, next) {
if (ctx.instance && !ctx.instance.blue_pixels) {
var exec = require('child_process').exec;
// Convert file to retrieve only blue pixels:
exec('convert ' + ctx.instance.path + ' -fx "u.b>(u.g+0.2)&&u.b>(u.r+0.2)&&saturation>0.6" -format "%[fx:mean*w*h]" info:',
function (error, stdout, stderr) {
if (error !== null) {
return next(error);
} else {
ctx.instance.blue_pixels = stdout;
File.upsert(ctx.instance);
next();
}
});
}
else{next();}
//next(); //run next hook ASAP (before imagemagick returns back the result)
});
};

ImageMagick drawing a series of images with growing glow effect

I'm learning ImageMagick. I want to create (with ImageMagick) a series of images
that after using the command
convert -delay 0 -loop 0 frame*.gif final.gif
gives the result like the attached animated gif.
I want to program the series of commands myself, but I need a hint for which effects and drawing instructions will give me the most similar result, so I'm looking for something like:
draw a circle
blur it
save the frame
increase the radius of the circle
repeat
but, probably the above is not enough.
Is this question very vague or can somebody give me a hint?
If you are in a shell environment (OSX or Linux) you could do something like this, which creates a series of blurred circles in ever-increasing radii and saves the image in appropriately named files...
for RAD in {10,20,30,40,50,60,70,80,90}; do convert -size 400x300 xc:black -fill cyan -stroke cyan -draw "translate 200,150 circle 0,0 $RAD,0" -blur 0x8 blur_circle$RAD.gif; done
To get fancier, you could add repeated blur operations, or more blur for the larger radii.
Then, as you suggest convert those to an animated gif:
convert -delay 0 -loop 0 blur_circle*.gif final.gif
EDIT
Working version closer to original "vision"
Here is a version that uses python to generate two circles, whose transparency varies on different time scales. With the current settings, transparency over time looks like this graph, but you can generate any lists of integer values to get different effects:
Here is the image produced by the program:
And here is the program itself:
#! /usr/bin/env python
""" Using imagemagick, generate a series of .gifs with fuzzy circles in them...
When the program is done, create an animated gif with:
convert -delay 0 -loop 0 blur_circle*.gif final.gif
(P.S. I know it is not 'convention' to capitalize variable names, but it makes
it easier to distinguish my definitions from built-ins)"""
import subprocess
import sys
CanvasW = 400
CanvasH = 400
CenterX = int(CanvasW/2)
CenterY = int(CanvasH/2)
InnerDia = 75
OuterDia = 155
BaseNameString = "blur_circles"
# play with overall blur level - 2 to 8
# this is in addition to the distance fuzzing
BlurLevel = '8'
# The following three lists must be the same length
# Transparency levels of the inner circle, ramping up and down
InnerAlphaList = range(0,101,10) + range(90,9,-20) + [5,0,0,0]
print "Inner:",len(InnerAlphaList),InnerAlphaList
# Transparency of the outer circle
OuterAlphaList = range(0,51,8) + range(40,9,-12) + range(8,0,-2) + [0,0,0,0,0,0]
print "Outer:",len(OuterAlphaList), OuterAlphaList
# Add 100 so the file names get sorted properly?
NameNumberList = range(101, 101+len(InnerAlphaList))
# Changing the Euclidaan distance parameters affects how fuzzy the objects are
BaseCommand = '''convert -size {w}x{h} xc:black \
-fill "rgba( 0, 255,255 , {outal:0.1f} )" -stroke none -draw "translate {cenx},{ceny} circle 0,0 {outdia},0" -morphology Distance Euclidean:4,1000\
-fill "rgba( 0, 255,255 , {inal:0.1f} )" -stroke none -draw "translate {cenx},{ceny} circle 0,0 {india},0" -morphology Distance Euclidean:4,500 \
-blur 0x{blur} {basename}_{namenum}.gif'''
sys.stderr.write("Starting imagegen .")
for InAlpha,OutAlpha,NameNumber in zip(InnerAlphaList,OuterAlphaList,NameNumberList):
FormattedCommand = BaseCommand.format(
w = CanvasW, h = CanvasH,
cenx = CenterX, ceny = CenterY,
india = InnerDia, outdia = OuterDia,
blur = BlurLevel,
inal = InAlpha/100.0, outal = OutAlpha/100.0,
basename = BaseNameString,
namenum = NameNumber
)
sys.stderr.write(".")
# sys.stderr.write("{}\n".format(FormattedCommand))
ProcString = subprocess.check_output(FormattedCommand, stderr=subprocess.STDOUT,shell=True)
if ProcString:
sys.stderr.write(ProcString)
sys.stderr.write(" Done.\n")
""" BASE COMMANDS:
# inner circle:
convert -size 400x300 xc:black -fill "rgba( 0, 255,255 , 1 )" -stroke none -draw "translate 200,150 circle 0,0 75,0" -blur 0x8 -morphology Distance Euclidean:4,1000 blur_circle100.gif
#outer circle
convert -size 400x300 xc:black -fill "rgba( 0, 255,255 , .5 )" -stroke none -draw "translate 200,150 circle 0,0 150,0" -morphology Distance Euclidean:4,500 -blur 0x6 blur_circle100.gif
#both circles
convert -size 400x300 xc:black -fill "rgba( 0, 255,255 , .5 )" -stroke none -draw "translate 200,150 circle 0,0 150,0" -morphology Distance Euclidean:4,500 \
-fill "rgba( 0, 255,255 , 1 )" -stroke none -draw "translate 200,150 circle 0,0 75,0" -morphology Distance Euclidean:4,1000\
-blur 0x8 blur_circle100.gif
"""

Resources