Đọc ảnh trong C#

Chia sẻ: Lại Văn Nghĩa | Ngày: | Loại File: DOC | Số trang:19

0
705
lượt xem
154
download

Đọc ảnh trong C#

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Bài này nói qua một số điều cần biết để có thể đọc được file ảnh, chỉnh sửa trên từng pixel của ảnh trong C#. Không làm 1 đề cụ thể nào, nhưng các bước tiến hành của các đề bài cụ thể thì cũng na ná như vậy. Kèm theo 1 cái code ví dụ để mọi người ngâm cứu.

Chủ đề:
Lưu

Nội dung Text: Đọc ảnh trong C#

  1. Admin Ngày tham gia: Chủ nhật Tháng 11 25, 2007 2:23 pm Bài viết: 338 Given: 5 thanks Received: 191 thanks Bài này nói qua một số điều cần biết để có thể đọc được file ảnh, chỉnh sửa trên từng pixel của ảnh trong C#. Không làm 1 đề cụ thể nào, nhưng các bước tiến hành của các đề bài cụ thể thì cũng na ná như vậy. Kèm theo 1 cái code ví dụ để mọi người ngâm cứu. * Đọc ảnh trong C#: Dùng 1 đối tượng của lớp Bitmap để tạo mới 1 ảnh( mới hoàn toàn hay đọc mới từ file). VD: Bitmap bm = new Bitmap("C:\Pictures\abc.jpg"); // lệnh trên khai báo 1 biến bm, là 1 ảnh lấy từ C:\Pictures\abc.jpg * In ảnh lên form: Đọc được vào biến rồi, giờ phải in ra để mọi người cùng thấy đúng không? Dùng phương thức DrawImage để hiện ảnh lên Ví dụ: Hiện ảnh lên form chính Graphics gr = CreateGraphics();// Khởi tạo đồ hoạ trên form chính gr.DrawImage(bm,0,0); gr.Dispose()// Giải phóng biến graphics Các tham số của DrawImage là: bm: ảnh cần đưa ra màn hình 0,0 : toạ độ góc trên bên trái của ảnh * Truy xuất và thay đổi từng pixel ảnh: **Cấu trúc file ảnh: Là 1 ma trận 2 chiều các pixel, ảnh thông thường là ảnh 24 bít, tức là mỗi pixel 24 bits= 3 bytes. Chúng ta tưởng tượng như sau: pixel đầu tiên của ảnh chiếm 3 byte đầu (0,1,2); pixel thứ 2 chiếm 3 byte tiếp (3,4,5); cứ như thế...
  2. Để biết ảnh bm có kích thước bao nhiêu, C# cung cấp 2 thuộc tính: bm.Width: số pixel trên 1 hàng(độ rộng) bm.Height: số hàng(độ cao) Như vậy, mỗi hàng của bức ảnh sẽ dùng hết bm.Width*3 bytes để lưu thông tin (do mỗi pixel 3 byte) Tuy nhiên khi lưu vào máy, thì mỗi hàng của ảnh phải dùng nhiều hơn số byte đó để lưu, vì mục đích lưu cả thông tin của biên thì phải( cái này sẽ xem thêm). Để biết thực sự số byte này là bao nhiêu, đầu tiên phải chuyển bm sang 1 lớp đệm, lớp BitmapData Ví du: Rectangle rec = new Rectangle(0,0,bm.Width,bm.Height); BitmapData bmData = bm.LockBits(rec, ImageLockMode.ReadWrite, PixelFormat.24bitrgp); Câu lệnh đầu tiên khai báo 1 hình chữ nhật rec, chính là kích thước của ảnh bm, và dùng làm tham số cho câu lệnh phía dưới. Phương thức LockBits sẽ chuyển từ 1 ảnh, sang 1 vùng nhớ (bmData). Trên ảnh bm, ta chỉ có thể xem nó, chứ không thể chỉnh sử pixel. Muốn sửa, phải chuyển nó sang lớp BitmapData, trên đó có các thứ cần thiết để can thiệp vào từng điểm ảnh. Bây giờ, bmData sẽ "trả công" cho ta bằng cách cung cấp cho ta 2 thuộc tính quan trọng: - bmData.Stride: số byte thực sự mà máy tính lưu trũ mỗi hàng của ảnh. Quan trọng, vì nếu ta nghĩ ảnh kích thứơc 5x5, thì lưu trên đĩa 5x3=15 byte 1 hàng, nhưng nếu stride=16, thì khi ta muốn xuống dòng thứ 2 đễr xử lý tiếp, ta truy xuất vào byte thứ 16, thì thực tế ta lại truy xuất vào cái rìa của dòng 1. - bmData.Scan0: chú ý là số 0, không phải chữ O. Cái này chỉ ra địa chỉ pixel đầu tiên của ảnh mà bmData quản lý. Muốn thay đổi từng pixel ta phải biết cái này, rồi dùng con trỏ trỏ đến địa chỉ đó, thay đổi, sau đó dịch con trỏ lên 1 đơn vị, tiếp tục thay đổi tiếp. Tư tưởng là vậy. ** Ghép mấy thứ loằng ngoằng trên để xử lý 1 bức ảnh xem nào: Đặt ra 1 ví dụ: đọc 1 bức ảnh và hiện lên form, khi người dùng nhấn vào 1 nút trên form, biến bức ảnh đó thành toàn màu trắng. Các bước đọc ảnh và hiển thị đã nói ở trên. Ở đây ta chỉ quan tâm đến việc thay tất cả các pixel của ảnh thành pixel màu trắng. Chú ý màu trắng là cả 3 byte của nó đều bằng 255. BitmapData bmData = bm.LockBits(rec, ImageLockMode.ReadWrite, PixelFormat.24bitrgp); int stride = bmData.Stride; int nOffset = stride - bm.Width*3; //nOffset chính là cái rìa của bức ảnh, khi con trỏ xử lý đến pixel cuối cùng của hàng, thì muốn xuống //hàng kế tiếp, ta phải bỏ qua cái rìa này bằng cách cộng thêm địa chỉ con
  3. trỏ với nOffset byte *p = (byte*)bmData.Scan0; //p sẽ trỏ đến địa chỉ đẩu của ảnh int x,y; for(y = 0; y
  4. - Có 2 miền xử lý chính: + Không gian + Tần số - Có 3 loại xử lý chính: + Xử lý trên điểm ảnh + Xử lý lân cận + Xử lý toàn cục (thường được xử lý trên miền tần số) - Những kiến thức cơ bản liên quan: + Không gian màu RGB + Cơ bản về ảnh số + Cách biểu diễn ảnh số + Độ phân giải ảnh, màu + Ngôn ngữ lập trình PHẦN XỬ LÝ TRÊN MIỀN KHÔNG GIAN 1. Chuyển đổi cơ bản 1.1 Sử dụng phương thức Set-GetPixel - Thông thường ảnh được xử lý trên ảnh mức xám (một lớp màu), ảnh trắng đen (ảnh nhị -phân) - Vậy làm sao để chuyển một ảnh màu về ảnh mức xám hay ảnh nhị phân? … - Như các bạn đã biết + Một ảnh mức xám có nghĩa là: một điểm ảnh trong ảnh này được biểu diễn bằng một số 8 bit = 2^8=256 giá trị từ tối tới sáng + Một ảnh nhị phân: một điểm ảnh được biểu diễn bằng số một bit = 2^1=2 giá trị tối và sáng
  5. + Ảnh màu đương nhiên được tổng hợp từ 3 màu R-G-B Ví dụ: - Để chuyển ảnh màu về ảnh xám chúng ta có công thức sau: a. O(x,y)=( IR(x,y) + IG(x,y) + IB(x,y)) /3 Với: x,y là tạo độ của điểm ảnh IR thành phân màu đỏ tại màu I(x,y) IG thành phân màu xanh lá tại màu I(x,y) IB thành phân màu xanh dương tại màu I(x,y) O(x,y): mà đầu ra, I(x,y) màu đầu vào b. O(x,y)= IR(x,y) * 0.287 + IG(x,y) * 0.599 + IB(x,y) * 0.114 Ví dụ 1: Sử dụng công thức b cho ví dụ 1 như sau: Code: private Bitmap ToGray(Bitmap bm) { Bitmap bitmap = new Bitmap(bm); int x, y; Color c; Byte gray; for (y = 0; y < bm.Height - 1; y++) { for (x = 0; x < bm.Width - 1; x++) { c = bm.GetPixel(x, y); gray =Convert.ToByte(c.R * 0.287 + c.G * 0.599 + c.B * 0.114); bitmap.SetPixel(x, y, Color.FromArgb(gray, gray, gray)); } } return bitmap; } Kết quả thử nghiệm như sau:
  6. - Đối với ảnh nhị phân ta kiểm tra giá trị màu nếu
  7. 1.2 Sử dụng LockBits - Nhìn lại ví dụ 2, chuyện gì đã xảy ra…: Chạy tốt, nhưng vấn đề là thời gian - Hai hàm trên đều sử dụng 2 phương thức set và get, về mặt thuật toán có lẻ bạn nghĩ 2 vòng lặp đó đã làm cho chương trình trở nên quá rùa đồng thời nếu sử dụng ảnh có độ phân giải hơi lớn tí là có thể treo luôn. - Vấn đề không phải giải thuật mà chính do 2 phương thức set và getpixel gây nên, khi bạn gọi 2 phương thức này hệ điều hành Win sẽ Lock ảnh lại đến khi kết thúc phương thức vừa goi tự động sẽ UnLock ảnh đó cho việt truy cập lần sau. Chính việc Lock rồi Unlock liên tục đã làm đã làm cho hàm trên xử lý chậm rãi từ tốn. - Giải thuật sau dùng kỹ thuật LockBits Code: private Bitmap ToBinaryLocBits(Bitmap bm, Byte band) { Bitmap bitmap = new Bitmap(bm); Rectangle rec = new Rectangle(0, 0, bitmap.Width, bitmap.Height); System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(rec, System.Drawing.Imaging.ImageLockMode.ReadWrite, bitmap.PixelFormat); IntPtr ptr = bmpData.Scan0; Int32 bytes = bmpData.Stride * bitmap.Height; Byte[ rgbValues = new Byte[bytes - 1]; System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes); Byte color; for (int counter = 0; counter < rgbValues.Length - 1; counter += 4) { color = rgbValues[counter]; if (color < band) { rgbValues[counter + 0] = 0; rgbValues[counter + 1] = 0; rgbValues[counter + 2] = 0; } else { rgbValues[counter + 0] = 255; rgbValues[counter + 1] = 255; rgbValues[counter + 2] = 255;
  8. } } System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes); bitmap.UnlockBits(bmpData); return bitmap; } - Giải thuật trên chủ yêu dựa trên 2 phương thức: Code: System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes); System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes); - Nhiệm vụ của chúng là sao chép các ô nhớ từ ptr vào mảng byte và ngược lại. - Tại sao lại có step là 4 (counter+=4) ? Đối với lớp Bitmap, khi ta khai báo khởi tạo bằng phương thức new sẽ nhận được đầu vào là một bitmap định dạng 32bit. Code: Bitmap bitmap = new Bitmap(bm); Nếu chỉ gán bằng thì PixelFormat sẽ nhận định dạng bằng chính ảnh đầu vào Code: Bitmap bitmap; bitmap = bm; Do đó, để tổng quát chúng ta dùng phương thức new: Bạn hãy thử viết lại ví dụ 1 với LockBits rồi so sánh với get-set. 1.3 Tổ Chức Đồ (Histogram) Định Nghĩa: Là đồ thị mô tả mối quan hệ giữa mức xám và số điểm ảnh có chung mức xám Ví dụ:
  9. - Tổ chức đồ được xử lý trên điểm ảnh, thường được xử lý để nâng cao chất lượng ảnh có thể dùng để nhận dạng ảnh - Cánh tính: + Tính h[x] là số lượng điểm ảnh có cùng mức xám x + Chạy i từ 0 -> 255 đếm số điểm ảnh có cùng mức xám i Ví dụ: Tính H[x] cho ảnh xám Code: int x,y; int [ H=new int[255]; for(y = 0;y
  10. - Bạn có thấy sự khác biệt giữa 2 ảnh? Rõ ràng ảnh sau có cường độ sáng cao hơn ảnh trước và điều đó tương đương với tổ chức đồ của 2 ảnh, chân của tổ chức đồ sau lệch về phải hơn so với tổ chức đồ trước. Vậy dựa vào tổ chức đồ của ảnh ta có thể nhận dạng ảnh tối hay sáng. + Nếu ảnh tối => ảnh sáng : Kéo tổ chức đồ về phải + Nếu ảnh sáng => ảnh tối : Kéo tổ chức đồ về trái (Các phép toán trên tổ chức đồ ta sẽ tìm hiểu ở phần sau) - Vẽ tổ chức đồ: Code:
  11. private Bitmap HistogramGray(Bitmap bm, int[ h) { //Tinh H[x] int x, y; for (y = 0; y < bm.Height - 1; y++) { for (x = 0; x < bm.Width - 1; x++) h[bm.GetPixel(x, y).R] += 1; } //Phân bố lại khích với pictureBox int max = h[0]; for (int j = 1; j < 255; j++) if (max < h[j]) max = h[j]; //'Vẽ int Height=100; Bitmap bmHistogram = new Bitmap(256, 100); Graphics gp = Graphics.FromImage(bmHistogram); gp.FillRectangle(new Pen(Color.Black).Brush, new Rectangle(0, 0, bmHistogram.Width, bmHistogram.Height)); int n; for (int i = 0; i < 255; i++) { n = (h * Height) / max; gp.DrawLine(new Pen(Color.Gray), i, 100, i, 100 - n); } return bmHistogram; } - Chép code trên vào project để kiểm tra. Bạn cũng có thể cải tiến giải thuật trên cho ảnh màu và tốc độ xử lý. 2. Một số phép toán trên tổ chức đồ - Trong phần này chúng ta đi vào các phép xử lý tổ chức đồ cơ bản. - Như đã biết, tổ chức đồ là một đặt trưng quan trọng của ảnh, dựa vào tổ chức đồ chúng ta có thể nhận dạng sơ về ảnh, có kết luận cơ bản về ảnh: ảnh sáng, tối, độ tương phản thấp, cao…. Với lý do đó, chúng ta có một số phép xử lý sau: + Trượt tổ chức đồ + Căng tổ chức đồ + Sửa chữa tổ chức đồ + San lấp tổ chức đồ 2.1 Trượt tổ chức đồ
  12. Mục đích: làm tăng hoặc giảm cường độ xám của ảnh O(x,y) = I(x,y) + n n < 0 : trượt ảnh về bên trái => ảnh tối hơn n > 0 : trượt ảnh về bên phải => ảnh sáng hơn Chú ý: Kết quả của O(x,y) có thể > 255 hoặc < 0 vì vậy bạn cần chú ý để set lại 2 giá trị này Code: private Bitmap HisToGramStep(Bitmap bm, int n) { Bitmap bitmap=new Bitmap(bm); int x, y; Color c; for( y = 0;y
  13. gì?) O(x,y) = I(x,y) x n (với n > 0) n < 1 : thu hẹp chân tổ chức đồ => giảm độ tương phản của ảnh n > 1 : mở rộng chân tổ chức đồ => tăng độ tương phản của ảnh Code: private Bitmap HisToGramStretch(Bitmap bm, int n) { if (n
  14. + Trượt tổ chức đồ về bên trái sao cho mức xám nhỏ nhất (có giá trị) về 0. + Căng tổ chức đồ sao cho mức xám lớn nhất (có giá trị) bằng 255. O(x,y) = I(x,y) * 255 / max(h()) Ví dụ minh họa: Bước 1: tính h[x] cho mỗi mức xám Code: private void GetH(Bitmap bm , int[ toH) { int x, y; for(y = 0;y
  15. return (byte)i; } return 0; } Bước 3: ráp lại theo thứ tự giải thuật Code: private Bitmap HistogramEdit(Bitmap bm) { Bitmap bitmap = new Bitmap(bm); //Tinh h(x) int[ hL = new int[255]; int[ hR = new int[255]; GetH(bitmap, hL); //Tim Trai & truot trai int left = HistogramLeft(hL); bitmap = new Bitmap(HisToGramStep(bm, -left)); //Tinh tien ve 0 va copy hL qua hR Array.Copy(hL, left, hR, 0, hL.Length - left); int x, y; Byte c; Byte max = HistogramRight(hR); for (y = 0; y < bitmap.Height - 1; y++) { for (x = 0; x < bitmap.Width - 1; x++) { c = bitmap.GetPixel(x, y).R; c = (byte)(c / max * 255); bitmap.SetPixel(x, y, Color.FromArgb(c, c, c)); } } return bitmap; } Chú ý: tìm left cho hL[x] trước khi trượt về trái, tìm right khi đã trượt về trái đồng thời đã tính hR[x], lợi dụng tính trượt của tổ chức đồ vì thế tôi không cần phải tính lại hR[x] mà chỉ thông qua hàm copy của lớp Array: Code: //'Tinh tien ve 0 va copy hL qua hR Array.Copy(hL, left, hR, 0, hL.Length - left); Xong! Chỉ có thế, giờ thì bạn có thể kiểm tra… 2.4 San lấp tổ chức đồ Mục đích: phân bố lại các mức xám => tăng độ tương phản của ảnh
  16. - Tổ chức đồ của ảnh kết quả sẽ ít lõm hơn ảnh ban đầu Từ đó ta có một số công thức sau cho việc san lấp: Code: private Bitmap HistogramEqualization(Bitmap bm) { Bitmap bitmap = new Bitmap(bm); int w = bitmap.Width * bitmap.Height; //Tinh to chuc do int[ h = new int[255]; int[ hN = new int[255]; GetH(bm, h); //Chuan hoa to chuc do for (int i = 0; i < 255; i++) { hN = h / w; } //Tinh ham mat do xac Suat int[ Z = new int[255]; Z[0] = hN[0]; for (int i = 1; i < 255; i++) { Z = Z[i - 1] + hN; }
  17. int x, y; Byte c; for (y = 0; y < bitmap.Height - 1; y++) { for (x = 0; x < bitmap.Width - 1; x++) { c = bitmap.GetPixel(x, y).R; c = Convert.ToByte(Math.Round(Z * 255)); bitmap.SetPixel(x, y, Color.FromArgb(c, c, c)); } } return bitmap; } Chú ý: - Nếu dựa theo công thức tính hàm mật độ xác suất thì cách tính sau có lẽ hay hơn vì rút ngắn thời gian tính toán Code: //Tinh ham mat do xac Suat int[ Z = new int[255]; Z[0] = hN[0]; for (int i = 1; i < 255; i++) { Z = Z[i - 1] + hN; } Kết quả: Kết luận về tổ chức đồ: - Dựa vào tổ chức đồ ta có thể kết luận về ảnh tương đối chính xác mà từ đó có thể xử lý chúng để được một ảnh tốt hơn. - Việc san lấp xảy ra khi căng và trượt không có tác dụng nâng cao chất lượng ảnh. - Trên thực tế, việc xử lý không phải lúc nào cũng xảy ra trên toàn bộ ảnh nên việc xử lý toàn cục như trên là không có tác dụng. Vì thế các giải thuật trên các bạn cần sửa lại một cách hợp lý để việc xử lý ảnh có kết quả tốt nhất. 3. Các phép toán trên ảnh
  18. 3.1 And - Ở phần này, các phép toán tuơng đối cơ bản, vì thế các bạn có thể tự code mà cũng chả cần xem code của ai - Để minh họa các bạn xem hình dưới đây: Ví dụ đơn giản về phép toán AND Code: for (y = 0; y < bitmap.Height - 1; y++) { for (x = 0; x < bitmap.Width - 1; x++) { r = bm1.GetPixel(x, y).R && bm2.GetPixel(x, y).R; g = bm1.GetPixel(x, y).G && bm2.GetPixel(x, y).G; b = bm1.GetPixel(x, y).B && bm2.GetPixel(x, y).B; bitmap.SetPixel(x, y, Color.FromArgb(r, g, b)); } }
Đồng bộ tài khoản