Changing Pixel values in Mat opencv - opencv

I am trying to work with am RGB image in Opencv. From the image i only want to keep the red pixels and the rest i want to set to white. I am unsure how to do this logic in opencv. The image is read as Mat.
I wrote the following code but its not working.
Mat image;
for(i to rows)
for(j to col)
{
b=input[image.step * j + i]
g=input[image.step * j + i + 1]
r=input[image.step * j + i + 2]
if(r == 255 && g&b == 0)
{
image.at<Vec3f>(j,i)=img.at<Vec3F>(j,i)
}
else image.push_back(0);
This is the code i wrote
I am sure its incorrect but I am unable to do it. Can i get some help

You would like to keep only those pixels which are purely red, i.e. red is 255 and green, and blue is zeros. Basically, you want to change those pixels which does not satisfy that condition:
if(~(red == 255 and green == 0 and blue == 0))
red = green = blue = 255
Below is the code in python:
img = cv2.imread(filename)
rows , cols , layers = img.shape
for i in range(rows):
for j in range(cols):
if(~(img[i][j][2] == 255 and img[i][j][0] == 0 and img[i][j][1] == 0)):
img[i][j][0] = img[i][j][1] = img[i][j][2] = 255

Related

python segment an image of text line by line [duplicate]

I am trying to find a way to break the split the lines of text in a scanned document that has been adaptive thresholded. Right now, I am storing the pixel values of the document as unsigned ints from 0 to 255, and I am taking the average of the pixels in each line, and I split the lines into ranges based on whether the average of the pixels values is larger than 250, and then I take the median of each range of lines for which this holds. However, this methods sometimes fails, as there can be black splotches on the image.
Is there a more noise-resistant way to do this task?
EDIT: Here is some code. "warped" is the name of the original image, "cuts" is where I want to split the image.
warped = threshold_adaptive(warped, 250, offset = 10)
warped = warped.astype("uint8") * 255
# get areas where we can split image on whitespace to make OCR more accurate
color_level = np.array([np.sum(line) / len(line) for line in warped])
cuts = []
i = 0
while(i < len(color_level)):
if color_level[i] > 250:
begin = i
while(color_level[i] > 250):
i += 1
cuts.append((i + begin)/2) # middle of the whitespace region
else:
i += 1
EDIT 2: Sample image added
From your input image, you need to make text as white, and background as black
You need then to compute the rotation angle of your bill. A simple approach is to find the minAreaRect of all white points (findNonZero), and you get:
Then you can rotate your bill, so that text is horizontal:
Now you can compute horizontal projection (reduce). You can take the average value in each line. Apply a threshold th on the histogram to account for some noise in the image (here I used 0, i.e. no noise). Lines with only background will have a value >0, text lines will have value 0 in the histogram. Then take the average bin coordinate of each continuous sequence of white bins in the histogram. That will be the y coordinate of your lines:
Here the code. It's in C++, but since most of the work is with OpenCV functions, it should be easy convertible to Python. At least, you can use this as a reference:
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
// Read image
Mat3b img = imread("path_to_image");
// Binarize image. Text is white, background is black
Mat1b bin;
cvtColor(img, bin, COLOR_BGR2GRAY);
bin = bin < 200;
// Find all white pixels
vector<Point> pts;
findNonZero(bin, pts);
// Get rotated rect of white pixels
RotatedRect box = minAreaRect(pts);
if (box.size.width > box.size.height)
{
swap(box.size.width, box.size.height);
box.angle += 90.f;
}
Point2f vertices[4];
box.points(vertices);
for (int i = 0; i < 4; ++i)
{
line(img, vertices[i], vertices[(i + 1) % 4], Scalar(0, 255, 0));
}
// Rotate the image according to the found angle
Mat1b rotated;
Mat M = getRotationMatrix2D(box.center, box.angle, 1.0);
warpAffine(bin, rotated, M, bin.size());
// Compute horizontal projections
Mat1f horProj;
reduce(rotated, horProj, 1, CV_REDUCE_AVG);
// Remove noise in histogram. White bins identify space lines, black bins identify text lines
float th = 0;
Mat1b hist = horProj <= th;
// Get mean coordinate of white white pixels groups
vector<int> ycoords;
int y = 0;
int count = 0;
bool isSpace = false;
for (int i = 0; i < rotated.rows; ++i)
{
if (!isSpace)
{
if (hist(i))
{
isSpace = true;
count = 1;
y = i;
}
}
else
{
if (!hist(i))
{
isSpace = false;
ycoords.push_back(y / count);
}
else
{
y += i;
count++;
}
}
}
// Draw line as final result
Mat3b result;
cvtColor(rotated, result, COLOR_GRAY2BGR);
for (int i = 0; i < ycoords.size(); ++i)
{
line(result, Point(0, ycoords[i]), Point(result.cols, ycoords[i]), Scalar(0, 255, 0));
}
return 0;
}
Basic steps as #Miki,
read the source
threshed
find minAreaRect
warp by the rotated matrix
find and draw upper and lower bounds
While code in Python:
#!/usr/bin/python3
# 2018.01.16 01:11:49 CST
# 2018.01.16 01:55:01 CST
import cv2
import numpy as np
## (1) read
img = cv2.imread("img02.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
## (2) threshold
th, threshed = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)
## (3) minAreaRect on the nozeros
pts = cv2.findNonZero(threshed)
ret = cv2.minAreaRect(pts)
(cx,cy), (w,h), ang = ret
if w>h:
w,h = h,w
ang += 90
## (4) Find rotated matrix, do rotation
M = cv2.getRotationMatrix2D((cx,cy), ang, 1.0)
rotated = cv2.warpAffine(threshed, M, (img.shape[1], img.shape[0]))
## (5) find and draw the upper and lower boundary of each lines
hist = cv2.reduce(rotated,1, cv2.REDUCE_AVG).reshape(-1)
th = 2
H,W = img.shape[:2]
uppers = [y for y in range(H-1) if hist[y]<=th and hist[y+1]>th]
lowers = [y for y in range(H-1) if hist[y]>th and hist[y+1]<=th]
rotated = cv2.cvtColor(rotated, cv2.COLOR_GRAY2BGR)
for y in uppers:
cv2.line(rotated, (0,y), (W, y), (255,0,0), 1)
for y in lowers:
cv2.line(rotated, (0,y), (W, y), (0,255,0), 1)
cv2.imwrite("result.png", rotated)
Finally result:

Improving the grey scale conversion result

Here is the colour menu:
Here is the same menu with some of the menu items disabled, and the bitmaps set as greyscale:
The code that converts to grey scale:
auto col = GetRValue(pixel) * 0.299 +
GetGValue(pixel) * 0.587 +
GetBValue(pixel) * 0.114;
pixel = RGB(col, col, col);
I am colourblind but it seems that some of them don’t look that much different. I assume it relates to the original colours in the first place?
It would just be nice if it was more obvious they are disabled. Like, it is very clear with the text.
Can we?
For people who are not colour blind it's pretty obvious.
Just apply the same intensity reduction to the images that you do to the text.
I did not check your values. Let's assume the text is white (100% intensity).
And the grayed out text is 50% intensity.
Then the maximum intensity of the bitmap should be 50% as well.
for each gray pixel:
pixel_value = pixel_value / max_pixel_value * gray_text_value
This way you decrease further decrease the contrast of each bitmap and avoid having any pixel brighter than the text.
This is not directly related to your question, but since you are changing colors you can also fix the corner pixels which stand out (by corner pixels I don't mean pixels at the edges of bitmap rectangle, I mean the corner of human recognizable image)
Example, in image below, there is a red pixel at the corner of the page. We want to find that red pixel and blend it with background color so that it doesn't stand out.
To find if the corner pixels, check the pixels at left and top, if both left and top are the background color then you have a corner pixel. Repeat the same for top-right, bottom-left, and bottom-right. Blend the corner pixels with background.
Instead of changing to grayscale you can change the alpha transparency as suggested by zett42.
void change(HBITMAP hbmp, bool enabled)
{
if(!hbmp)
return;
HDC memdc = CreateCompatibleDC(nullptr);
BITMAP bm;
GetObject(hbmp, sizeof(bm), &bm);
int w = bm.bmWidth;
int h = bm.bmHeight;
BITMAPINFO bi = { sizeof(BITMAPINFOHEADER), w, h, 1, 32, BI_RGB };
std::vector<uint32_t> pixels(w * h);
GetDIBits(memdc, hbmp, 0, h, &pixels[0], &bi, DIB_RGB_COLORS);
//assume that the color at (0,0) is the background color
uint32_t old_color = pixels[0];
//this is the new background color
uint32_t bk = GetSysColor(COLOR_MENU);
//swap RGB with BGR
uint32_t new_color = RGB(GetBValue(bk), GetGValue(bk), GetRValue(bk));
//define lambda functions to swap between BGR and RGB
auto bgr_r = [](uint32_t color) { return GetBValue(color); };
auto bgr_g = [](uint32_t color) { return GetGValue(color); };
auto bgr_b = [](uint32_t color) { return GetRValue(color); };
BYTE new_red = bgr_r(new_color);
BYTE new_grn = bgr_g(new_color);
BYTE new_blu = bgr_b(new_color);
//change background and modify disabled bitmap
for(auto &p : pixels)
{
if(p == old_color)
{
p = new_color;
}
else if(!enabled)
{
//blend color with background, similar to 50% alpha
BYTE red = (bgr_r(p) + new_red) / 2;
BYTE grn = (bgr_g(p) + new_grn) / 2;
BYTE blu = (bgr_b(p) + new_blu) / 2;
p = RGB(blu, grn, red); //<= BGR/RGB swap
}
}
//fix corner edges
for(int row = h - 2; row >= 1; row--)
{
for(int col = 1; col < w - 1; col++)
{
int i = row * w + col;
if(pixels[i] != new_color)
{
//check the color of neighboring pixels:
//if that pixel has background color,
//then that pixel is the background
bool l = pixels[i - 1] == new_color; //left pixel is background
bool r = pixels[i + 1] == new_color; //right ...
bool t = pixels[i - w] == new_color; //top ...
bool b = pixels[i + w] == new_color; //bottom ...
//we are on a corner pixel if:
//both left-pixel and top-pixel are background or
//both left-pixel and bottom-pixel are background or
//both right-pixel and bottom-pixel are background or
//both right-pixel and bottom-pixel are background
if(l && t || l && b || r && t || r && b)
{
//blend corner pixel with background
BYTE red = (bgr_r(pixels[i]) + new_red) / 2;
BYTE grn = (bgr_g(pixels[i]) + new_grn) / 2;
BYTE blu = (bgr_b(pixels[i]) + new_blu) / 2;
pixels[i] = RGB(blu, grn, red);//<= BGR/RGB swap
}
}
}
}
SetDIBits(memdc, hbmp, 0, h, &pixels[0], &bi, DIB_RGB_COLORS);
DeleteDC(memdc);
}
Usage:
CBitmap bmp1, bmp2;
bmp1.LoadBitmap(IDB_BITMAP1);
bmp2.LoadBitmap(IDB_BITMAP2);
change(bmp1, enabled);
change(bmp2, disabled);

Multi colour detection does not show centroid more than 1 object, Only show the centroid of Image which had largest pixel

I am still new to OpenCV, i am trying to detect object of multiple colour as well as center of the object. The color i need to detect is Red, Blue and Yellow. My program able to detect different colour, but it can't detect centroid of the same colour. The object with the bigger pixel will take the priority. Is there anythings i miss or where can i improve in my code?
main.cpp
inRange(imgHSV, Scalar(redLowH,redLowS,redLowV), Scalar(redHighH, redHighS, redHighV), imgThresred); //Threshold the image
inRange(imgHSV, Scalar(blueLowH,blueLowS,blueLowV), Scalar(blueHighH, blueHighS, blueHighV), imgThresblue); //Threshold the image
inRange(imgHSV, Scalar(yellowLowH,yellowLowS,yellowLowV), Scalar(yellowHighH, yellowHighS, yellowHighV), imgThresyellow); //Threshold the image
Moments rMoments = moments(imgThresred);
double dM01_r = rMoments.m01;
double dM10_r = rMoments.m10;
double dArea_r = rMoments.m00;
//Calculate area of Red Object
if (dArea_r > 10000)
{
//calculate the position of the ball
posX_r = dM10_r / dArea_r;
posY_r = dM01_r / dArea_r;
//cout<<"The red object : X coodinate is "<< posX_r <<", Y coodinate is "<< posY_r <<endl;
if (iLastX_r >= 0 && iLastY_r >= 0 && posX_r >= 0 && posY_r >= 0)
{
//Draw a red line from the previous point to the current point
line(imgLines_r, Point(posX_r, posY_r), Point(iLastX_r, iLastY_r), Scalar(0,0,255), 10);
}
iLastX_r = posX_r;
iLastY_r = posY_r;
}
imgOriginal = imgOriginal + imgLines_r + imgLines_b + imgLines_y;
//Check Object region in the image
if (posX_r < 100)
{
cout<<"left side"<<endl;
}else
{
cout<<"no"<<endl;
}

FFT Convolution - Really low PSNR

I'm convoluting an image (512*512) with a FFT filter (kernelsize=10), it looks good.
But when I compare it with an image which I convoluted the normal way the result was horrible.
The PSNR is about 35.
67,187/262,144 Pixel values have a difference of 1 or more(peak at ~8) (having a max pixel value of 255).
My question is, is it normal when convoluting in frequency space or might there be a problem with my convolution/transforming functions? . Because the strange thing is that I should get better results when using double as data-type. But it stays COMPLETELY the same.
When I transform an image into frequency space, DON'T convolute it, then transform it back it's fine and the PSNR is about 140 when using float.
Also, due to the pixel differences being only 1-10 I think I can rule out scaling errors
EDIT: More Details for bored interested people
I use the open source kissFFT library. With real 2dimensional input (kiss_fftndr.h)
My Image Datatype is PixelMatrix. Simply a matrix with alpha, red, green and blue values from 0.0 to 1.0 float
My kernel is also a PixelMatrix.
Here some snippets from the Convolution function
Used datatypes:
#define kiss_fft_scalar float
#define kiss_fft_cpx struct {
kiss_fft_scalar r;
kiss_fft_scalar i,
}
Configuration of the FFT:
//parameters to kiss_fftndr_alloc:
//1st param = array with the size of the 2 dimensions (in my case dim={width, height})
//2nd param = count of the dimensions (in my case 2)
//3rd param = 0 or 1 (forward or inverse FFT)
//4th and 5th params are not relevant
kiss_fftndr_cfg stf = kiss_fftndr_alloc(dim, 2, 0, 0, 0);
kiss_fftndr_cfg sti = kiss_fftndr_alloc(dim, 2, 1, 0, 0);
Padding and transforming the kernel:
I make a new array:
kiss_fft_scalar kernel[width*height];
I fill it with 0 in a loop.
Then I fill the middle of this array with the kernel I want to use.
So if I would use a 2*2 kernel with values 1/4, 1/4, 1/4 and 1/4 it would look like
0 0 0 0 0 0
0 1/4 1/4 0
0 1/4 1/4 0
0 0 0 0 0 0
The zeros are padded until they reach the size of the image.
Then I swap the quadrants of the image diagonally. It looks like:
1/4 0 0 1/4
0 0 0 0
0 0 0 0
1/4 0 0 1/4
now I transform it: kiss_fftndr(stf, floatKernel, outkernel);
outkernel is declarated as
kiss_fft_cpx outkernel= new kiss_fft_cpx[width*height]
Getting the colors into arrays:
kiss_fft_scalar *red = new kiss_fft_scalar[width*height];
kiss_fft_scalar *green = new kiss_fft_scalar[width*height];
kiss_fft-scalar *blue = new kiss_fft_scalar[width*height];
for(int i=0; i<height; i++) {
for(int j=0; i<width; j++) {
red[i*height+j] = input.get(j,i).getRed(); //input is the input image pixel matrix
green[i*height+j] = input.get(j,i).getGreen();
blue{i*height+j] = input.get(j,i).getBlue();
}
}
Then I transform the arrays:
kiss_fftndr(stf, red, outred);
kiss_fftndr(stf, green, outgreen);
kiss_fftndr(stf, blue, outblue); //the out-arrays are type kiss_fft_cpx*
The convolution:
What we have now:
3 transformed color arrays from type kiss_fft_cpx*
1 transformed kernel array from type kiss_fft_cpx*
They are both complex arrays
Now comes the convolution:
for(int m=0; m<til; m++) {
for(int n=0; n<til; n++) {
kiss_fft_scalar real = outcolor[m*til+n].r; //I do that for all 3 arrys in my code!
kiss_fft_scalar imag = outcolor[m*til+n].i; //so I have realred, realgreen, realblue
kiss_fft_scalar realMask = outkernel[m*til+n].r; // and imagred, imaggreen, etc.
kiss_fft_scalar imagMask = outkernel[m*til+n].i;
outcolor[m*til+n].r = real * realMask - imag * imagMask; //Same thing here in my code i
outcolor[m*til+n].i = real * imagMask + imag * realMask; //do it with all 3 colors
}
}
Now I transform them back:
kiss_fftndri(sti, outred, red);
kiss_fftndri(sti, outgreen, green);
kiss_fftndri(sti, outblue, blue);
and I create a new Pixel Matrix with the values from the color-arrays
PixelMatrix output;
for(int i=0; i<height; i++) {
for(int j=0; j<width; j++) {
Pixel p = new Pixel();
p.setRed( red[i*height+j] / (width*height) ); //I divide through (width*height) because of the scaling happening in the FFT;
p.setGreen( green[i*height+j] );
p.setBlue( blue[i*height+j] );
output.set(j , i , p);
}
}
Notes:
I already take care in advance that the image has a size with a power of 2 (256*256), (512*512) etc.
Examples:
kernelsize: 10
Input:
Output:
Output from normal convolution:
my console says :
142519 out of 262144 Pixels have a difference of 1 or more (maxRGB = 255)
PSNR: 32.006027221679688
MSE: 44.116752624511719
though for my eyes they look the same °.°
Maybe one person is bored and goes through the code. It's not urgent, but it's a kind of problem I just want to know what the hell I did wrong ^^
Last, but not least, my PSNR function, though I don't really think that's the problem :D
void calculateThePSNR(const PixelMatrix first, const PixelMatrix second, float* avgpsnr, float* avgmse) {
int height = first.getHeight();
int width = first.getWidth();
BMP firstOutput;
BMP secondOutput;
firstOutput.SetSize(width, height);
secondOutput.SetSize(width, height);
double rsum=0.0, gsum=0.0, bsum=0.0;
int count = 0;
int total = 0;
for(int i=0; i<height; i++) {
for(int j=0; j<width; j++) {
Pixel pixOne = first.get(j,i);
Pixel pixTwo = second.get(j,i);
double redOne = pixOne.getRed()*255;
double greenOne = pixOne.getGreen()*255;
double blueOne = pixOne.getBlue()*255;
double redTwo = pixTwo.getRed()*255;
double greenTwo = pixTwo.getGreen()*255;
double blueTwo = pixTwo.getBlue()*255;
firstOutput(j,i)->Red = redOne;
firstOutput(j,i)->Green = greenOne;
firstOutput(j,i)->Blue = blueOne;
secondOutput(j,i)->Red = redTwo;
secondOutput(j,i)->Green = greenTwo;
secondOutput(j,i)->Blue = blueTwo;
if((redOne-redTwo) > 1.0 || (redOne-redTwo) < -1.0) {
count++;
}
total++;
rsum += (redOne - redTwo) * (redOne - redTwo);
gsum += (greenOne - greenTwo) * (greenOne - greenTwo);
bsum += (blueOne - blueTwo) * (blueOne - blueTwo);
}
}
fprintf(stderr, "%d out of %d Pixels have a difference of 1 or more (maxRGB = 255)", count, total);
double rmse = rsum/(height*width);
double gmse = gsum/(height*width);
double bmse = bsum/(height*width);
double rpsnr = 20 * log10(255/sqrt(rmse));
double gpsnr = 20 * log10(255/sqrt(gmse));
double bpsnr = 20 * log10(255/sqrt(bmse));
firstOutput.WriteToFile("test.bmp");
secondOutput.WriteToFile("test2.bmp");
system("display test.bmp");
system("display test2.bmp");
*avgmse = (rmse + gmse + bmse)/3;
*avgpsnr = (rpsnr + gpsnr + bpsnr)/3;
}
Phonon had the right idea. Your images are shifted. If you shift your image by (1,1), then the MSE will be approximately zero (provided that you mask or crop the images accordingly). I confirmed this using the code (Python + OpenCV) below.
import cv
import sys
import math
def main():
fname1, fname2 = sys.argv[1:]
im1 = cv.LoadImage(fname1)
im2 = cv.LoadImage(fname2)
tmp = cv.CreateImage(cv.GetSize(im1), cv.IPL_DEPTH_8U, im1.nChannels)
cv.AbsDiff(im1, im2, tmp)
cv.Mul(tmp, tmp, tmp)
mse = cv.Avg(tmp)
print 'MSE:', mse
psnr = [ 10*math.log(255**2/m, 10) for m in mse[:-1] ]
print 'PSNR:', psnr
if __name__ == '__main__':
main()
Output:
MSE: (0.027584912741602553, 0.026742391458366047, 0.028147870144492403, 0.0)
PSNR: [63.724087463606452, 63.858801190963192, 63.636348220531396]
My advice for you to try to implement the following code :
A=double(inputS(1:10:length(inputS))); %segmentation
A(:)=-A(:);
%process the image or signal by fast fourior transformation and inverse fft
fresult=fft(inputS);
fresult(1:round(length(inputS)*2/fs))=0;
fresult(end-round(length(fresult)*2/fs):end)=0;
Y=real(ifft(fresult));
that's code help you to obtain the same size image and good for remove DC component ,the you can to convolution.

Algorithm for Hue/Saturation Adjustment Layer from Photoshop

Does anyone know how adjustment layers work in Photoshop? I need to generate a result image having a source image and HSL values from Hue/Saturation adjustment layer. Conversion to RGB and then multiplication with the source color does not work.
Or is it possible to replace Hue/Saturation Adjustment Layer with normal layers with appropriately set blending modes (Mulitiply, Screen, Hue, Saturation, Color, Luminocity,...)?
If so then how?
Thanks
I've reverse-engineered the computation for when the "Colorize" checkbox is checked. All of the code below is pseudo-code.
The inputs are:
hueRGB, which is an RGB color for HSV(photoshop_hue, 100, 100).ToRGB()
saturation, which is photoshop_saturation / 100.0 (i.e. 0..1)
lightness, which is photoshop_lightness / 100.0 (i.e. -1..1)
value, which is the pixel.ToHSV().Value, scaled into 0..1 range.
The method to colorize a single pixel:
color = blend2(rgb(128, 128, 128), hueRGB, saturation);
if (lightness <= -1)
return black;
else if (lightness >= 1)
return white;
else if (lightness >= 0)
return blend3(black, color, white, 2 * (1 - lightness) * (value - 1) + 1)
else
return blend3(black, color, white, 2 * (1 + lightness) * (value) - 1)
Where blend2 and blend3 are:
blend2(left, right, pos):
return rgb(left.R * (1-pos) + right.R * pos, same for green, same for blue)
blend3(left, main, right, pos):
if (pos < 0)
return blend2(left, main, pos + 1)
else if (pos > 0)
return blend2(main, right, pos)
else
return main
I have figured out how Lightness works.
The input parameter brightness b is in [0, 2], Output is c (color channel).
if(b<1) c = b * c;
else c = c + (b-1) * (1-c);
Some tests:
b = 0 >>> c = 0 // black
b = 1 >>> c = c // same color
b = 2 >>> c = 1 // white
However, if you choose some interval (e.g. Reds instead of Master), Lightness behaves completely differently, more like Saturation.
Photoshop, dunno. But the theory is usually: The RGB image is converted to HSL/HSV by the particular layer's internal methods; each pixel's HSL is then modified according to the specified parameters, and the so-obtained result is being provided back (for displaying) in RGB.
PaintShopPro7 used to split up the H space (assuming a range of 0..360) in discrete increments of 30° (IIRC), so if you bumped only the "yellows", i.e. only pixels whose H component was valued 45-75 would be considered for manipulation.
reds 345..15, oranges 15..45, yellows 45..75, yellowgreen 75..105, greens 105..135, etc.
if (h >= 45 && h < 75)
s += s * yellow_percent;
There are alternative possibilities, such as applying a falloff filter, as in:
/* For h=60, let m=1... and linearly fall off to h=75 m=0. */
m = 1 - abs(h - 60) / 15;
if (m < 0)
m = 0;
s += s * yellow_percent * d;
Hello I wrote colorize shader and my equation is as folows
inputRGB is the source image which should be in monochrome
(r+g+b) * 0.333
colorRGB is your destination color
finalRGB is the result
pseudo code:
finalRGB = inputRGB * (colorRGB + inputRGB * 0.5);
I think it's fast and efficient
I did translate #Roman Starkov solution to java if any one needed, but for some reason It not worked so well, then I started read a little bit and found that the solution is very simple , there are 2 things have to be done :
When changing the hue or saturation replace the original image only hue and saturation and the lightness stay as is was in the original image this blend method called 10.2.4. luminosity blend mode :
https://www.w3.org/TR/compositing-1/#backdrop
When changing the lightness in photoshop the slider indicates how much percentage we need to add or subtract to/from the original lightness in order to get to white or black color in HSL.
for example :
If the original pixel is 0.7 lightness and the lightness slider = 20
so we need more 0.3 lightness in order to get to 1
so we need to add to the original pixel lightness : 0.7 + 0.2*0.3;
this will be the new blended lightness value for the new pixel .
#Roman Starkov solution Java implementation :
//newHue, which is photoshop_hue (i.e. 0..360)
//newSaturation, which is photoshop_saturation / 100.0 (i.e. 0..1)
//newLightness, which is photoshop_lightness / 100.0 (i.e. -1..1)
//returns rgb int array of new color
private static int[] colorizeSinglePixel(int originlPixel,int newHue,float newSaturation,float newLightness)
{
float[] originalPixelHSV = new float[3];
Color.colorToHSV(originlPixel,originalPixelHSV);
float originalPixelLightness = originalPixelHSV[2];
float[] hueRGB_HSV = {newHue,100.0f,100.0f};
int[] hueRGB = {Color.red(Color.HSVToColor(hueRGB_HSV)),Color.green(Color.HSVToColor(hueRGB_HSV)),Color.blue(Color.HSVToColor(hueRGB_HSV))};
int color[] = blend2(new int[]{128,128,128},hueRGB,newSaturation);
int blackColor[] = new int[]{Color.red(Color.BLACK),Color.green(Color.BLACK),Color.blue(Color.BLACK)};
int whileColor[] = new int[]{Color.red(Color.WHITE),Color.green(Color.WHITE),Color.blue(Color.WHITE)};
if(newLightness <= -1)
{
return blackColor;
}
else if(newLightness >=1)
{
return whileColor;
}
else if(newLightness >=0)
{
return blend3(blackColor,color,whileColor, (int) (2*(1-newLightness)*(originalPixelLightness-1) + 1));
}
else
{
return blend3(blackColor,color,whileColor, (int) ((1+newLightness)*(originalPixelLightness) - 1));
}
}
private static int[] blend2(int[] left,int[] right,float pos)
{
return new int[]{(int) (left[0]*(1-pos)+right[0]*pos),(int) (left[1]*(1-pos)+right[1]*pos),(int) (left[2]*(1-pos)+right[2]*pos)};
}
private static int[] blend3(int[] left,int[] main,int[] right,int pos)
{
if(pos < 0)
{
return blend2(left,main,pos+1);
}
else if(pos > 0)
{
return blend2(main,right,pos);
}
else
{
return main;
}
}
When the “Colorize” checkbox is checked, the lightness of the underlying layer is combined with the values of the Hue and Saturation sliders and converted from HSL to RGB according to the equations at https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL . (The Lightness slider just remaps the lightness to a subset of the scale as you can see from watching the histogram; the effect is pretty awful and I don’t see why anyone would ever use it.)

Resources