2015-08-27 20:42:48 xingyanxiao 阅读数 4653

前言:之前在公司做项目的用到photoshop颜色空间的一些相关方法,在此总结一下。下面原理部分是从我的总结文档里截取来的。需要复制的童鞋自己手写一下~


2、程序部分

1)Matlab实验程序。

clc;clear;close all;
Image=imread('Fotor_LomoOrg.bmp');
figure(1);
imshow(Image);

R=double(Image(:,:,1));
G=double(Image(:,:,2));
B=double(Image(:,:,3));

%调整参数
HighLight=238;
Shadow=159;
Midtones=0.51;

Diff=HighLight-Shadow;
rDiff=R-Shadow;
gDiff=G-Shadow;
bDiff=B-Shadow;

rDiff(rDiff<0)=0;  
gDiff(gDiff<0)=0;  
bDiff(bDiff<0)=0;  

R=(rDiff/Diff).^(1/Midtones)*255;
G=(gDiff/Diff).^(1/Midtones)*255;
B=(bDiff/Diff).^(1/Midtones)*255;

R(R>255)=255;
G(G>255)=255;
B(B>255)=255;

img(:,:,1)=uint8(R);
img(:,:,2)=uint8(G);
img(:,:,3)=uint8(B);
figure(2);
imshow(img);
</span>

2)C程序

void LevelAdjustRGB(unsigned char *pSrc, unsigned char *pDest, int nWidth, int nHeight,int nShadow,int nHighLight,double dMidtone)
{
       //局部变量声明
	int i = 0; 
	int nLength  = nWidth * nHeight;
	double dDiff = nHighLight-nShadow;
	int *nRgbDiff = new int[3 * nLength];

	for(i = 0;i < 3 * nLength; i++)
	{
		nRgbDiff[i] = pSrc[i] - nShadow;
		if(nRgbDiff[i] < 0)
		{
			nRgbDiff[i] = 0;
		}

		//此处必须用int作为中间变量 不能用char  会有数据截断
		int nTemp = static_cast<int>(pow((nRgbDiff[i] / dDiff),1 / dMidtone) * 255);
		pDest[i] = CLIP8(nTemp);
	}
	//释放空间
	if(!nRgbDiff)
	{
		delete []nRgbDiff;
		nRgbDiff = NULL;
	}
}</span>

其中
#define  CLIP8(a)  (((a) & 0xFFFFFF00) ? (((a) < 0) ? 0 : 255 ) : (a))      ///<判断8位数据范围

3、实验结果,同photoshop处理结果


图1  原图


图2 nShadow=159  nHighLight=238  dMidtone=0.51结果

2015-02-27 19:16:17 maozefa 阅读数 9667

阅读提示

    《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。

    《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。

    尽可能保持二者内容一致,可相互对照。

    本文代码必须包括《C++图像处理 -- 数据类型及公用函数文章中的BmpData.h头文件。


    在Photoshop中,图像色阶调整应用很广泛,本文介绍的图像色阶调整过程与Photoshop处理效果基本一致。

    Photoshop的色阶调整分输入色阶调整和输出色阶调整,其中输入色阶调整有3个调整点,即通常所说的黑场、白场及灰场调整。

    输入色阶调整的基本算法并不复杂,首先计算出白场与黑场的离差Diff,然后计算出像素各份量值与黑场的离差rgbDiff,如果rgbDiff<=0,像素各份量值等于0,否则,计算以rgbDiff与Diff的比值为底的灰场倒数的幂。用公式表示:

    Diff = Highlight -Shadow

    rgbDiff = RGB - Shadow

    clRGB = Power(rgbDiff / Diff,  1 / Midtones)

    其中Shadow为输入色阶低端数据(黑场),Highlight为输入色阶高端数据(白场), Midtones为输入色阶中间数据(灰场),Diff为二者的离差(必须大于1),RGB为调整前的像素分量值,clRGB为调整输入色阶后的像素分量值。

    输出色阶调整更简单,首先计算输出色阶白场与黑场的离差与255的比值系数,然后用输入色阶调整后的像素分量值乘上这个系数,再加上输出黑场值即可。用公式表示:

    outClRGB = clRGB * (outHighlight - outShadow) / 255 + outShadow

    其中,outShadow为输出黑场,outHighlight为输出白场,outClRGB为全部色阶调整后的像素分量值。

    前面已经提到输入色阶黑白场的离差必须大于1,而输入色阶并没有这个限制,输出黑白场的离差可以为负数,当输出黑场与白场完全颠倒时,输出色阶调整后的图片为原图片的负片。

    色阶调整涉及四个通道,即R、G、B各分量通道及整体颜色通道,如果每个通道单独调整,将是比较麻烦和耗时的,本文采用色阶表替换法,可一次性完成所有四个通道的色阶调整。

    下面是图像色阶调整的代码:

// 色阶项结构
typedef struct
{
	UINT Shadow;
	FLOAT Midtones;
	UINT Highlight;
	UINT OutShadow;
	UINT OutHighlight;
}ColorLevelItem, *PColorLevelItem;

typedef struct
{
	ColorLevelItem Blue;
	ColorLevelItem Green;
	ColorLevelItem Red;
	ColorLevelItem RGB;
}ColorLevelData, *PColorLevelData;

VOID InitColorLevelData(PColorLevelData clData)
{
	PColorLevelItem item = &clData->Blue;
	for (INT i = 0; i < 4; i ++, item ++)
	{
		item->Shadow = item->OutShadow = 0;
		item->Highlight = item->OutHighlight = 255;
		item->Midtones = 1.0;
	}
}

BOOL GetColorLevelTable(PColorLevelItem item, LPBYTE clTable)
{
	INT diff = (INT)(item->Highlight - item->Shadow);
	INT outDiff = (INT)(item->OutHighlight - item->OutShadow);

	if (!((item->Highlight <= 255 && diff < 255 && diff >= 2) ||
		(item->OutShadow <= 255 && item->OutHighlight <= 255 && outDiff < 255) ||
		(!(item->Midtones > 9.99 && item->Midtones > 0.1) && item->Midtones != 1.0)))
		return FALSE;

	DOUBLE coef = 255.0 / diff;
	DOUBLE outCoef = outDiff / 255.0;
	DOUBLE exponent = 1.0 / item->Midtones;

	for (INT i = 0; i < 256; i ++)
	{
		INT v;
		// 计算输入色阶黑白场
		if (clTable[i] <= (BYTE)item->Shadow)
			v = 0;
		else
		{
			v = (INT)((clTable[i] - item->Shadow) * coef + 0.5);
			if (v > 255)
				v = 255;
		}
		// 计算输入色阶灰场
		v = (INT)(pow(v / 255.0, exponent) * 255.0 + 0.5);
		// 计算输出色阶
		clTable[i] = (BYTE)(v * outCoef + item->OutShadow + 0.5);
	}
	return TRUE;
}

BOOL CheckColorLevelData(PColorLevelData clData, BYTE clTables[][256])
{
	BOOL result = FALSE;
	INT i, j;
	for (i = 0; i < 3; i ++)
	{
		for (j = 0; j < 256; j ++)
			clTables[i][j] = (BYTE)j;
	}
	PColorLevelItem item = &clData->Blue;
	for (i = 0; i < 3; i ++, item ++)
	{
		if (GetColorLevelTable(item, clTables[i]))
			result = TRUE;
	}
	for (i = 0; i < 3; i ++)
	{
		if (!GetColorLevelTable(item, clTables[i]))
			break;
		result = TRUE;
	}
	return result;
}

// 图像数据色阶调整
VOID ImageColorLevel(BitmapData *dest, BitmapData *source, PColorLevelData clData)
{
	PARGBQuad pd, ps;
	UINT width, height;
	INT dstOffset, srcOffset;
	GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);

	BYTE clTables[3][256];
	if (CheckColorLevelData(clData, clTables))
	{
		for (UINT y = 0; y < height; y ++, ps += srcOffset, pd += dstOffset)
		{
			for (UINT x = 0; x < width; x ++, ps ++, pd ++)
			{
				pd->Blue = clTables[0][ps->Blue];
				pd->Green = clTables[1][ps->Green];
				pd->Red = clTables[2][ps->Red];
				pd->Alpha = ps->Alpha;
			}
		}
	}
	else if (dest != source)
	{
		for (UINT y = 0; y < height; y ++, ps += srcOffset, pd += dstOffset)
		{
			for (UINT x = 0; x < width; x ++, ps ++, pd ++)
			{
				pd->Color = ps->Color;
			}
		}
    }
}

    下面给一个简单的图像色阶调整函数调用例子:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
	BitmapData dest, source;

	Bitmap *sBmp = new Bitmap(L"..\\..\\media\\source1.jpg");
	LockBitmap(sBmp, &source);

	Bitmap *dBmp = new Bitmap(source.Width, source.Height, PixelFormat32bppARGB);
	LockBitmap(dBmp, &dest);

	ColorLevelData clData;
	InitColorLevelData(&clData);

	clData.RGB.Shadow = 10;
	clData.RGB.Midtones = 1.2;
	clData.RGB.Highlight = 240;
	clData.RGB.OutShadow = 50;
	clData.RGB.OutHighlight = 200;

/*
	clData.RGB.OutShadow = 255;
	clData.RGB.OutHighlight = 0;
*/
	ImageColorLevel(&dest, &source, &clData);

	UnlockBitmap(dBmp, &dest);
	UnlockBitmap(sBmp, &source);

	Gdiplus::Graphics g(Canvas->Handle);
	g.DrawImage(sBmp, 0, 0);
	g.DrawImage(dBmp, source.Width, 0);

	delete dBmp;
	delete sBmp;
}

    下面是文章Delphi图像处理 -- 图像色阶调整例子运行界面效果图,第一张效果图绿色通道色阶调整,第二张效果图是RGB输出色阶调整到完全颠倒时的负片图,详细的图像色阶调整界面例子请参考Delphi图像处理 -- 图像色阶调整》。
     


    本文代码系用BCB XE7编辑和编译。


    因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com

    这里可访问《C++图像处理 -- 文章索引



2010-06-02 22:57:00 maozefa 阅读数 6237

阅读提示:

    《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。

    《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。

    尽可能保持二者内容一致,可相互对照。

   本文代码必须包括文章《Delphi图像处理 -- 数据类型及公用过程》中的ImageData.pas单元


    在Photoshop中,图像色阶调整应用很广泛,本文介绍的图像色阶调整过程与Photoshop处理效果基本一致。

    Photoshop的色阶调整分输入色阶调整和输出色阶调整,其中输入色阶调整有3个调整点,即通常所说的黑场、白场及灰场调整。

    输入色阶调整的基本算法并不复杂,首先计算出白场与黑场的离差Diff,然后计算出像素各份量值与黑场的离差rgbDiff,如果rgbDiff<=0,像素各份量值等于0,否则,计算以rgbDiff与Diff的比值为底的灰场倒数的幂。用公式表示:

    Diff = Highlight -Shadow

    rgbDiff = RGB - Shadow

    clRGB = Power(rgbDiff / Diff,  1 / Midtones)

    其中Shadow为输入色阶低端数据(黑场),Highlight为输入色阶高端数据(白场), Midtones为输入色阶中间数据(灰场),Diff为二者的离差(必须大于1),RGB为调整前的像素分量值,clRGB为调整输入色阶后的像素分量值。

    输出色阶调整更简单,首先计算输出色阶白场与黑场的离差与255的比值系数,然后用输入色阶调整后的像素分量值乘上这个系数,再加上输出黑场值即可。用公式表示:

    outClRGB = clRGB * (outHighlight - outShadow) / 255 + outShadow

    其中,outShadow为输出黑场,outHighlight为输出白场,outClRGB为全部色阶调整后的像素分量值。

    前面已经提到输入色阶黑白场的离差必须大于1,而输入色阶并没有这个限制,输出黑白场的离差可以为负数,当输出黑场与白场完全颠倒时,输出色阶调整后的图片为原图片的负片。

    色阶调整涉及四个通道,即R、G、B各分量通道及整体颜色通道,调整如果每个通道单独调整,将是比较麻烦和耗时的,本文采用色阶表替换法,可一次性完成所有四个通道的色阶调整。

    下面直接给出一个完整的图像色阶调整例子源代码,其中包含了色阶调整和灰度计算函数:

unit main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, ComCtrls, ImageData, Gdiplus;

type
  // 色阶项结构
  PColorLevelItem = ^TColorLevelItem;
  TColorLevelItem = packed record
    Shadow: LongWord;
    Midtones: Single;
    Highlight: LongWord;
    OutShadow: LongWord;
    OutHighlight: LongWord;
  end;

  // 色阶通道结构
  PColorLevelData = ^TColorLevelData;
  TColorLevelData = record
    Blue: TColorLevelItem;
    Green: TColorLevelItem;
    Red: TColorLevelItem;
    RGB: TColorLevelItem;
  end;

  // 256色灰度统计数组,每个元素表示该下标对应的颜色个数
  PGrayArray = ^TGrayArray;
  TGrayArray = array[0..255] of LongWord;
  // 灰度信息结构
  PImageGrayInfo = ^TImageGrayInfo;
  TImageGrayInfo = packed record
    Grays: TGrayArray;          // 灰度数组
    Total: int64;               // 总的灰度值
    Count: LongWord;            // 总的像素点数
    MaxValue: LongWord;         // 像素点最多的灰度值
    MinValue: LongWord;         // 像素点最少的灰度值
    Average: LongWord;          // 平均灰度值(Total / Count)
  end;

  TMainForm = class(TForm)
    LBar: TTrackBar;
    HBar: TTrackBar;
    Button1: TButton;
    Label3: TLabel;
    GrayMap: TPaintBox;
    ComboBox1: TComboBox;
    Label1: TLabel;
    LLabel: TLabel;
    HLabel: TLabel;
    MBar: TTrackBar;
    MLabel: TLabel;
    PaintBox1: TPaintBox;
    OLBar: TTrackBar;
    OHBar: TTrackBar;
    OLLabel: TLabel;
    OHLabel: TLabel;
    Label5: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure GrayMapPaint(Sender: TObject);
    procedure LBarChange(Sender: TObject);
    procedure HBarChange(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure ComboBox1Change(Sender: TObject);
    procedure MBarChange(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);
    procedure OLBarChange(Sender: TObject);
    procedure OHBarChange(Sender: TObject);
  private
    { Private declarations }
    FBitmap: TGpBitmap;
    FSource: TImageData;
    FDest: TImageData;
    FLevelData: TColorLevelData;
    FLevelItems: array[0..3] of PColorLevelItem;
    FGrayInfos: array[0..3] of TImageGrayInfo;
    FLock: Boolean;
    FIsRun: Boolean;
    FAbort: Boolean;
  public
    { Public declarations }
    procedure AdjustmentImage;
    procedure GetGrayInfos;
  end;

var
  MainForm: TMainForm;

implementation

uses Math;

{$R *.dfm}

const
  GRAY_MMX: TMMType = (3735, 19235, 9798, 0); // [0.114,0.587,0.229] * 32768

// 获取图像灰度信息
function GetGrayInfo(const Data: TImageData; var GrayInfo: TImageGrayInfo;
  isGrayImage: Boolean = False): Integer;
asm
    push      ebp
    push      esi
    push      edi
    push      ebx
    push      ecx
    push      eax
    mov       edi, edx
    mov       esi, edx
    xor       eax, eax
    mov       ecx, 256
    rep       stosd               // init Grays
    pop       eax
    call      _SetDataRegs
    mov       eax, ecx
    imul      eax, edx
    xchg      eax, [esp]          // pixel count
    test      al, al
    jnz       @@yGrayLoop

    // 建立灰度数组
    pxor      mm7, mm7
    movq      mm2, GRAY_MMX
@@yLoop:
    push      ecx
@@xLoop:
    movd      mm0, [edi]
    punpcklbw mm0, mm7
    pmaddwd   mm0, mm2
    movq      mm1, mm0
    psrlq     mm1, 32
    paddd     mm0, mm1
    movd      eax, mm0
    add       eax, 16384
    shr       eax, 15
    inc       [esi].TImageGrayInfo.Grays[eax*4].Integer
    add       edi, 4              // grayData.Grays[gray] ++
    loop      @@xLoop
    pop       ecx
    add       edi, ebx
    dec       edx
    jnz       @@yLoop
    emms
    jmp       @@SumStart
    // 建立灰度图的灰度数组
@@yGrayLoop:
    push      ecx
@@xGrayLoop:
    movzx     eax, [edi].TARGBQuad.Blue       // gray = *edi
    inc       [esi].TImageGrayInfo.Grays[eax*4].Integer// grayData.Grays[gray] ++
    add       edi, 4
    loop      @@xGrayLoop
    pop       ecx
    add       edi, ebx
    dec       edx
    jnz       @@yGrayLoop
    // 计算总的灰度值、最大灰度值及最小灰度值
@@SumStart:
    push      esi
    mov       edi, esi            // esi = ebx = &GrayData.Grays[0]
    mov       ebx, esi
    xor       eax, eax            // edx:eax = 0 (GrayData.Total)
    xor       edx, edx
    xor       ecx, ecx            // for (index = 0; index < 256; index ++)
@@SumLoop:                      // {
    mov       ebp, [edi]
    cmp       [esi], ebp
    cmovb     esi, edi            //   if (*esi < *edi) esi = edi
    cmp       [ebx], ebp
    cmova     ebx, edi            //   if (*ebx > *edi) ebx = edi
    imul      ebp, ecx            //   ebp = *edi * index
    add       eax, ebp            //   edx:eax += ebp
    adc       edx, 0
    add       edi, 4              //   edi += 4
    inc       ecx
    cmp       ecx, 255
    jle       @@SumLoop           // }
    pop       edi
    sub       ebx, edi
    shr       ebx, 2              // min = (ebx - &GrayData.Grays[0]) / 4
    mov       [edi].TImageGrayInfo.MinValue, ebx
    sub       esi, edi
    shr       esi, 2              // max = (esi - &GrayData.Grays[0]) / 4
    mov       [edi].TImageGrayInfo.MaxValue, esi
    pop       ebx                 // count = data.Width * data.Height
    mov       [edi].TImageGrayInfo.Count, ebx
    mov       dword ptr[edi].TImageGrayInfo.Total, eax // total = edx:eax
    mov       dword ptr[edi].TImageGrayInfo.Total+4, edx
    mov       ecx, ebx
    shr       ecx, 1
    add       eax, ecx
    adc       edx, 0
    div       ebx                 // average = (total + count / 2) / count
    mov       [edi].TImageGrayInfo.Average, eax
@@Exit:                           // return GrayData.Average
    pop       ebx
    pop       edi
    pop       esi
    pop       ebp
end;

// 用色阶表替换Source像素值到Dest
procedure _DoColorLevel(var Dest: TImageData; const Source: TImageData;
  const Table: PGrayTable);
var
  height, dstOffset, srcOffset: Integer;
asm
    push    ecx
    call    _SetCopyRegs
    mov     height, edx
    mov     dstOffset, ebx
    mov     srcOffset, eax
    pop     ebx
@@yLoop:
    push    ecx
@@xLoop:
    movzx   eax, [esi].TARGBQuad.Blue
    movzx   edx, [esi].TARGBQuad.Green
    mov     al, [ebx+eax]
    mov     dl, [ebx+edx+256]
    mov     [edi].TARGBQuad.Blue, al
    mov     [edi].TARGBQuad.Green, dl
    movzx   eax, [esi].TARGBQuad.Red
    mov     al, [ebx+eax+512]
    mov     ah, [esi].TARGBQuad.Alpha
    mov     [edi].TARGBQuad.Red.Word, ax
    add     esi, 4
    add     edi, 4
    loop    @@xLoop
    pop     ecx
    add     esi, srcOffset
    add     edi, dstOffset
    dec     height
    jnz     @@yLoop
end;

// 拷贝Source到Dest
procedure _DoCopyImageData(var Dest: TImageData; const Source: TImageData);
asm
    call    _SetCopyRegs
@@yLoop:
    push    ecx
    rep     movsd
    pop     ecx
    add     esi, eax
    add     edi, ebx
    dec     edx
    jnz     @@yLoop
end;

// 如果色阶项Item参数非初始值,计算色阶表Table并返回真
function GetColorLevelTable(Item: TColorLevelItem; var Table: TGrayTable): Boolean;
var
  i, v: Integer;
  outDiff, diff: Integer;
  outCoef, coef: double;
  exponent: double;
  isMidtones: Boolean;
begin
  outDiff := Integer(Item.OutHighlight - Item.OutShadow);
  diff := Integer(Item.Highlight - Item.Shadow);
  isMidtones := (Item.Midtones <> 1.0) and not ((Item.Midtones > 9.99) or (Item.Midtones < 0.1));

  Result := ((Item.Highlight <= 255) and (diff < 255) and (diff >= 2)) or
            ((Item.OutHighlight <= 255) and (Item.OutShadow <= 255) and (outDiff < 255)) or
            isMidtones;
  if not Result then Exit;

  Coef := 255 / diff;
  outCoef := outDiff / 255;
  exponent := 1 / Item.Midtones;
  for i := 0 to 255 do
  begin
    // 计算输入色阶黑白场
    if Table[i] <= Item.Shadow then v := 0
    else
    begin
      v := Round((Table[i] - Item.Shadow) * coef);
      if v > 255 then v := 255;
    end;
    // 计算输入色阶灰场
    v := Round(Power(v / 255, exponent) * 255);
    // 计算输出色阶
    Table[i] := Round(v * outCoef + Item.OutShadow);
  end;
end;

// 如果色阶通道数据clData参数非初始值,计算所有通道色阶表并返回真
function _CheckColorLevelData(const clData: TColorLevelData;
  var Tables: array of TGrayTable): Boolean;
type
  PColorLevels = ^TColorLevels;
  TColorLevels = array[0..2] of TColorLevelItem;
var
  i, j: Integer;
begin
  Result := False;
  for i := 0 to 2 do      // 初始化R、G、B通道色阶表
  begin
    for j := 0 to 255 do
      Tables[i, j] := j;
  end;
  for i := 0 to 2 do      // 计算R、G、B通道色阶表
  begin
    if GetColorLevelTable(PColorLevels(@clData)^[i], Tables[i]) then
      Result := True;
  end;
  for i := 0 to 2 do      // 计算整个RGB图像色阶表
  begin
    if not GetColorLevelTable(clData.RGB, Tables[i]) then
      Break;
    Result := True;
  end;
end;

// 初始化色阶通道数据
procedure InitColorLevelData(var clData: TColorLevelData);

  procedure InitTColorLevelItem(var Item: TColorLevelItem);
  begin
    Item.Shadow := 0;
    Item.Midtones := 1.0;
    Item.Highlight := 255;
    Item.OutShadow := 0;
    Item.OutHighlight := 255;
  end;

begin
  InitTColorLevelItem(clData.Blue);
  InitTColorLevelItem(clData.Green);
  InitTColorLevelItem(clData.Red);
  InitTColorLevelItem(clData.RGB);
end;

// 按clData拷贝Source的色阶调整数据到Dest
procedure ImageColorLevel(var Dest: TImageData; const Source: TImageData;
  const clData: TColorLevelData);
var
  Tables: array[0..2] of TGrayTable;
begin
  if _CheckColorLevelData(clData, Tables) then
    _DoColorLevel(Dest, Source, @Tables)
  else
    _DoCopyImageData(Dest, Source);
end;

procedure TMainForm.AdjustmentImage;
begin
  if not FIsRun then
  begin
    FIsRun := True;
    FAbort := False;
    ImageColorLevel(FDest, FSource, FLevelData);
    PaintBox1Paint(nil);
    FIsRun := False;
  end
  else
    FAbort := True;
end;

procedure TMainForm.Button1Click(Sender: TObject);
begin
  Close;
end;

procedure TMainForm.ComboBox1Change(Sender: TObject);
var
  x: Integer;
begin
  FLock := True;
  x := ComboBox1.ItemIndex;
  LBar.Position := FLevelItems[x].Shadow;
  MBar.Position := 46 - Round(Ln(FLevelItems[x].Midtones * 10) * 10);
  HBar.Position := FLevelItems[x].Highlight;
  FLock := False;
  GrayMap.Invalidate;
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FBitmap := TGpBitmap.Create('..\..\media\source1.jpg');
  FSource := LockGpBitmap(FBitmap);
  FDest := NewImageData(FSource.Width, FSource.Height);
  InitColorLevelData(FLevelData);
  FLevelItems[0] := @FLevelData.RGB;
  FLevelItems[1] := @FLevelData.Red;
  FLevelItems[2] := @FLevelData.Green;
  FLevelItems[3] := @FLevelData.Blue;
  GetGrayInfos;
  ComboBox1.ItemIndex := 0;
  AdjustmentImage;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FreeImageData(FDest);
  UnlockGpBitmap(FBitmap, FSource);
  FBitmap.Free;
end;

procedure TMainForm.GetGrayInfos;
var
  i, j: Integer;
  maxIdx, minIdx: Integer;
begin
  for i := 1 to 3 do
  begin
    GetGrayInfo(FSource, FGrayInfos[i], True);
    for j := 0 to 255 do
      Inc(FGrayInfos[0].Grays[j], FGrayInfos[i].Grays[j]);
    Inc(Integer(FSource.Scan0), 1);
  end;
  Dec(Integer(FSource.Scan0), 3);
  maxIdx := 0;
  minIdx := 0;
  for i := 0 to 255 do
  begin
    FGrayInfos[0].Grays[i] := Round(FGrayInfos[0].Grays[i] / 3);
    Inc(FGrayInfos[0].Total, FGrayInfos[0].Grays[i] * i);
    if (FGrayInfos[0].Grays[minIdx] > FGrayInfos[0].Grays[i]) then
      minIdx := i;
    if (FGrayInfos[0].Grays[maxIdx] < FGrayInfos[0].Grays[i]) then
      maxIdx := i;
  end;
  FGrayInfos[0].MaxValue := maxIdx;
  FGrayInfos[0].MinValue := minIdx;
  FGrayInfos[0].Count := FGrayInfos[1].Count;
  FGrayInfos[0].Average := FGrayInfos[0].Total div FGrayInfos[0].Count;
end;

procedure TMainForm.GrayMapPaint(Sender: TObject);
const
  PenColor: array[0..3] of TColor = ($000000, $0000FF, $008000, $FF0000);
var
  I, v, x: Integer;
begin
  x := ComboBox1.ItemIndex;
  GrayMap.Canvas.Brush.Color := clSkyBlue;
  GrayMap.Canvas.FillRect(GrayMap.ClientRect);
  GrayMap.Canvas.Pen.Color := PenColor[x];
  for I := 0 to 255 do
  begin
    v := Round(FGrayInfos[x].Grays[i] / FGrayInfos[x].Grays[FGrayInfos[x].MaxValue] * GrayMap.Height);
    GrayMap.Canvas.MoveTo(I, GrayMap.Height);
    GrayMap.Canvas.LineTo(I, GrayMap.Height - v);
  end;
end;

procedure TMainForm.HBarChange(Sender: TObject);
begin
  HLabel.Caption := IntToStr(HBar.Position);
  if FLock then Exit;
  if HBar.Position - LBar.Position < 2 then
  begin
    FLock := True;
    HBar.Position := LBar.Position + 2;
    FLock := False;
  end;
  FLevelItems[ComboBox1.ItemIndex].Highlight := HBar.Position;
  AdjustmentImage;
end;

procedure TMainForm.LBarChange(Sender: TObject);
begin
  LLabel.Caption := IntToStr(LBar.Position);
  if FLock then Exit;
  if HBar.Position - LBar.Position < 2 then
  begin
    FLock := True;
    LBar.Position := HBar.Position - 2;
    FLock := False;
  end;
  FLevelItems[ComboBox1.ItemIndex].Shadow := LBar.Position;
  AdjustmentImage;
end;

procedure TMainForm.MBarChange(Sender: TObject);
var
  v: Single;
begin
  v := Power(Exp(1), (MBar.Max - MBar.Position) / 10.0) / 10;
  MLabel.Caption := Format('%.1f', [v]);
  if FLock then Exit;
  FLevelItems[ComboBox1.ItemIndex].Midtones := StrToFloat(MLabel.Caption);
  AdjustmentImage;
end;

procedure TMainForm.OHBarChange(Sender: TObject);
begin
  OHLabel.Caption := IntToStr(OHBar.Position);
  if FLock then Exit;
  FLevelItems[ComboBox1.ItemIndex].OutHighlight := OHBar.Position;
  AdjustmentImage;
end;

procedure TMainForm.OLBarChange(Sender: TObject);
begin
  OLLabel.Caption := IntToStr(OLBar.Position);
  if FLock then Exit;
  FLevelItems[ComboBox1.ItemIndex].OutShadow := OLBar.Position;
  AdjustmentImage;
end;

procedure TMainForm.PaintBox1Paint(Sender: TObject);
var
  dstBmp, srcBmp: TGpBitmap;
  g: TGpGraphics;
begin
  g := TGpGraphics.Create(PaintBox1.Canvas.Handle);
  dstBmp := TGpBitmap.Create(FDest.Width, FDest.Height, FDest.Stride,
    pf32bppArgb, FDest.Scan0);
  srcBmp := TGpBitmap.Create(FSource.Width, FSource.Height, FSource.Stride,
    pf32bppArgb, FSource.Scan0);
  try
    g.DrawImage(dstBmp, 0, 0);
    g.DrawImage(srcBmp, 0, FDest.Height);
  finally
    srcBmp.Free;
    dstBmp.Free;
    g.Free;
  end;
end;

end.


    下面是2张运行效果图,第一张效果图绿色通道色阶调整,第二张效果图是RGB输出色阶调整到完全颠倒时的负片图:

 

    PhotoShop中的色阶调整只用了2个滑条分别进行输入、输出色阶调整,我没有这种滑槽组件,只好用了5个滑条组件,不太美观。当然在实用时完全可写一个与PhotoShop类似的元件,并不复杂。

    说明:本文章里的灰度计算和色阶调整源代码可以在http://download.csdn.net/detail/maozefa/8323289下载,但其中的输出色阶调整和输入色阶调整一样,是不允许黑白场颠倒的,而且黑白场离差最小允许值是4而不是2,可以按本文代码修正过来。


    《Delphi图像处理》系列使用GDI+单元下载地址和说明见文章《GDI+ for VCL基础 -- GDI+ 与 VCL》。

    因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com

    这里可访问《Delphi图像处理 -- 文章索引》。

 



2017-11-07 17:10:07 aitazhixin 阅读数 3674

通过仿真自动色阶算法,发现其去雾效果十分明显,并且速度快于暗通道算法。

python实现:

#!python3.6

import numpy as np
import cv2

def ComputeHist(img):
    h,w = img.shape
    hist, bin_edge = np.histogram(img.reshape(1,w*h), bins=list(range(257)))
    return hist
    
def ComputeMinLevel(hist, rate, pnum):
    sum = 0
    for i in range(256):
        sum += hist[i]
        if (sum >= (pnum * rate * 0.01)):
            return i
            
def ComputeMaxLevel(hist, rate, pnum):
    sum = 0
    for i in range(256):
        sum += hist[255-i]
        if (sum >= (pnum * rate * 0.01)):
            return 255-i
            
def LinearMap(minlevel, maxlevel):
    if (minlevel >= maxlevel):
        return []
    else:
        newmap = np.zeros(256)
        for i in range(256):
            if (i < minlevel):
                newmap[i] = 0
            elif (i > maxlevel):
                newmap[i] = 255
            else:
                newmap[i] = (i-minlevel)/(maxlevel-minlevel) * 255
        return newmap
        
def CreateNewImg(img):
    h,w,d = img.shape
    newimg = np.zeros([h,w,d])
    for i in range(d):
        imgmin = np.min(img[:,:,i])
        imgmax = np.max(img[:,:,i])
        imghist = ComputeHist(img[:,:,i])
        minlevel = ComputeMinLevel(imghist, 8.3, h*w)
        maxlevel = ComputeMaxLevel(imghist, 2.2, h*w)
        newmap = LinearMap(minlevel,maxlevel)
        # print(minlevel, maxlevel)
        if (newmap.size ==0 ):
            continue
        for j in range(h):
            newimg[j,:,i] = newmap[img[j,:, i]]
    return newimg
    
    
    
if __name__ == '__main__':
    img = cv2.imread('2017/2017_0_0_1000_.jpg',1)
    newimg = CreateNewImg(img)
    cv2.namedWindow('img',0)
    cv2.imshow('img', img)
    cv2.namedWindow('newimg',0)
    cv2.imshow('newimg', newimg/255)
    cv2.waitKey(0)
        




原图去雾效果

2015-05-05 14:21:21 lxw907304340 阅读数 808

VC编程实现对位图图像自动色阶处理

VC编程实现位图拷贝、切除空白边介绍了VC实现位图图像拷贝,切除二值图空白边,本文继续介绍位图处理类CImageUtility的其它成员方法,着重介绍VC编程实现位图图像自动色阶的功能。

根据互联网搜索的结果,位图图像自动色阶算法主要包含两种方案,一种是拉开LAB色彩空间的L(亮度)分量,使图像的亮度区域拉开,第二种是讲图像的RGB各分量值的区域拉开。第一种方案效果较好,但是计算比较复杂,需要用到前面介绍的色彩空间转换,第二种方案计算简单,但是效果不如前一种方案,下面列出具体的VC实现源码和处理效果,读者可以根据实际需要进行选用。

1. VC源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// 自动色阶
// gType 自动色阶的算法
// darkLimen 暗调阈值
// brightLimen 高光阈值
void CImageUtility::ImageAutoGradationProcess(AutoGradationType gType,double darkLimen,double brightLimen){
    // 目前只处理24位以上的位图
    if(nPixBytes <3)
        return;
    //临时保存像素点的颜色
    int nRGB[3]={0,0,0};
    switch(gType){
        case g_L_VALUE:{    //通过LAB中的L分量自动色阶
            //保存每个亮度分量的像素个数
            unsigned long gradation[101];
            for(size_t i=0;i<101;i++)
                gradation[i]=0;
            //保存亮度最多的像素个数
            unsigned long maxPixle=0;
            // 暗调阈值序号
            int darkPos=0;
            // 高光阈值序号
            int lightPos=0;        
            double LAB[3]={0.0,0.0,0.0};
            for(long nHeight=0; nHeight<bmSrcInfo.bmHeight; nHeight++)  
            {      
                for(long nWidth=0; nWidth<bmSrcInfo.bmWidth; nWidth++)  
                {
                    //获取对应像素点得颜色
                    getPixelColor(nRGB,nHeight,nWidth);    
                    //转换颜色到LAB色彩空间
                    CColorUtility::_cie_rgb2lab(nRGB,LAB);
                    //对应亮度的像素点个数加1
                    gradation[(int)(LAB[0]+0.5)]++;            //LAB中的L分量【0.0-100.0】
                    if(maxPixle<gradation[(int)(LAB[0]+0.5)])
                        maxPixle=gradation[(int)(LAB[0]+0.5)];
                }             
            }
            //分析直方图
            // 暗调位置
            for(size_t i=0;i<101;i++){
                if(((long double)(gradation[i]))/maxPixle>darkLimen){
                    darkPos=i;
                    break;
                }
            }
            // 高光位置
            for(size_t i=100;i>=0;i--){
                if(((long double)(gradation[i]))/maxPixle>brightLimen){
                    lightPos=i;
                    break;
                }
            }
            //对像素进行处理
            double offsetLight=(lightPos+darkPos)/2-50.0;        //中间亮度偏移
            double quotiety=100.0/(lightPos-darkPos);            //亮度缩放系数
            for(long nHeight=0; nHeight<bmSrcInfo.bmHeight; nHeight++)  
            {      
                for(long nWidth=0; nWidth<bmSrcInfo.bmWidth; nWidth++)  
                {
                    //获取对应像素点得颜色
                    getPixelColor(nRGB,nHeight,nWidth);    
                    //转换颜色到LAB色彩空间
                    CColorUtility::_cie_rgb2lab(nRGB,LAB);
                    //移动当前亮度的中心到自动色阶后亮度的中心
                    LAB[0]=LAB[0]-offsetLight;    
                    //计算当前亮度与中心的距离
                    LAB[0]-=50;
                    //将距离乘于系数加上中心位置及时当前亮度最终的位置
                    LAB[0]=quotiety*LAB[0]+50;    
                    //重新给像素点赋值
                    CColorUtility::_cie_lab2rgb(LAB,nRGB);
                    //设置像素点颜色
                    setPixelColor(nRGB,nHeight,nWidth);    
                }             
            }    
            break;                       
        }
        // 还有问题,有待研究
        case g_RGB:{        //通过RGB自动色阶
            //保存每个亮度分量的像素个数
            unsigned long gradation[3][256];
            for(size_t i=0;i<3;i++)
                for(size_t j=0;j<256;j++)
                    gradation[i][j]=0;
            //保存亮度最多的像素个数
            unsigned long maxPixle[3]={0,0,0};
            // 暗调阈值序号
            int darkPos[3]={0,0,0};    
            // 高光阈值序号
            int lightPos[3]={0,0,0};            
            for(long nHeight=0; nHeight<bmSrcInfo.bmHeight; nHeight++)  
            {      
                for(long nWidth=0; nWidth<bmSrcInfo.bmWidth; nWidth++)  
                {
                    //获取对应像素点得颜色
                    getPixelColor(nRGB,nHeight,nWidth);
                    for(int i=0;i<3;i++){
                        //保存每种颜色分量的像素个数
                        gradation[i][nRGB[i]]++;
                        //保存每种颜色分量最大的像素个数
                        if(maxPixle[i]<gradation[i][nRGB[i]])
                            maxPixle[i]=gradation[i][nRGB[i]];
                    }
                }             
            }
            //分析直方图
            // 暗调位置
            for(size_t i=0;i<3;i++){
                for(size_t j=0;j<256;j++){
                    if(((long double)(gradation[i][j]))/maxPixle[i]>darkLimen){
                        darkPos[i]=j;
                        break;
                    }
                }
            }
            // 高光位置
            for(size_t i=0;i<3;i++){
                for(size_t j=255;j>=0;j--){
                    if(((long double)(gradation[i][j]))/maxPixle[i]>brightLimen){
                        lightPos[i]=j;
                        break;
                    }
                }
            }
            //对像素进行处理
            int offsetLight[3]={0,0,0};
            for(int i=0;i<3;i++)
                offsetLight[i]=(lightPos[i]+darkPos[i])/2-128;        //中间亮度偏移
            double quotiety[3]={0.0,0.0,0.0};
            for(int i=0;i<3;i++)
                quotiety[i]=255.0/(lightPos[i]-darkPos[i]);            //亮度缩放系数
            for(long nHeight=0; nHeight<bmSrcInfo.bmHeight; nHeight++)  
            {      
                for(long nWidth=0; nWidth<bmSrcInfo.bmWidth; nWidth++)  
                {
                    //获取对应像素点得颜色
                    getPixelColor(nRGB,nHeight,nWidth);    
                    for(int i=0;i<3;i++){
                        //移动当前亮度的中心到自动色阶后亮度的中心
                        nRGB[i]=nRGB[i]-offsetLight[i];    
                        //计算当前亮度与中心的距离
                        nRGB[i]-=128;
                        //将距离乘于系数加上中心位置及时当前亮度最终的位置
                        nRGB[i]=(int)(quotiety[i]*nRGB[i])+128;    
                    }
                    //设置像素点颜色
                    setPixelColor(nRGB,nHeight,nWidth);    
                }             
            }
            break;
                   }
    }    //switch over
    bmpSrc->SetBitmapBits(dwBmByteSize, pBmBits);  
}

2. 效果对比

2.1 LAB亮度分量实现自动色阶:

VC实现位图自动色阶算法

2.2 RGB实现自动色阶:

VC实现位图自动色阶功能

对于RGB色彩空间与LAB色彩空间的转换,读者可以参考:VC编程实现色彩空间RGB与XYZ相互转换VC编程实现色彩空间XYZ与LAB相互转换

没有更多推荐了,返回首页