C# Bitmap图像处理(含增强对比度的三种方法)
作者:莫干 发布时间:2023-11-01 02:33:53
目录
Bitmap类
BitmapData类
参考:
Bitmap类
Bitmap对象封装了GDI+中的一个位图,此位图由图形图像及其属性的像素数据组成.因此Bitmap是用于处理由像素数据定义的图像的对象.该类的主要方法和属性如下:
1. GetPixel方法和SetPixel方法:获取和设置一个图像的指定像素的颜色.
2. PixelFormat属性:返回图像的像素格式.
3. Palette属性:获取和设置图像所使用的颜色调色板.
4. Height Width属性:返回图像的高度和宽度.
5. LockBits方法和UnlockBits方法:分别锁定和解锁系统内存中的位图像素.在基于像素点的图像处理方法中使用LockBits和UnlockBits是一个很好的方式,这两种方法可以使我们指定像素的范围来控制位图的任意一部分,从而消除了通过循环对位图的像素逐个进行处理,每调用LockBits之后都应该调用一次UnlockBits.
BitmapData类
BitmapData对象指定了位图的属性
1. Height属性:被锁定位图的高度.
2. Width属性:被锁定位图的宽度.
3. PixelFormat属性:数据的实际像素格式.
4. Scan0属性:被锁定数组的首字节地址,如果整个图像被锁定,则是图像的第一个字节地址.
5. Stride属性:步幅,也称为扫描宽度.
这里要重点说说Stride属性,这个和Width有什么区别呢,可以这么说,如果你的图片大小也就是图片字节是4的整数倍,那么Stride与Width是相等的,否则Stride就是大于Width的最小4的整数倍。在处理过程中,Stride肯定是4的整数倍,这里是个坑啊。。。
例1:有一个一维像素点阵数组,里面放的是每个像素点的灰度值,知道宽和高,要转换成bitmap
/// <summary>
/// 像素点阵转换为bitmap
/// </summary>
/// <param name="rawValues">byte[]数组</param>
/// <param name="width">图片的宽度</param>
/// <param name="height">图片的高度</param>
/// <returns>bitmap图片</returns>
public static Bitmap ToGrayBitmap(byte[] rawValues, int width, int height)
{
Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
BitmapData bmpData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
获取图像参数
//bmpData.Stride = width;
int stride = bmpData.Stride; // 扫描线的宽度
int offset = stride - width; // 显示宽度与扫描线宽度的间隙
IntPtr iptr = bmpData.Scan0; // 获取bmpData的内存起始位置
int scanBytes = stride * height;// 用stride宽度,表示这是内存区域的大小
下面把原始的显示大小字节数组转换为内存中实际存放的字节数组
int posScan = 0, posReal = 0;// 分别设置两个位置指针,指向源数组和目标数组
byte[] pixelValues = new byte[scanBytes]; //为目标数组分配内存
for (int x = 0; x < height; x++)
{
下面的循环节是模拟行扫描
for (int y = 0; y < width; y++)
{
pixelValues[posScan++] = rawValues[posReal++];
}
posScan += offset; //行扫描结束,要将目标位置指针移过那段“间隙”
}
用Marshal的Copy方法,将刚才得到的内存字节数组复制到BitmapData中
System.Runtime.InteropServices.Marshal.Copy(pixelValues, 0, iptr, scanBytes);
bmp.UnlockBits(bmpData); // 解锁内存区域
下面的代码是为了修改生成位图的索引表,从伪彩修改为灰度
ColorPalette tempPalette;
using (Bitmap tempBmp = new Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format8bppIndexed))
{
tempPalette = tempBmp.Palette;
}
for (int i = 0; i < 256; i++)
{
tempPalette.Entries[i] = System.Drawing.Color.FromArgb(i, i, i);
}
bmp.Palette = tempPalette;
算法到此结束,返回结果
return bmp;
}
至于24位位图数据其实就是 一个像素点有rgb三个值而已,道理一样。
例2::根据图片得到他的灰度数组
//8位位图得到除去文件头信息的一位灰度数组
BitmapData bmpData = map.LockBits(new System.Drawing.Rectangle(0, 0, map.Width, map.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
获取图像参数
int stride = bmpData.Stride; // 扫描线的宽度
int offset = stride - map.Width; // 显示宽度与扫描线宽度的间隙
IntPtr iptr = bmpData.Scan0; // 获取bmpData的内存起始位置
int scanBytes = stride * map.Height;// 用stride宽度,表示这是内存区域的大小
下面把原始的显示大小字节数组转换为内存中实际存放的字节数组
mapdata = new byte[scanBytes]; //为目标数组分配内存
System.Runtime.InteropServices.Marshal.Copy(iptr, mapdata, 0, scanBytes); //copy内存中数据到数组中
这里对与bitmapdata的操作方式是ReadOnly
下面的三个例子分别基于像素(GetPixel和SetPixel)、基于内存、基于指针这三种方法增强图片对比度。均测试通过
运行时间:
1)基于像素:400-600ms
2)基于内存:17-18ms
3)基于指针:20-23ms
利用LUT,应该可以进一步减少运行时间
// 第一种方法:像素提取法。速度慢
public Bitmap MethodBaseOnPixel(Bitmap bitmap,int degree)
{
Color curColor;
int grayR, grayG, grayB;
double Deg = (100.0 + degree) / 100.0;
for (int i = 0; i < bitmap.Width; i++)
{
for (int j = 0; j < bitmap.Height; j++)
{
curColor = bitmap.GetPixel(i, j);
grayR =Convert.ToInt32((((curColor.R / 255.0 - 0.5) * Deg + 0.5)) * 255);
grayG = Convert.ToInt32((((curColor.G / 255.0 - 0.5) * Deg + 0.5)) * 255);
grayB = Convert.ToInt32((((curColor.B / 255.0 - 0.5) * Deg + 0.5)) * 255);
if (grayR < 0)
grayR = 0;
else if (grayR > 255)
grayR = 255;
if (grayB < 0)
grayB = 0;
else if (grayB > 255)
grayB = 255;
if (grayG < 0)
grayG = 0;
else if (grayG > 255)
grayG = 255;
bitmap.SetPixel(i, j, Color.FromArgb(grayR, grayG, grayB));
}
}
return bitmap;
}
// 第二种方法:基于内存
public unsafe Bitmap MethodBaseOnMemory(Bitmap bitmap, int degree)
{
if (bitmap == null)
{
return null;
}
double Deg = (100.0 + degree) / 100.0;
int width = bitmap.Width;
int height = bitmap.Height;
int length = height * 3 * width;
byte[] RGB = new byte[length];
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
System.IntPtr Scan0 = data.Scan0;
System.Runtime.InteropServices.Marshal.Copy(Scan0, RGB, 0, length);
double gray = 0;
for (int i = 0; i < RGB.Length; i += 3)
{
for (int j = 0; j < 3; j++)
{
gray = (((RGB[i + j] / 255.0 -0.5) * Deg+0.5)) * 255.0;
if (gray > 255)
gray = 255;
if (gray < 0)
gray = 0;
RGB[i + j] = (byte) gray;
}
}
System.Runtime.InteropServices.Marshal.Copy(RGB, 0, Scan0, length);// 此处Copy是之前Copy的逆操作
bitmap.UnlockBits(data);
return bitmap;
}
}
//第三种方法:基于指针
public unsafe Bitmap MethodBaseOnPtr(Bitmap b, int degree)
{
if (b == null)
{
return null;
}
try
{
double num = 0.0;
double num2 = (100.0 + degree) / 100.0;
num2 *= num2;
int width = b.Width;
int height = b.Height;
BitmapData bitmapdata = b.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
byte* numPtr = (byte*)bitmapdata.Scan0;
int offset = bitmapdata.Stride - (width * 3);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
for (int k = 0; k < 3; k++)
{
num = ((((((double)numPtr[k]) / 255.0) - 0.5) * num2) + 0.5) * 255.0;
if (num < 0.0)
{
num = 0.0;
}
if (num > 255.0)
{
num = 255.0;
}
numPtr[k] = (byte)num;
}
numPtr += 3;
}
numPtr += offset;
}
b.UnlockBits(bitmapdata);
return b;
}
catch
{
return b;
}
}
参考:
1. http://blog.csdn.net/jiangxinyu/article/details/6222302 (此博客的代码中有错误,精简代码基于内存处理的copy顺序有问题)
2. http://www.pin5i.com/showtopic-20228.html // C# 特效图片:雾化、浮雕等。
来源:https://blog.csdn.net/qq_20161893/article/details/78478125
猜你喜欢
- 微信公众号发送模版消息 背景:如下图,当用户发布需求的时候,公众号自定推送消息。例如:微信支付的时候,公众号会推送支付成功消息前提:发送模版
- 本文旨在用通俗的语言讲述枯燥的知识定时任务作为一种系统调度工具,在一些需要有定时作业的系统中应用广泛,如每逢某个时间点统计数据、在将来某个时
- 前提说明之前公司有一个项目是由androidstudio接入高德地图实现导航,定位等功能,然后还有一部分登陆页面和其他逻辑都放在Unity方
- 前言在RequestMappingHandlerAdapter对request进行了适配,并且调用了目标handler之后,其会返回一个Mo
- 背景最近让我做一个大数据的系统,分析了一下,麻烦的地方就是多数据源切换抽取数据。考虑到可以跨服务器跨数据库抽数,再整理数据,就配置了这个动态
- 引言在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在
- 本文实例为大家分享了C#实现飞行棋的具体代码,供大家参考,具体内容如下游戏规则如果玩家A踩到了玩家B,玩家B退6格踩到了1幸运轮盘,a交换位
- 数据访问之Druid数据源的使用说明:该数据源Druid,使用自定义方式实现,后面文章使用start启动器实现,学习思路为主。为什么要使用数
- 本文实例为大家分享了Java工具类DateUtils的具体代码,供大家参考,具体内容如下import java.text.ParseExce
- 一、前言java是一门跨硬件平台的面向对象高级编程语言,java程序运行在java虚拟机上(JVM),由JVM管理内存,这点是和C++最大区
- 前言同C语言一样,Java也有断言关键字assert,它们的用法也比较相似。注意:Java的断言是从1.4版本开始的,以前的版本不支持断言。
- 本文实例讲述了java实现的日期时间转换工具类。分享给大家供大家参考,具体如下:最基础的东西,总结一下,下次用的时候就方便一些了。废话不多说
- LinkedBlockingDeque介绍LinkedBlockingDeque是双向链表实现的双向并发阻塞队列。该阻塞队列同时支持FIFO
- 一、前导知识串行口是计算机的标准接口,现在的PC机(个人电脑)一般至少有两个串行口COM1和COM2。串行口应用广泛,在数据通信、计算机网络
- 需求:用户和账户一对一关系,查询账户时实现用户的延迟加载思路:根据id查询,需要延迟加载的一方1、用户实体类package com.yl.b
- 前言:这个语句的作用是,确保该语句执行之后,关闭每一个资源,也就是说它确保了每个资源都在生命周期结束之后被关闭,因此,比如读写文
- android跑马灯出现重复跳动、不滚动问题,本文给出解决方案,供大家参考。原因:页面有View被重新绘制了、焦点被抢占例如:1、TextV
- using System;using System.Data;using System.Data.OleDb;namespace ZFSof
- C++对string进行大小写转换操作方法方法一:使用C语言之前的方法,使用函数,进行转换#include <iostream>
- 前言大家都知道MySQL数据库很好用,但数据量到了千万以上了,想增加字段是非常痛苦的,这个在MongoDB里就不存在,字段想怎么加就怎么加,