.net c#通過Exif獲取圖片信息(參數)

分類:IT技術 時間:2016-10-09

簡介

      想要獲取圖片的信息,例如快門速度、ISO值等等,我們可以通過讀取Exif中存儲的信息。Exif(Exchangeable Image File)是存儲在JPEG格式照片頭部的一段信息,相機和手機拍攝的照片都會攜帶這些信息,但是需要註意,PS的照片的時候采用低質量保存會丟失這些信息。在PS中保存為10-12等級的時候不會丟失,在美圖秀秀中保存質量為100%不會丟失。軟件在處理的時候也會將自己的信息寫入Exif,所以也可以通過這種方式判斷是否為原圖,或者圖片是否經過處理。

     本文中我介紹兩種方式獲取Exif。一是C#自帶的Image.PropertyItems 屬性(了解),二是通過第三方控件metadata-extractor獲取(推薦)。

一、通過Image.PropertyItems 屬性獲取照片信息

Image.PropertyItems 屬性中有幾個重要屬性,Id:為int型,不同的Id表示不同的參數的;Value:表示參數的值,byte[]型;Len:為int型,表示Value的長度,以字節為單位;Type:short型,表示Value的取數方法。Type主要有以下幾個類型:

type=1 時 Value 為字節數組。

type=2 時 Value 為空終止 ASCII 字符串。如果將類型數據成員設置為 ASCII 類型,則應該將 Len 屬性設置為包括空終止的字符串長度。例如,字符串“Hello”的長度為 6

type=3 時 Value 為無符號的短(16 位)整型數組。

type=4 時 Value 為無符號的長(32 位)整型數組。

type=5 時 Value 數據成員為無符號的長整型對數組。每一對都表示一個分數;第一個整數是分子,第二個整數是分母。

type=6 時 Value 為可以包含任何數據類型的值的字節數組。

type=7 時 Value 為有符號的長(32 位)整型數組。

type=10 時 Value 為有符號的長整型對數組。每一對都表示一個分數;第一個整數是分子,第二個整數是分母。

參考文獻:http://blog.csdn.net/yang073402/article/details/5470127

在使用Image.PropertyItems屬性時需要引用:using system.Drawing

 下面是代碼:

#region 通過PropertyItems獲取照片參數

        /// <summary>
        /// 表示參數的結構
        /// </summary>
        public struct Exif 
        {
            /// <summary>
            /// 數據的ID
            /// </summary>
            public string Id;
            /// <summary>
            /// 數據類型
            /// </summary>
            public int Type;
            /// <summary>
            /// 數據中值的字節長度
            /// </summary>
            public int Length;

            /// <summary>
            /// 根據ID對應的中文名
            /// </summary>
            public string Name;

            /// <summary>
            /// 根據原字節解析的參數值
            /// </summary>
            public string Value;
        }

        /// <summary>將字節通過ASCII轉換為字符串
        /// </summary>
        /// <param name="bt">原字節</param>
        /// <returns></returns>
        private static string ToStrOfByte(this byte[] bt)
        {
            return Encoding.ASCII.GetString(bt);
        }

        /// <summary>將字節轉換為int
        /// </summary>
        /// <param name="bt">原字節</param>
        /// <returns></returns>
        private static int ToUnInt16(this byte[] bt)
        {
            return Convert.ToUInt16(bt[1] << 8 | bt[0]);
        }

        /// <summary>將原兩組字節轉換為uint
        /// </summary>
        /// <param name="bt">原字節</param>
        /// <param name="isFirst">是否轉第一個字節組</param>
        /// <returns></returns>
        private static uint ToUnInt32(this byte[] bt,bool isFirst=true)
        {
            return isFirst ? Convert.ToUInt32(bt[3] << 24 | bt[2] << 16 | bt[1] << 8 | bt[0]) : Convert.ToUInt32(bt[7] << 24 | bt[6] << 16 | bt[5] << 8 | bt[4]);
        }

        /// <summary>獲取曝光模式
        /// </summary>
        /// <param name="value">曝光模式值</param>
        /// <returns></returns>
        private static string ExposureMode(int value)
        {
            var rt = "Undefined";
            switch (value)
            {
                case 0:
                    rt = "自動"; break;
                case 1:
                    rt = "手動控制"; break;
                case 2:
                    rt = "程序控制"; break;
                case 3:
                    rt = "光圈優先"; break;
                case 4:
                    rt = "快門優先"; break;
                case 5:
                    rt = "夜景模式"; break;
                case 6:
                    rt = "運動模式"; break;
                case 7:
                    rt = "肖像模式"; break;
                case 8:
                    rt = "風景模式"; break;
                case 9:
                    rt = "其他模式"; break;
            }
            return rt;
        }

        /// <summary>獲取測光模式
        /// </summary>
        /// <param name="value">測光模式值</param>
        /// <returns></returns>
        private static string MeteringMode(int value)
        {
            var rt = "Unknown";
            switch (value)
            {
                case 0:
                    rt = "Unknown"; break;
                case 1:
                    rt = "平均測光"; break;
                case 2:
                    rt = "中央重點平均測光"; break;
                case 3:
                    rt = "點測光"; break;
                case 4:
                    rt = "多點測光"; break;
                case 5:
                    rt = "評價測光"; break;
                case 6:
                    rt = "局部測光"; break;
                case 255:
                    rt = "其他測光"; break;
            }
            return rt;
        }

        /// <summary>獲取閃光燈模式
        /// </summary>
        /// <param name="value">閃光燈值</param>
        /// <returns></returns>
        private static string flashMode(int value)
        {
            var rt = "Unkown";
            switch (value)
            {
                case 0:
                    rt = "未使用"; break;
                case 1:
                    rt = "使用閃光燈"; break;
            }
            return rt;
        }

        /// <summary>獲取白平衡模式
        /// </summary>
        /// <param name="value">白平衡值</param>
        /// <returns></returns>
        private static string WhiteBalance(int value)
        {
            var rt = "Unkown";
            switch (value)
            {
                case 0: rt = "自動";//Unkown
                    break;
                case 1: rt = "日光";
                    break;
                case 2: rt = "熒光燈";
                    break;
                case 3: rt = "白熾燈";
                    break;
                case 17: rt = "標準光源A";
                    break;
                case 18: rt = "標準光源B";
                    break;
                case 19: rt = "標準光源C";
                    break;
                case 255: rt = "其他";
                    break;
            }
            return rt;
        }

        /// <summary>通過Id獲取Exif中關鍵名稱和值
        /// </summary>
        /// <param name="pId">ID</param>
        /// <param name="pType">類型</param>
        /// <param name="pBytes">字節值</param>
        /// <returns></returns>
        private static Exif InfoOfExif(int pId,int pType,byte[] pBytes)
        {

            var rt=new Exif {
                Id ="0X"+pId.ToString("X"), 
                Length = pBytes.Length, 
                Type = pType
            };
            uint fm;
            uint fz;
            switch (pId)
            {
                case 0x010F:
                    rt.Name = "相機制造商";
                    rt.Value = pBytes.ToStrOfByte();
                    break;
                case 0x0110:
                    rt.Name = "相機型號";
                    rt.Value = pBytes.ToStrOfByte();
                    break;
                case 0xA433:
                    rt.Name = "鏡頭制造商";
                    rt.Value = pBytes.ToStrOfByte();
                    break;
                case 0xA434:
                    rt.Name = "鏡頭型號";
                    rt.Value = pBytes.ToStrOfByte();
                    break;
                case 0x9003:
                    rt.Name = "拍攝時間";
                    var temp=pBytes.ToStrOfByte().Split(' ');
                    rt.Value =temp[0].Replace(":","/")+" "+temp[1];
                    break;
                case 0x0132:
                    rt.Name = "修改時間";
                    temp=pBytes.ToStrOfByte().Split(' ');
                    rt.Value =temp[0].Replace(":","/")+" "+temp[1];
                    break;
                case 0x0131:
                    rt.Name = "軟件";
                    rt.Value = pBytes.ToStrOfByte();
                    break;
                case 0xA002:
                    rt.Name = "圖像高度";
                    rt.Value = pBytes.ToUnInt16()+" px";
                    break;
                case 0xA003:
                    rt.Name = "圖像寬度";
                    rt.Value = pBytes.ToUnInt16()+" px";
                    break;
                case 0x011A:
                     fm=pBytes.ToUnInt32(false); 
                     fz= pBytes.ToUnInt32();
                    rt.Value = fm == 1 ? fz.ToString() : fz + "/" + fm;
                    rt.Value+=" dpi";
                    rt.Name = "水平方向分辨率";
                    break;
                case 0x011B:
                    fm=pBytes.ToUnInt32(false); 
                     fz= pBytes.ToUnInt32();
                    rt.Value = fm == 1 ? fz.ToString() : fz + "/" + fm;
                    rt.Value += " dpi";
                    rt.Name = "垂直方向分辨率";
                    break;
                case 0x8822:
                    rt.Value = ExposureMode(pBytes.ToUnInt16());
                    rt.Name = "曝光程序";
                    break;
                case 0x9207:
                    rt.Value = MeteringMode(pBytes.ToUnInt16());
                    rt.Name = "測光模式";
                    break;
                case 0x829A:
                    fm=pBytes.ToUnInt32(false); 
                    fz= pBytes.ToUnInt32();
                    //分母大於分子寫為1/XXX,分母小於分子,寫為保留一位小數
                    rt.Value = http://www.cnblogs.com/fancyblogs/p/fm>fz ? "1/"+fm/fz:((double)fz/fm).ToString("0.0");
                    rt.Value += "";
                    rt.Name = "曝光時間";
                    break;
                case 0x8827:
                    rt.Value = pBytes.ToUnInt16().ToString();
                    rt.Name = "ISO";
                    break;
                case 0x920A:
                    fm=pBytes.ToUnInt32(false); 
                     fz= pBytes.ToUnInt32();
                    rt.Value=fm==1?fz.ToString():((double)fz/fm).ToString("0.00");
                    rt.Value += " mm";
                    rt.Name = "焦距";
                    break;
                case 0x829D:
                    rt.Value ="f/"+((double)pBytes.ToUnInt32() / pBytes.ToUnInt32(false));
                    rt.Name = "光圈";
                    break;
                
                case 0x9204:
                    fm = pBytes.ToUnInt32(false);
                    var fz1=Convert.ToInt32(pBytes[3] << 24 | pBytes[2] << 16 | pBytes[1] << 8 | pBytes[0]);
                    //曝光補償要加+ -
                    rt.Value = http://www.cnblogs.com/fancyblogs/p/fz1 > 0 ? "+" : "";
                    rt.Value +=fz1 == 0 ? "0" : fz1+ "/"+ fm;
                    rt.Name = "曝光補償";
                    break;
                case 0x9208:
                    rt.Value = WhiteBalance(pBytes.ToUnInt16());
                    rt.Name = "白平衡";
                    break;
                case 0x9209:
                    rt.Value = FlashMode(pBytes.ToUnInt16());
                    rt.Name = "閃光燈";
                    break;
                default: rt.Name = "其他";
                    rt.Value = "Unkown";
                    break;
            }
            return rt;
        }

        /// <summary>通過PropertyItems獲取照片參數
        /// </summary>
        /// <param name="imgPath">照片的絕對路徑</param>
        /// <returns>參數的集合</returns>
        public static IEnumerable<Exif> GetExifByPi(string imgPath)
        {
            var img = Image.FromFile(imgPath);
            var pItems = img.PropertyItems;//將"其他"信息過濾掉
            return pItems.Select(pi => InfoOfExif(pi.Id, pi.Type, pi.Value)).Where(j=>j.Name!="其他").ToList();
        }

        #endregion

在調用的時候用 var piList=GetExifByPi("照片路徑");這種方法需要註意以下幾個方面:

註意:

1、Image.PropertyItems的Type中同一個類型有的時候不能用同一個方法得到,這是由於參數的表現方式不同,所以建議用Id,每一個ID用對應的方法將byte[]裝換為string。

2、不同型號的手機和相機Exif中存儲方式不一樣,這一點非常重要,也就是說這個方法其實無法準確獲每個圖片的信息。我們需要將每種相機和手機分別用不同方法獲取,這個工作量太大了,幸好有第三方插件。

二、通過metadata-extractor獲取照片參數

metadata-extractor是目前最簡單易用的EXIF信息處理包,是由Drew Noakes寫的。官網: https://drewnoakes.com/code/exif/  官網上面的是用的.nupkg的文件,而不是傳統的.dll文件,需要通過nuget引入本地。如果不會安裝和使用nuget的可以參考文獻:http://www.cnblogs.com/chsword/archive/2011/09/14/NuGet_Install_Operatepackage.html  成功安裝nuget後再vs中點擊:工具->NuGet程序包管理器->程序包管理器控制臺。

然後在"pm>"處輸入:Install-Package MetadataExtractor  可以參考:https://www.nuget.org/packages/MetadataExtractor/ 

最後將dll引用到您的項目中:

 

完整代碼:

 

#region 通過metadata-extractor獲取照片參數

        //參考文獻
        //官網: https://drewnoakes.com/code/exif/
        //nuget 官網:https://www.nuget.org/
        //nuget 使用: http://www.cnblogs.com/chsword/archive/2011/09/14/NuGet_Install_OperatePackage.html
        //nuget MetadataExtractor: https://www.nuget.org/packages/MetadataExtractor/

        /// <summary>通過MetadataExtractor獲取照片參數
        /// </summary>
        /// <param name="imgPath">照片絕對路徑</param>
        /// <returns></returns>
        public static Dictionary<string,string> GetExifByMe(string imgPath)
        { 
            var rmd = ImageMetadataReader.ReadMetadata(imgPath);
            var rt=new Dictionary<string,string>();
            foreach (var rd in rmd)
            {
                foreach(var tag in rd.Tags)
                {
                    var temp = EngToChs(tag.Name);
                    if (temp == "其他")
                    {
                        continue;
                    }
                    if (!rt.ContainsKey(temp))
                    {
                        rt.Add(temp, tag.Description);
                    }
                    
                }
            }
            return rt;
        }

        /// <summary>篩選參數並將其名稱轉換為中文
        /// </summary>
        /// <param name="str">參數名稱</param>
        /// <returns>參數中文名</returns>
        private static string EngToChs(string str)
        {
            var rt = "其他";
            switch (str)
            {
                case "Exif version": rt = "Exif版本";
                    break;
                case "Model": rt = "相機型號";
                    break;
                case "Lens Model": rt = "鏡頭類型";
                    break;
                case "File Name": rt = "文件名";
                    break;
                case "File Size": rt = "文件大小";
                    break;
                case "Date/Time": rt = "拍攝時間";
                    break;
                case "File Modified Date": rt = "修改時間";
                    break;
                case "Image Height": rt = "照片高度";
                    break;
                case "Image Width": rt = "照片寬度";
                    break;
                case "X Resolution": rt = "水平分辨率";
                    break;
                case "Y Resolution": rt = "垂直分辨率";
                    break;
                case "Color Space": rt = "色彩空間";
                    break;

                case "Shutter Speed Value": rt = "快門速度";
                    break;
                case "F-Number": rt = "光圈";//Aperture Value也表示光圈
                    break;
                case "ISO Speed Ratings": rt = "ISO";
                    break;
                case "Exposure Bias Value": rt = "曝光補償";
                    break;
                case "Focal Length": rt = "焦距";
                    break;

                case "Exposure Program": rt = "曝光程序";
                    break;
                case "Metering Mode": rt = "測光模式";
                    break;
                case "Flash Mode": rt = "閃光燈";
                    break;
                case "White Balance Mode": rt = "白平衡";
                    break;
                case "Exposure Mode": rt = "曝光模式";
                    break;
                case "Continuous Drive Mode": rt = "驅動模式";
                    break;
                case "Focus Mode": rt = "對焦模式";
                    break;
            }
            return rt;
        }

        #endregion

 

使用的時候:var me=GetExifByMe(); 

註意:

1、var rmd = ImageMetadataReader.ReadMetadata(imgPath);方法裏可以是照片路徑和Stream類型。

2、metadata-extractor會將所有信息讀出來,而且還是英文的,所以要將裏面的數據進行選取,需要的還要轉換為中文。

參考文獻:
官網: https://drewnoakes.com/code/exif/
nuget MetadataExtractor: https://www.nuget.org/packages/MetadataExtractor/
nuget 使用: http://www.cnblogs.com/chsword/archive/2011/09/14/NuGet_Install_OperatePackage.html

 


Tags: 美圖秀秀 第三方 字符串 圖片 .net

文章來源:


ads
ads

相關文章
ads

相關文章

ad