How is a sepia tone created? - image-processing

What are the basic operations needed to create a sepia tone? My reference point is the perl imagemagick library, so I can easily use any basic operation. I've tried to quantize (making it grayscale), colorize, and then enhance the image but it's still a bit blurry.

Sample code of a sepia converter in C# is available in my answer here: What is wrong with this sepia tone conversion algorithm?
The algorithm comes from this page, each input pixel color is transformed in the following way:
outputRed = (inputRed * .393) + (inputGreen *.769) + (inputBlue * .189)
outputGreen = (inputRed * .349) + (inputGreen *.686) + (inputBlue * .168)
outputBlue = (inputRed * .272) + (inputGreen *.534) + (inputBlue * .131)
If any of these output values is greater than 255, you simply set it
to 255. These specific values are the values for sepia tone that are
recommended by Microsoft.

This is in C#, however, the basic concepts are the same. You will likely be able to convert this into perl.
private void SepiaBitmap(Bitmap bmp)
{
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
System.Drawing.Imaging.PixelFormat.Format32bppRgb);
IntPtr ptr = bmpData.Scan0;
int numPixels = bmpData.Width * bmp.Height;
int numBytes = numPixels * 4;
byte[] rgbValues = new byte[numBytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, numBytes);
for (int i = 0; i < rgbValues.Length; i += 4)
{
rgbValues[i + 2] = (byte)Math.Min((.393 * red) + (.769 * green) + (.189 * (blue)), 255.0); //red
rgbValues[i + 1] = (byte)Math.Min((.349 * red) + (.686 * green) + (.168 * (blue)), 255.0); //green
rgbValues[i + 0] = (byte)Math.Min((.272 * red) + (.534 * green) + (.131 * (blue)), 255.0); //blue
if ((rgbValues[i + 2]) > 255)
{
rgbValues[i + 2] = 255;
}
if ((rgbValues[i + 1]) > 255)
{
rgbValues[i + 1] = 255;
}
if ((rgbValues[i + 0]) > 255)
{
rgbValues[i + 0] = 255;
}
}
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, numBytes);
this.Invalidate();
bmp.UnlockBits(bmpData);
}

It's easy if you use the imagemagic command line.
http://www.imagemagick.org/script/convert.php
Use the "-sepia-tone threshold" argument when converting.
Strangely enough, the PerlMagick API doesn't seem to include a method for doing this directly:
http://www.imagemagick.org/script/perl-magick.php
...and no reference to any Sepia method.

Take a look at how it's implemented in the AForge.NET library, the C# code is here.
The basics seem to be
transform it to the YIQ color space
modify it
transform back to RGB
The full alrogithm is in the source code, plus the RGB -> YIQ and YIQ -> RGB transformations are explained.

Related

Example usage of a libyuv API MJPGToI420()

I'm trying to use a libyuv API, more specifically MJPGToI420().
I want to first take a jpeg image as input to MJPGToI420(), the signature of which is below:
int MJPGToI420(const uint8_t* sample,
size_t sample_size,
uint8_t* dst_y,
int dst_stride_y,
uint8_t* dst_u,
int dst_stride_u,
uint8_t* dst_v,
int dst_stride_v,
int src_width,
int src_height,
int dst_width,
int dst_height);
Then, I want to allocate space for the dst_y, dst_u, and dst_v pointers. However, I don't know how much space to allocate for them. I'm also confused about what the strides should be, i.e., what the parameters dst_stride_y, dst_stride_u and dst_stride_v should be.
Would really appreciate any pointers in the right direction.
EDIT: Here's a snippet of code from the libyuv source unit tests that uses this function. However, the test returns 1 which is failure of the function as the intended behavior. The test also just uses zeroes for the data, instead of an actual MJPG file.
TEST_F(LibYUVConvertTest, MJPGToI420) {
const int kOff = 10;
const int kMinJpeg = 64;
const int kImageSize = benchmark_width_ * benchmark_height_ >= kMinJpeg
? benchmark_width_ * benchmark_height_
: kMinJpeg;
const int kSize = kImageSize + kOff;
align_buffer_page_end(orig_pixels, kSize);
align_buffer_page_end(dst_y_opt, benchmark_width_ * benchmark_height_);
align_buffer_page_end(dst_u_opt, SUBSAMPLE(benchmark_width_, 2) *
SUBSAMPLE(benchmark_height_, 2));
align_buffer_page_end(dst_v_opt, SUBSAMPLE(benchmark_width_, 2) *
SUBSAMPLE(benchmark_height_, 2));
// EOI, SOI to make MJPG appear valid.
memset(orig_pixels, 0, kSize);
orig_pixels[0] = 0xff;
orig_pixels[1] = 0xd8; // SOI.
orig_pixels[kSize - kOff + 0] = 0xff;
orig_pixels[kSize - kOff + 1] = 0xd9; // EOI.
for (int times = 0; times < benchmark_iterations_; ++times) {
int ret =
MJPGToI420(orig_pixels, kSize, dst_y_opt, benchmark_width_, dst_u_opt,
SUBSAMPLE(benchmark_width_, 2), dst_v_opt,
SUBSAMPLE(benchmark_width_, 2), benchmark_width_,
benchmark_height_, benchmark_width_, benchmark_height_);
// Expect failure because image is not really valid.
EXPECT_EQ(1, ret);
}
free_aligned_buffer_page_end(dst_y_opt);
free_aligned_buffer_page_end(dst_u_opt);
free_aligned_buffer_page_end(dst_v_opt);
free_aligned_buffer_page_end(orig_pixels);
}
EDIT 2: Furthermore, this is what I've tried, however, the end yuv files are not even viewable in a yuv viewer (created using the buffers dst_u_opt and dst_y_opt), which makes me believe there might be something that I'm messing up with the function:
int convertMJPGToI420() {
auto fileSize = filesize(IMG_NAME);
// load image into memory
uint8_t* my_img = (uint8_t*) calloc(fileSize, 1);
std::ifstream fin(IMG_NAME, ios::in | ios::binary);
fin.read(reinterpret_cast<char*>(my_img), fileSize);
// exif data offset
// This is the size of the exif data
const int kOff = 4096;
// 4k image is being sent in
int benchmark_width_ = 3840;
int benchmark_height_ = 2160;
const int kSize = fileSize;
// align_buffer_page_end is a macro (look at link posted for unit tests above)
// I'm not sure if the size allocation for these is correct
// I have tried to model it based off the example
align_buffer_page_end(orig_pixels, kSize);
align_buffer_page_end(dst_y_opt, benchmark_width_ * benchmark_height_);
align_buffer_page_end(dst_u_opt, SUBSAMPLE(benchmark_width_, 2) *
SUBSAMPLE(benchmark_height_, 2));
align_buffer_page_end(dst_v_opt, SUBSAMPLE(benchmark_width_, 2) *
SUBSAMPLE(benchmark_height_, 2));
// EOI, SOI to make MJPG appear valid
memset(orig_pixels, 0, kSize);
orig_pixels[0] = 0xff;
orig_pixels[1] = 0xd8; // SOI
memcpy(orig_pixels + 2, my_img, kSize - kOff - 3);
orig_pixels[kSize - kOff + 0] = 0xff;
orig_pixels[kSize - kOff + 1] = 0xd9; // EOI
// using async as this function might be ansynchronous
std::future<int> ret = std::async(libyuv::MJPGToI420, orig_pixels, kSize, dst_y_opt, benchmark_width_,
dst_u_opt, SUBSAMPLE(benchmark_width_, 2),
dst_v_opt, SUBSAMPLE(benchmark_width_, 2),
benchmark_width_, benchmark_height_,
benchmark_width_, benchmark_height_);
ret.wait();
// ret is always one, which means there was a failure
if(ret.get() == 0) {
cout << "return value was zero" << endl;
} else {
cout << "return value was one" << endl;
}
FILE* file = fopen("/data/dst_u_opt", "wb");
fwrite(dst_y_opt, 1, SUBSAMPLE(benchmark_width_, 2) * SUBSAMPLE(benchmark_height_, 2) , file);
fclose(file);
file = fopen("/data/dst_v_opt", "wb");
fwrite(dst_y_opt, 1, SUBSAMPLE(benchmark_width_, 2) * SUBSAMPLE(benchmark_height_, 2), file);
fclose(file);
free_aligned_buffer_page_end(dst_y_opt);
free_aligned_buffer_page_end(dst_u_opt);
free_aligned_buffer_page_end(dst_v_opt);
free_aligned_buffer_page_end(orig_pixels);
return 0;
}
You'll need to know the width and height of the jpeg.
I420 is a 420 sub-sampled YUV.
The Y plane is width * height in bytes.
The dst_stride_y value is width
e.g.
char* dst_y = malloc(width * height);
The U and V planes are half width and height. To handle odd sizes you should round up.
dst_stride_u = (width + 1) / 2;
dst_stride_v = (width + 1) / 2;
The u and v planes are ((width + 1) / 2) * ((height + 1) / 2) bytes.
char* dst_u = malloc(((width + 1) / 2) * ((height + 1) / 2));
char* dst_y = malloc(((width + 1) / 2) * ((height + 1) / 2));
If you'd like to file an issue, including better documentation, post it here:
https://bugs.chromium.org/p/libyuv/issues/list

Removing R from RGB Color

Is there a way to remove the red channel of an RGB pixel in a way such that in the resulting picture the red color goes to white not to black? I need to distinguish between red color and blue/black color, but in different light the RGB value varies. If I simply remove the R channel, darker red colors become black and I want the opposite result.
Thanks!
If I understand you correctly -
You need to normalize the red channel value and then use it as a mixing value:
mix = R / 255
Then mix white with the normal color minus the red channel using the mix factor:
Original-red White
R' = 0 + 255 * mix
G' = G * (1 - mix) + 255 * mix
B' = B * (1 - mix) + 255 * mix
Just notice it will kill the yellow and magenta colors as well as these of course uses the red channel, and the more red the more white is mixed in.
You should be able to get around this using the CMYK color-model, or a combination of both, so you can separate out all the main components. Then override the mix with f.ex. the yellow/magenta components from CMYK.
The mixing process should be the same as described though.
Conceptual demo
var ctx = c.getContext("2d");
var img = new Image;
img.onload = function() {
c.width = img.width;
c.height = img.height;
ctx.drawImage(this, 0, 0);
var idata = ctx.getImageData(0,0,c.width,c.height),
data = idata.data, len = data.length, i, mix;
/* mix = R / 255
R = 0 + 255 * mix
G = G * (1 - mix) + 255 * mix
B = B * (1 - mix) + 255 * mix
*/
for(i = 0; i < len; i+= 4) {
mix = data[i] / 255; // mix using red
data[i ] = 255 * mix; // red channel
data[i+1] = data[i+1] * (1 - mix) + 255 * mix; // green channel
data[i+2] = data[i+2] * (1 - mix) + 255 * mix; // blue channel
}
ctx.putImageData(idata,0,0);
};
img.crossOrigin = "";
img.src = "//i.imgur.com/ptOPQZx.png";
document.body.appendChild(img)
<h4>Red removed + to white</h4><canvas id=c></canvas><h4>Original:</h4>

How to encode a .wmv file that XNA DirectShow will play properly?

I'm playing around with XNA DirectShow to stream a video from a file rather than loading it into my project (I'm fully aware of the XNA MediaPlayer class by the way). It plays the sample video it came with no problem. When I try to make my own .wmv from a series of PNG files I have using ffmpeg the video plays but is all blue (should be mostly yellow). Pixel format wrong? Wrong codec? I'm certainly no expert in these waters..
The sample video is a VC-1 WMV3 apparantly, and I don't think I can replicate that? What encoding/codec/fileformat should I be using?
Also! If transparent video background is possible, that would be amazing. Is it?
Ok I've solved it - I simply switched the order pixels are assigned to when DirectShow creates its output texture. In the VideoPlayer class I changed UpdateBuffer to:
private void UpdateBuffer()
{
int waitTime = avgTimePerFrame != 0 ? (int)((float)avgTimePerFrame / 10000) : 20;
int samplePosRGBA = 0;
int samplePosRGB24 = 0;
while (true)
{
for (int y = 0, y2 = videoHeight - 1; y < videoHeight; y++, y2--)
{
for (int x = 0; x < videoWidth; x++)
{
samplePosRGBA = (((y2 * videoWidth) + x) * 4);
samplePosRGB24 = ((y * videoWidth) + x) * 3;
//make transparent if pixel matches a certain colour
if (WhiteTransparent && bgrData[samplePosRGB24 + 2] > 200 && bgrData[samplePosRGB24 + 1] > 200 && bgrData[samplePosRGB24 + 0] > 200)
{
//transparent pixel
videoFrameBytes[samplePosRGBA + 0] = 0;
videoFrameBytes[samplePosRGBA + 1] = 0;
videoFrameBytes[samplePosRGBA + 2] = 0;
videoFrameBytes[samplePosRGBA + 3] = 0;
}
else
{
//modified pixel format order - switch the 2,1,0 on the right for other formats..
videoFrameBytes[samplePosRGBA + 0] = bgrData[samplePosRGB24 + 2];
videoFrameBytes[samplePosRGBA + 1] = bgrData[samplePosRGB24 + 1];
videoFrameBytes[samplePosRGBA + 2] = bgrData[samplePosRGB24 + 0];
videoFrameBytes[samplePosRGBA + 3] = alphaTransparency;
}
}
}
frameAvailable = false;
while (!frameAvailable)
{ Thread.Sleep(waitTime); }
}
}
which also displays any white areas as transparent in the final image if a bool I added to the class - WhiteTransparent is true. Crude I know, but it's doing the trick for me. Just use the lines in the else statement if not desired.

Implementing Ordered Dithering (24 bit RGB to 3 bit per channel RGB)

I'm writing an image editing programme, and I need functionality to dither any arbitrary 24-bit RGB image (I've taken care of loading it with CoreGraphics and such) to an image with 3 bit colour channels, then displaying it. I've set up my matrices and such, but I've not got any results from the code below besides a simple pattern that is applied to the image:
- (CGImageRef) ditherImageTo16Colours:(CGImageRef)image withDitheringMatrixType:(SQUBayerDitheringMatrix) matrix {
if(image == NULL) {
NSLog(#"Image is NULL!");
return NULL;
}
unsigned int imageWidth = CGImageGetWidth(image);
unsigned int imageHeight = CGImageGetHeight(image);
NSLog(#"Image size: %u x %u", imageWidth, imageHeight);
CGContextRef context = CGBitmapContextCreate(NULL,
imageWidth,
imageHeight,
8,
4 * (imageWidth),
CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB),
kCGImageAlphaNoneSkipLast);
CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image); // draw it
CGImageRelease(image); // get rid of the image, we don't want it anymore.
unsigned char *imageData = CGBitmapContextGetData(context);
unsigned char ditheringModulusType[0x04] = {0x02, 0x03, 0x04, 0x08};
unsigned char ditheringModulus = ditheringModulusType[matrix];
unsigned int red;
unsigned int green;
unsigned int blue;
uint32_t *memoryBuffer;
memoryBuffer = (uint32_t *) malloc((imageHeight * imageWidth) * 4);
unsigned int thresholds[0x03] = {256/8, 256/8, 256/8};
for(int y = 0; y < imageHeight; y++) {
for(int x = 0; x < imageWidth; x++) {
// fetch the colour components, add the dither value to them
red = (imageData[((y * imageWidth) * 4) + (x << 0x02)]);
green = (imageData[((y * imageWidth) * 4) + (x << 0x02) + 1]);
blue = (imageData[((y * imageWidth) * 4) + (x << 0x02) + 2]);
if(red > 36 && red < 238) {
red += SQUBayer117_matrix[x % ditheringModulus][y % ditheringModulus];
} if(green > 36 && green < 238) {
green += SQUBayer117_matrix[x % ditheringModulus][y % ditheringModulus];
} if(blue > 36 && blue < 238) {
blue += SQUBayer117_matrix[x % ditheringModulus][y % ditheringModulus];
}
// memoryBuffer[(y * imageWidth) + x] = (0xFF0000 + ((x >> 0x1) << 0x08) + (y >> 2));
memoryBuffer[(y * imageWidth) + x] = find_closest_palette_colour(((red & 0xFF) << 0x10) | ((green & 0xFF) << 0x08) | (blue & 0xFF));
}
}
//CGContextRelease(context);
context = CGBitmapContextCreate(memoryBuffer,
imageWidth,
imageHeight,
8,
4 * (imageWidth),
CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB),
kCGImageAlphaNoneSkipLast);
NSLog(#"Created context from buffer: %#", context);
CGImageRef result = CGBitmapContextCreateImage(context);
return result;
}
Note that find_closest_palette_colour doesn't do anything besides returning the original colour right now for testing.
I'm trying to implement the example pseudocode from Wikipedia, and I don't really get anything out of that right now.
Anyone got a clue on how to fix this up?
Use the code that I have provided here: https://stackoverflow.com/a/17900812/342646
This code converts the image to a single-channel gray-scale first. If you want the dithering to be done on a three-channel image, you can just split your image into three channels and call the function three times (once per channel).

How to deal with RGB to YUV conversion

The formula says:
Y = 0.299 * R + 0.587 * G + 0.114 * B;
U = -0.14713 * R - 0.28886 * G + 0.436 * B;
V = 0.615 * R - 0.51499 * G - 0.10001 * B;
What if, for example, the U variable becomes negative?
U = -0.14713 * R - 0.28886 * G + 0.436 * B;
Assume maximum values for R and G (ones) and B = 0
So, I am interested in implementing this convetion function in OpenCV,
So, how to deal with negative values?
Using float image? anyway please explain me, may be I don't understand something..
Y, U and V are all allowed to be negative when represented by decimals, according to the YUV color plane.
You can convert RGB<->YUV in OpenCV with cvtColor using the code CV_YCrCb2RGB for YUV->RGB and CV_RGBYCrCb for RGB->YUV.
void cvCvtColor(const CvArr* src, CvArr* dst, int code)
Converts an image from one color space
to another.
for planar formats OpenCV is not the right tool for the job. Instead you are better off using ffmpeg. for example
static void rgbToYuv(byte* src, byte* dst, int width,int height)
{
byte* src_planes[3] = {src,src + width*height, src+ (width*height*3/2)};
int src_stride[3] = {width, width / 2, width / 2};
byte* dest_planes[3] = {dst,NULL,NULL};
int dest_stride[3] = {width*4,0,0};
struct SwsContext *img_convert_ctx = sws_getContext(
width,height,
PIX_FMT_YUV420P,width,height,PIX_FMT_RGB32,SWS_POINT,NULL,NULL,NULL);
sws_scale(img_convert_ctx, src_planes,src_stride,0,height,dest_planes,dest_stride);
sws_freeContext(img_convert_ctx);
}
will convert a YUV420 image to RGB32

Resources