Color Transformations and the Color Matrix

本文介绍如何使用颜色矩阵进行图像的颜色变换,包括调整颜色强度、对比度、亮度等,并提供了具体的编程实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

So far we've see transforming graphics shapes from one state to another but did you ever think about transforming colors? Why would you want to transform an image's colors? Suppose you want to provide gray scale effects, or need to reduce or increase the contrast, brightness, or redness of an image. That's when a color matrix is needed.

As we have discussed in earlier chapters, the color of each pixel of a GDI+ image or bitmap is represented by a 32-bit number where 8-bits are used for each of the red, green, blue, and alpha components. Each of the four components is a number from 0 to 255. For red, green, and blue components, 0 represents no intensity and 255 represents full intensity. 

For the alpha component, 0 represents fully transparent and 255 represents fully opaque. A color vector includes four items, i.e., (R, G, B, A). The minimum values for this vector are (0, 0, 0, 0) and the maximum values for this vector are (255, 255, 255, 255).

GDI+ allows the use of values between 0 and 1 where 0 represents the minimum intensity and 1 represents the maximum intensity. These values are used in a color matrix to represent the intensity and opacity of color components. For example, the color vector with the minimum values is (0, 0, 0, 0) and the color vector with maximum values is (1, 1, 1, 1).

In color transformation, we apply a color matrix on a color vector. This can be done by multiplying a 4 x 4 matrix. However a 4 x 4 matrix supports only linear transformations such as rotation, and scaling. To perform non-linear transformations such as translation, you need to use a 5 x 5 matrix. The element of the fifth row and the fifth column of the matrix must be 1 and all of the other entries in the five columns must be 0.

The elements of the matrix are identified using a zero-based index. Thus the first element of the matrix is M[0][0] and the last element of a 5 x 5 matrix is M[4][4]. A 5x5 identity matrix is shown in Figure 10-19. In this matrix, the elements M[0][0], M[1][1], M[2][2] and M[3][3] represent the red, blue, green, and alpha factors respectively. The element M[4][4] means nothing and it must always be 1.

Now suppose you want to double the intensity of the red component of a color: simply set M[0][0] = 2. For example, the matrix shown in Figure 10-20 doubles the intensity of the red component, decreases the intensity of the green component by half, triples the intensity of the blue component, and decreases the opacity of the color by half (semi-transparent).

In the above matrix, we multiplied the intensity values. You can also add by using other matrix elements. For example, the matrix shown in Figure 10-21 will double the intensity of red component and add 0.2 to each of the red, green, and blue component intensities:



The ColorMatrix Class

In this section, we will discuss the ColorMatrix class. As you can guess from its name, this class defines a matrix of colors. In the preceding sections, we discussed the Matrix class. The ColorMatrix class is not very different from the Matrix class. The Matrix class is used in general transformation to transform graphics shapes and images, while the ColorMatrix class is specifically designed to transform colors. Before we see practical use of the color transformation, we will discuss the ColorMatrix class, its properties and methods.

The ColorMatrix class constructor takes an array, which contains the values of matrix items. The Item property of this class represents a cell of the matrix and can be used to get and set cell values. Besides the Item property, the ColorMatrix class provides 25 MatrixXY properties, which represent items of the matrix at row (X+1) and column (Y+1). MatrixXY properties can be used to get and set an item's value. Listing 10-15 creates a ColorMatrix object with item(4,4) set to 0.5 (half opacity). After that it sets the value of item (3, 4) to 0.8 and item (1, 1) to 0.3.

float[][] ptsArray ={ new float[] {1, 0, 0, 0, 0},
new float
[] {0, 1, 0, 0, 0},
new float
[] {0, 0, 1, 0, 0},
new float
[] {0, 0, 0, 0.5f, 0}, 
new float
[] {0, 0, 0, 0, 1}}; 
ColorMatrix clrMatrix = 
new
 ColorMatrix(ptsArray);
if
( clrMatrix.Matrix34 <= 0.5)
{
clrMatrix.Matrix34 = 0.8f;
clrMatrix.Matrix11 = 0.3f;
}

Listing 10-15. Creating a ColorMatrix object

Now let's apply color matrices transform colors.

Matrix Operations in Image Processing

Re-coloring, the process of changing image colors, is a good example of color transformation. Re-coloring includes changing colors, intensity, contrast, and brightness of an image. It all can be done using the ImageAttribute class and its methods.

The ColorMatrix can be applied on an Image using the SetColorMatrix method of the ImageAttribute class. The ImageAttribute object is used as a parameter when you call DrawImage.

Translating Colors

Translating colors increases or decreases color intensities by a set amount (not by multiplying them). Each color component (red, green, and blue) has 255 different intensity levels ranging from 0 to 255. For example, assume the current intensity level for the red component of a color is 100. Changing its intensity level to 150 would imply translating by 50.

In a color matrix representation, the intensity varies from 0 to 1. The last row's first four elements of a color matrix represent the translation of red, green, blue, and alpha components of a color, per Figure 10-21. Hence adding a value to these elements will transform a color. For example, t1, t2, t3, and t4 values in the following color matrix represent the red, green, blue and alpha component translations:

Color Matrix ={1, 0, 0, 0, 0},{0, 1, 0, 0, 0},{0, 0, 1, 0, 0},{0, 0, 0, 1, 0},{t1, t2, t3, t4, 1}};

Listing 10-16 uses a ColorMatrix to translate colors. We change the current intensity of the red component to 0.90. First we create a Graphics object using the CreateGraphics method and create a Bitmap object from a file. After that we create an array of ColorMatrix elements and create a ColorMatrix from this array. Then we create an ImageAttributes object and set the color matrix using SetColorMatrix, which takes the ColorMatrix object as its first parameter. After all that we draw two images. The first image has no effects, while the second image shows the result of our color matrix transformation. Finally, we dispose the objects.

private void TranslationMenu_Click(object sender, System.EventArgs e)
{
// Create a Graphics object
Graphics g = this
.CreateGraphics();
g.Clear(
this
.BackColor);
// Create a Bitmap
Bitmap curBitmap = new
 Bitmap("roses.jpg"); 
// ColorMatrix elements
float
[][] ptsArray = 

new float
[] {1, 0, 0, 0, 0},
new float
[] {0, 1, 0, 0, 0},
new float
[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 1, 0},new float
[] {.90f, .0f, .0f, .0f, 1}
};
// Create a ColorMatrix
ColorMatrix clrMatrix = new
 ColorMatrix(ptsArray);
// Create ImageAttributes
ImageAttributes imgAttribs = new
 ImageAttributes(); 
// Set color matrix
imgAttribs.SetColorMatrix(clrMatrix, 
ColorMatrixFlag.Default,
ColorAdjustType.Default);
// Draw image with no affects
g.DrawImage(curBitmap, 0, 0, 200, 200);
// Draw image with ImageAttributes
g.DrawImage(curBitmap,
new
 Rectangle(205, 0, 200, 200), 
0, 0, curBitmap.Width, curBitmap.Height, 
GraphicsUnit.Pixel, imgAttribs) ;
// Dispose
curBitmap.Dispose();
g.Dispose();
}  

Listing 10-16. Translating colors using ColorMatrix.

Listing 10-16 generates Figure 10-22. The original image is on the left, while on the right we have the results of our color translation. If you change the values of other components (red, blue, and alpha) in the last row of the ColorMatrix, you'll see different results.

Scaling Colors

Scaling color involves multiplying a color component value by a scaling factor. For example, the t1, t2, t3, and t4 values in the following color matrix represent the red, green, blue and alpha components. If you change the value of M[2][2] to 0.5, the transformation operation will multiply the green component by 0.5, cutting its intensity by half.

Color Matrix =
{t1, 0, 0, 0, 0},
{0, t2, 0, 0, 0},
{0, 0, t3, 0, 0},
{0, 0, 0, t4, 0},
{0, 0, 0, 0, 1}};

Listing 10-17 uses the ColorMatrix to scale image colors.

private void ScalingMenu_Click(object sender,System.EventArgs e)
{
// Create a Graphics
Graphics g = this
.CreateGraphics();
g.Clear(
this
.BackColor);
// Create a Bitmap
Bitmap curBitmap = new
 Bitmap("roses.jpg"); 
// ColorMatrix elements
float
[][] ptsArray = 

new float
[] {1, 0, 0, 0, 0},
new float
[] {0, 0.8f, 0, 0, 0},
new float
[] {0, 0, 0.5f, 0, 0},
new float
[] {0, 0, 0, 0.5f, 0},
new float
[] {0, 0, 0, 0, 1}
};
// Create a ColorMatrix
ColorMatrix clrMatrix = new
 ColorMatrix(ptsArray);
// Create ImageAttributes
ImageAttributes imgAttribs = new
 ImageAttributes(); 
// Set color matrix
imgAttribs.SetColorMatrix(clrMatrix,
ColorMatrixFlag.Default,
ColorAdjustType.Default);
// Draw an image with no affects
g.DrawImage(curBitmap, 0, 0, 200, 200);
// Draw Image with image attributes
g.DrawImage(curBitmap, 
new
 Rectangle(205, 0, 200, 200), 
0, 0, curBitmap.Width, curBitmap.Height, 
GraphicsUnit.Pixel, imgAttribs) ;
// Dispose
curBitmap.Dispose();
g.Dispose(); 

 

Listing 10-17. Scaling Colors

Output from Listing 10-18 is shown in Figure 10-23. The original image is on the left, and on the right is the image after color scaling. If you change the values of t1, t2, t3, and t4, you will see different results.



Shearing Colors

Earlier in this chapter, we saw image shearing. It can be thought of as anchoring one corner of a rectangular region and stretching the opposite corner horizontally, vertically or in both directions. Shearing colors is basically the same process, but shearing color components rather than the image itself. 

Color shearing increases or decreases a color component by an amount proportional to another color component. For example, consider the transformation where the red component is increased by one half the value of the blue component. Under such a transformation, the color (0.2, 0.5, 1) would become (0.7, 0.5, 1). The new red component is 0.2 + (1/2)(1) = 0.7. The following ColorMatrix is used to shear image colors.

float[][] ptsArray = { 
new float
[] {1, 0, 0, 0, 0},
new float
[] {0, 1, 0, 0, 0},
new float
[] {.50f, 0, 1, 0, 0},
new float
[] {0, 0, 0, 1, 0},
new float
[] {0, 0, 0, 0, 1}};
ColorMatrix clrMatrix = 
new
 ColorMatrix(ptsArray);

Now if you substitute this color matrix in Listing 10-17, the output will look like Figure 10-24.



Rotating Colors

Color, in GDI+ have four components: red, green, blue, and alpha. Rotating all four components in a four dimension space is hard to visualize. However it can be visualized in a three dimensional space. To do this, we drop the alpha component from the color structure and assume that there are only three colors: red, green, and blue as shown in Figure 10-25. The three colors, red, green, and blue are perpendicular to each other, so the angle between any two primary colors is 90 degrees.

Let's say the red, green, and blue colors are represented by points (1, 0, 0), (0, 1, 0), and (0, 0, 1) respectively. If you rotate a color with green component 1, and red and blue component 0 each by 90 degrees, the new value of the color would have red component 1, and green and blue component 0 each. If you rotate less than 90 degrees, the location would be in between green and red.

Figure 10-26 shows how to initialize a color matrix to perform rotations about each of the three components: red, green, blue.

Listing 10-18 rotates the colors by 45 degrees from the red component.  

private void RotationMenu_Click(object sender, System.EventArgs e)
{
float
 degrees = 45.0f;
double
 r = degrees*System.Math.PI/180; 
// Create a Graphics object 
Graphics g = this
.CreateGraphics();
g.Clear(
this
.BackColor);
// Create a Bitmap from a file
Bitmap curBitmap = new
 Bitmap("roses.jpg"); 
// ColorMatrix elements
float
[][] ptsArray = 

new float[] {(float
)System.Math.Cos(r), 
(
float
)System.Math.Sin(r),0, 0, 0},
new float[] {(float
)-System.Math.Sin(r),
(
float
)-System.Math.Cos(r), 
0, 0, 0},
new float
[] {.50f, 0, 1, 0, 0},
new float
[] {0, 0, 0, 1, 0},
new float
[] {0, 0, 0, 0, 1}
};
// Create a ColorMatrix
ColorMatrix clrMatrix = new
 ColorMatrix(ptsArray);
// Create ImageAttributes
ImageAttributes imgAttribs = new
 ImageAttributes();
// Set ColorMatrix to ImageAttributes
imgAttribs.SetColorMatrix(clrMatrix, 
ColorMatrixFlag.Default,
ColorAdjustType.Default);
// Draw image with no affects
g.DrawImage(curBitmap, 0, 0, 200, 200);
// Draw image with ImageAttributes
g.DrawImage(curBitmap, 
new
 Rectangle(205, 0, 200, 200), 
0, 0, curBitmap.Width, curBitmap.Height, 
GraphicsUnit.Pixel, imgAttribs) ;
// Dispose
curBitmap.Dispose();
g.Dispose(); 


Listing 10-18. Color Rotation

Figure 10-27 slows output from Listing 10-18. On the left is the original image, and on the right is the image after color rotation.

* This example program shows how 128 images of the interior of a church can be * combined into a mosaic that covers a 360x130 degree view. The images were acquired * with a camera in which the exposure and white balance were set to automatic. * Therefore, there are very large brightness and color differences between the images. * Hence, adjust_mosaic_images is used to align the images radiometrically. * Furthermore, blending is used to hide the transitions between the individual * images that make up the mosaic. dev_update_off () dev_close_window () dev_open_window (0, 0, 978, 324, 'black', WindowHandle) dev_set_part (0, 0, 647, 1955) set_display_font (WindowHandle, 16, 'mono', 'true', 'false') dev_set_color ('yellow') set_tposition (WindowHandle, 20, 20) write_string (WindowHandle, 'Reading images...') * Read the 128 images that make up the mosaic. gen_empty_obj (Images) for J := 1 to 128 by 1 read_image (Image, 'panorama/sankt_martin_automatic_' + J$'03d') concat_obj (Images, Image, Images) endfor get_image_size (Image, Width, Height) * Construct the tuples that determine which images should be matched. The * mosaic images were acquired as 16 vertical strips of 8 images each. * For each image, we match the image below the current image in the same * strip and the image to the left of the current image in the adjacent strip. FF := [1,1,2,2,3,3,4,4,5,5,6,6,7,7,8] TT := [2,9,3,10,4,11,5,12,6,13,7,14,8,15,16] From := [] To := [] for J := 0 to 15 by 1 From := [From,(FF - 1 + 8 * J) % 128 + 1] To := [To,(TT - 1 + 8 * J) % 128 + 1] endfor * Initialize the data that is required for the self-calibration. HomMatrices2D := [] Rows1 := [] Cols1 := [] Rows2 := [] Cols2 := [] NumMatches := [] for J := 0 to |From| - 1 by 1 * Select the images to match. select_obj (Images, ImageF, From[J]) select_obj (Images, ImageT, To[J]) * Perform the point extraction of the images. points_foerstner (ImageF, 1, 2, 3, 50, 0.1, 'gauss', 'true', RowsF, ColsF, CoRRJunctions, CoRCJunctions, CoCCJunctions, RowArea, ColumnArea, CoRRArea, CoRCArea, CoCCArea) points_foerstner (ImageT, 1, 2, 3, 50, 0.1, 'gauss', 'true', RowsT, ColsT, CoRRJunctions1, CoRCJunctions1, CoCCJunctions1, RowArea1, ColumnArea1, CoRRArea1, CoRCArea1, CoCCArea1) concat_obj (ImageT, ImageF, ImageTF) tile_images_offset (ImageTF, TiledImage, [0,0], [0,Width + 20], [-1,-1], [-1,-1], [-1,-1], [-1,-1], 2 * Width + 20, Height) gen_cross_contour_xld (PointsF, RowsF, ColsF + Width + 20, 6, rad(45)) gen_cross_contour_xld (PointsT, RowsT, ColsT, 6, rad(0)) * Convert the images to gray value images. rgb1_to_gray (ImageF, ImageFG) rgb1_to_gray (ImageT, ImageTG) * Determine the projective transformation between the images. proj_match_points_ransac (ImageFG, ImageTG, RowsF, ColsF, RowsT, ColsT, 'ncc', 10, 0, 0, 648, 968, [rad(-10),rad(40)], 0.5, 'gold_standard', 10, 42, HomMat2D, Points1, Points2) * After this, we accumulate the required data. HomMatrices2D := [HomMatrices2D,HomMat2D] Rows1 := [Rows1,subset(RowsF,Points1)] Cols1 := [Cols1,subset(ColsF,Points1)] Rows2 := [Rows2,subset(RowsT,Points2)] Cols2 := [Cols2,subset(ColsT,Points2)] NumMatches := [NumMatches,|Points1|] * The rest of the code within the loop visualizes the point matches. RF := subset(RowsF,Points1) CF := subset(ColsF,Points1) + Width + 20 RT := subset(RowsT,Points2) CT := subset(ColsT,Points2) gen_empty_obj (Matches) for K := 0 to |RF| - 1 by 1 gen_contour_polygon_xld (Match, [RF[K],RT[K]], [CF[K],CT[K]]) concat_obj (Matches, Match, Matches) endfor dev_clear_window () dev_display (TiledImage) dev_set_color ('blue') dev_display (Matches) dev_set_color ('green') dev_display (PointsF) dev_display (PointsT) dev_set_color ('yellow') set_tposition (WindowHandle, 20, 20) write_string (WindowHandle, 'Matches between images ' + From[J]$'d' + ' and ' + To[J]$'d') endfor dev_clear_window () dev_set_window_extents (-1, -1, 856, 428) dev_set_color ('yellow') set_tposition (WindowHandle, 20, 20) write_string (WindowHandle, 'Performing self-calibration...') * Perform the self-calibration. stationary_camera_self_calibration (128, 968, 648, 6, From, To, HomMatrices2D, Rows1, Cols1, Rows2, Cols2, NumMatches, 'gold_standard', ['focus','principal_point','kappa'], 'true', CameraMatrix, Kappa, RotationMatrices, X, Y, Z, Error) dev_clear_window () dev_set_color ('yellow') set_tposition (WindowHandle, 20, 20) write_string (WindowHandle, 'Removing radial distortions...') * Remove the radial distortions from the images. cam_mat_to_cam_par (CameraMatrix, Kappa, 968, 648, CamParam) change_radial_distortion_cam_par ('fixed', CamParam, 0, CamParOut) gen_radial_distortion_map (Map, CamParam, CamParOut, 'bilinear') map_image (Images, Map, Images) dev_clear_window () dev_set_color ('yellow') set_tposition (WindowHandle, 20, 20) write_string (WindowHandle, 'Adjusting the images radiometrically...') * Before we adjust the images radiometrically, we compute the perspective * transformations between the images from the camera matrix and the rotation * matrices that are returned by the self-calibration. They are more accurate * than the perspective transformations that are returned by the matching * since they have been optimized over all images. For details on how the * perspective transformation matrices are computed by the code below, see the * documentation of stationary_camera_self_calibration. hom_mat2d_invert (CameraMatrix, CameraMatrixInv) PermMat := [0.0,1.0,0.5,1.0,0.0,0.5,0.0,0.0,1.0] hom_mat2d_invert (PermMat, PermMatInv) hom_mat2d_compose (CameraMatrixInv, PermMatInv, CamMatPermInv) hom_mat2d_compose (PermMat, CameraMatrix, CamMatPerm) HomMats2D := [] for J := 0 to |From| - 1 by 1 RotMatFrom := RotationMatrices[9 * (From[J] - 1):9 * (From[J] - 1) + 8] RotMatTo := RotationMatrices[9 * (To[J] - 1):9 * (To[J] - 1) + 8] hom_mat2d_transpose (RotMatFrom, RotMatFromInv) hom_mat2d_compose (RotMatTo, RotMatFromInv, RotMat) hom_mat2d_compose (RotMat, CamMatPermInv, RotCamMatInv) hom_mat2d_compose (CamMatPerm, RotCamMatInv, HomMat2D) HomMats2D := [HomMats2D,HomMat2D] endfor * Now adjust the images radiometrically. Since the exposure and white balance * were set to automatic, we calculate 'mult_gray'. Since the camera is a consumer * camera and therefore has a highly nonlinear response, we compute 'response'. * To compensate the vignetting in the images, we compute 'vignetting'. Finally, * to speed up the optimization, we use a subsampling by a factor of 4. adjust_mosaic_images (Images, CorrectedImages, From, To, 118, HomMats2D, 'gold_standard', ['mult_gray','response','vignetting','subsampling_4'], 'laguerre') * Since the reference image was not aligned perfectly horizontally, we modify the * calibrated rotation matrices by rotating them by -5.5 degrees around the x axis. hom_mat3d_identity (HomMat3D) hom_mat3d_rotate (HomMat3D, rad(-5.5), 'x', 0, 0, 0, HomMat3D) RotMat := [HomMat3D[0:2],HomMat3D[4:6],HomMat3D[8:10]] RotMats := [] for J := 0 to 127 by 1 RotMatCalib := RotationMatrices[J * 9:J * 9 + 8] hom_mat2d_compose (RotMatCalib, RotMat, RotMatRot) RotMats := [RotMats,RotMatRot] endfor dev_clear_window () dev_set_color ('yellow') set_tposition (WindowHandle, 20, 20) write_string (WindowHandle, 'Creating spherical mosaic of the original images...') * Create the spherical mosaic of the original images. gen_spherical_mosaic (Images, SphericalMosaicOrig, CameraMatrix, RotMats, -90, 90, -180, 180, 0, 'voronoi', 'bilinear') get_image_size (SphericalMosaicOrig, Width, Height) dev_set_part (0, 0, Height - 1, Width - 1) dev_clear_window () dev_display (SphericalMosaicOrig) dev_set_color ('yellow') set_tposition (WindowHandle, Height - 300, 20) write_string (WindowHandle, 'Spherical mosaic of the original images') set_tposition (WindowHandle, Height - 150, 20) write_string (WindowHandle, 'Press \'Run\' to continue') stop () dev_clear_window () dev_set_color ('yellow') set_tposition (WindowHandle, 20, 20) write_string (WindowHandle, 'Creating spherical mosaic of the radiometrically adjusted images...') * Create the spherical mosaic of the radiometrically adjusted images. gen_spherical_mosaic (CorrectedImages, SphericalMosaicAdjust, CameraMatrix, RotMats, -90, 90, -180, 180, 0, 'voronoi', 'bilinear') get_image_size (SphericalMosaicAdjust, Width, Height) dev_set_part (0, 0, Height - 1, Width - 1) dev_clear_window () dev_display (SphericalMosaicAdjust) dev_set_color ('yellow') set_tposition (WindowHandle, Height - 300, 20) write_string (WindowHandle, 'Spherical mosaic of the radiometrically adjusted images') set_tposition (WindowHandle, Height - 150, 20) write_string (WindowHandle, 'Press \'Run\' to continue') stop () dev_clear_window () dev_set_color ('yellow') set_tposition (WindowHandle, 20, 20) write_string (WindowHandle, 'Creating blended spherical mosaic of the radiometrically adjusted images...') * Create the blended spherical mosaic of the radiometrically adjusted images. gen_spherical_mosaic (CorrectedImages, SphericalMosaicAdjustBlend, CameraMatrix, RotMats, -90, 90, -180, 180, 0, 'blend', 'bilinear') get_image_size (SphericalMosaicAdjustBlend, Width, Height) dev_set_part (0, 0, Height - 1, Width - 1) dev_clear_window () dev_display (SphericalMosaicAdjustBlend) dev_set_color ('yellow') set_tposition (WindowHandle, Height - 300, 20) write_string (WindowHandle, 'Blended spherical mosaic of the radiometrically adjusted images')
最新发布
06-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值