Image trimming trong PHP với kỹ thuật Edge Detecting

trong danh mục PHP

image-trimming-with-php

Bây giờ mới có thời gian ngồi viết blog để chia sẻ một số kỹ thuật với mọi người tiếp, mọi người thông cảm vì dạo này ít viết nhé!

Hôm nay mình chia sẻ tới các bạn 1 kỹ thuật nho nhỏ mà mình sử dụng trong Reader dùng trong admin panel đó là trim image. Kỹ thuật này tập trung vào các hàm xử lý hình trong PHP với thư viện gd2, không cần các thư viện khác kèm theo như imagemagick…

Các bạn xem hình này:

trim-image-with-php

Hình gốc trước khi trim

Trong admin, có một tính năng cho phép lấy URL hình bìa sách rồi save về hệ thống. Có một vấn đề là như các bạn quan sát hình lấy về ở trên, nó bị 2 vùng màu trắng 2 bên (do chủ ý của bên cung cấp hình) nên mình không thể nào dùng trực tiếp hình này mà fải cắt bỏ 2 vùng màu trắng 2 bên, kỹ thuật này trong đồ họa gọi là “trim image”.

Nhiệm vụ của xử lý PHP đề cập trong bài này là làm sao kết quả lấy về phải trở thành thế này:

image-trim-with-php

Sau khi trim image

Bắt đầu!

Trước tiên, như mọi lần tìm 1 xử lý bằng PHP nào đó thì search banh thằng google trước, nếu không có thì mới tự code vì thời gian là vàng bạc. Search thử thì thấy hướng dẫn ở đây http://zavaboy.com/2007/10/06/trim_an_image_using_php_and_gd có vẻ là gần với mình nhất, thử triển khai thì kết quả không như mình mong muốn, mà kết quả bằng script đó giống như kết quả của tính năng TRIM trong photoshop thế này:

image trimming with photoshop

Kết quả trim bằng Photoshop

Tại sao lại còn dư khúc màu trắng mà không loại bỏ hết?
Đây là lý do vì sao hàm trim ở trên và Photoshop lại fail.

Chú ý ở phần phóng to, các vùng màu nhòe nhòe nhạt ở bên trái đường biên

Chính vì phần màu mờ mờ không phải trắng thuần khiến cho quá trình trim bị fail vì thuật toán trim nói chung dựa vào 1 background color để tìm đường biên rồi cắt hình.
Như vậy là thuật toán này không sử dụng được!

Trên đường đi tìm thuật toán cao hơn

Edge detection

Search lần hồi cũng tìm ra bài toán cao hơn giải quyết vấn đề tương tự là các thuật toán Edge Detecting (tìm đường biên). Nguyên lý của thuật toán này là dựa vào màu sắc tương phản và khoảng cách giữa các màu để tìm ra các đường biên trong hình ảnh.

Ví dụ về thuật toán Edge detection

Thuật toán Edge detection dường như quá rộng và không tập trung cho bài toán nhỏ của mình và thời gian không cho phép để triển khai thuật toán dạng này nên không thể theo dạng này được, nhưng ý tưởng của thuật toán này đã được vận dụng và đi đến phương án mà mình đề cập dưới đây.

Small Edge Trimming

Bước 1: Tìm màu trung bình cho mỗi cột

Nếu quan sát thì thấy hình input của mình cần trim 2 bên nên thường là 2 bên sẽ có vùng gần như màu trắng (nếu trắng hết thì may mắn quá rồi).
Mình tiến hành scan theo từng cột và tìm ra màu sắc trung bình cho từng cột. Cách tính màu sắc trung bình cũng đơn giản, lấy tất cả điểm ảnh của cột đó, lấy trung bình R (Rt), trung bình G(Gt), trung bình B(Bt) là có được màu trung bình trên cột đó (Rt,Gt,Bt).
Dưới đây là màu sắc trung bình từ hình ví dụ ở trên:

Chỉ có 1 màu nên chỉ cao 1px, nhưng vì hiển thị cho dễ nên set chiều cao là 300px

Khoảng cách giữa các màu trên từng cột

Dựa vào quan sát thì thấy bài toán của mình cần phân biệt 2 loại màu là trắng và không phải trắng, do đó nếu dùng thuật toán tính khoảng cách để tìm khoảng cách giữa các màu thì sẽ thấy có sự chênh lệch rõ ràng giữa đường viền và khu vực màu trắng bên trái.

Có nhiều thuật toán để tìm khoảng cách giữa 2 màu, mình chọn công thức tính khoảng cách euclid để tính khoảng cách. Distance = sqrt((Ra – Rb) * (Ra – Rb) + (Ga – Gb) * (Ga – Gb) + (Ba – Bb) * (Ba – Bb))

Bảng dưới đây cho thấy khoảng cách màu giữa từng cột dựa vào các màu trung bình ở trên:

Dòng ở trên là thứ tự pixel, dòng ở dưới là khoảng cách từ màu ở pixel đó so với trước. Chú ý tại pixel 45 có sự chênh lệch khá lớn và cho thấy màu sắc thay đổi lớn, và đó là nơi xuất hiện đường viền trái.

Tìm điểm biên

Dựa vào bảng kết quả này, ta có thể cho vòng lặp chạy cho tới khi gặp điểm có sự chênh lêch lớn và đánh dấu là điểm biên trái. Tương tự, ta cũng có thể lặp từ phải sang để tìm điểm có sự chênh lệch lớn và đánh dấu biên phải, vậy là đủ thông số để gọi hàm imagecopy() trong PHP để cắt hình rồi.

Lưu ý khi tìm điểm biên: Trong thuật toán này có một con số quan trọng đó là khoảng cách bao nhiêu là đủ để biết điểm tiếp theo là điểm biên. Tùy vào hình mà bạn có thể set giới hạn khoảng cách này, trong code của mình là 100.

Mở rộng

Ban đầu thuật toán chỉ áp dụng cho hình không có padding trên và dưới vì nếu có thêm 2 padding này sẽ làm cho màu trung bình của từng cột tịnh tiến dần về sáng trắng (vì cộng thêm màu trắng ^^), do đó sẽ làm cho khoảng cách giữa các điểm biên và thường thu hẹp lại, dễ gây nhầm lẫn khi cắt tự động, lúc này có thể điều chỉnh là khoảng cách nhận biết biên có đề cập ở trên để thuật toán chính xác hơn, bởi đây là một thuật toán nhận dạng, phải có can thiệp và cảm nhận của con người vào.
– Hiện tại mình không có thời gian test đối với các hình có độ trong suốt như PNG, GIF và cũng không có nhu cầu, nếu ai test có gì cải tiến thì chia sẻ nhé.

———————

Đoạn code cho thấy thuật toán đề cập ở trên:

function trimImage($imagepath, $outputimagepath = '')
{
	$result = false;
	
	$imageinfo = getimagesize($imagepath);
	$ext = strtoupper(substr($imagepath, strrpos($imagepath, '.')+1));
	switch($ext)
	{
		case 'JPG':
		case 'JPEG': $im = imagecreatefromjpeg($imagepath); break;
		case 'GIF': $im = imagecreatefromgif($imagepath); break;
		case 'PNG': $im = imagecreatefrompng($imagepath); break;
	}
	
	if($im)
	{
		// Get the image width and height.
		$imw = imagesx($im);
		$imh = imagesy($im);

		// scan by vertical line
		// algorith: tinh mau trung binh cua 1 line, neu distanct vuot qua threshold thi tuc la edge tai X
		for($ix = 0; $ix < $imw; $ix++)
		{
			$ixTotalR = 0;
			$ixTotalG = 0;
			$ixTotalB = 0;
			
			for($iy = 0; $iy < $imh; $iy++)
			{
			    $rgb = imagecolorat($im, $ix, $iy);
			    $r = ($rgb >> 16) & 0xFF;
				$g = ($rgb >> 8) & 0xFF;
				$b = $rgb & 0xFF;
				$ixTotalR += $r;
				$ixTotalG += $g;
				$ixTotalB += $b;
			}
			
			//find average of all color in column
			$ixColumnR[$ix] = $ixTotalR/$imh;
			$ixColumnG[$ix] = $ixTotalG/$imh;
			$ixColumnB[$ix] = $ixTotalB/$imh;
			
			
		}
					
		//find left border
		for($i = 0; $i < $imw; $i++)
		{
			$dstprev = 0;
			if($i > 0)
			{
				$dstr = $ixColumnR[$i] - $ixColumnR[$i-1];
				$dstg = $ixColumnG[$i] - $ixColumnG[$i-1];
				$dstb = $ixColumnB[$i] - $ixColumnB[$i-1];
				$dstprev = sqrt($dstr * $dstr + $dstg * $dstg + $dstb * $dstb);
			}
			
			//thredshold = 100 to detect edge from previous color
			if($dstprev > 100)
			{
				$xmin = $i;
				break;
			}
		}
		
		//find right border
		for($i = $imw-1; $i >= 0; $i--)
		{
			$dstnext = 0;
			if($i < $imw - 1)
			{
				$dstr = $ixColumnR[$i] - $ixColumnR[$i+1];
				$dstg = $ixColumnG[$i] - $ixColumnG[$i+1];
				$dstb = $ixColumnB[$i] - $ixColumnB[$i+1];
				$dstnext = sqrt($dstr * $dstr + $dstg * $dstg + $dstb * $dstb);
			}
			
			//thredshold = 100 to detect edge from previous color
			if($dstnext > 100 && $xmax == 0)
			{
				$xmax = $i;
				
			}
		}
		
		$ymin = 0;
		$ymax = $imh - 1;
		
		// The new width and height of the image. (not including padding)
		$imw = 1+ $xmax-$xmin - 2; // Image width in pixels
		$imh = 1+ $ymax-$ymin - 2; // Image height in pixels
		
		// Make another image to place the trimmed version in.
		$im2 = imagecreatetruecolor($imw, $imh);
    	
		// Copy it over to the new image.
		if(!imagecopy($im2, $im, 0, 0, $xmin+1, $ymin+1, $imw, $imh))
		{
			$result = false;
		}
		else
		{
			if($outputimagepath == '')
			{
				$outputimagepath = $imagepath;
			}
			
			//OUTPUT IMAGE
			switch($ext)
			{
		    		case 'JPG':  imagejpeg($im2, $outputimagepath); break;
		    		case 'JPEG': imagejpeg($im2, $outputimagepath); break;
		    		case 'GIF': imagegif($im2, $outputimagepath); break;
		    		case 'PNG': imagepng($im2, $outputimagepath); break;
			}
			
			$result = true;
		}
		
		imagedestroy($im);
		imagedestroy($im2);
	}
	
	return $result;
}

———————-
Download source PHP + Image

12 bình luận

  1. Nguyễn Tam Hùng says:

    Bài viết rất hay và bổ ích. Cảm ơn anh nhiều.

  2. Nên có thêm đánh dấu phân loại bài :D, và cho bài này vào loại: Trung Bình, hoặc Nâng Cao

  3. […] Image trimming trong PHP với kỹ thuật Edge Detecting- Blog Hoc Tap. […]

  4. Minh Huân says:

    Bài viết hay quá. Được mở mang kiến thức. Cám ơn thầy.

  5. Luongtd says:

    Tuyệt vời, cảm ơn cậu

  6. nguyễn Thị Thu Hà says:

    Quá hay. thanks you

  7. Bá Lượng says:

    Xin chào! Mình có một bài toán muốn tham khảo ý kiến của bạn, bài toán thế này: “khi người dùng upload 1 hình lên website của mình, tìm và lấy ra những hình tương tự trong thư mục hình ảnh.”
    Mình đã nghĩ đến việc lấy màu trung bình của ảnh ng dùng up, so sánh với màu trung bình của từng ảnh trong thư mục, nhưng khi load sẽ rất lâu(có thể treo máy^^) và cũng ko bít ý tưởng này có thể thực hiện được không nữa. Nên nhờ bạn có thể góp ý cho mình được ko? Thanks you!

  8. admin says:

    @Bá Lượng, Chào bạn, vấn đề của bạn tùy theo hệ thống và khái niệm “tương tự” của bạn nó ra sao mà tiến hành thông. Mỗi người mối kiểu và mỗi cách “tương tự” là mỗi thuật toán do bạn quy định. Còn về đợi đến lúc upload mới đi lấy màu trung bình của tất cả hình thì quá muộn. Bạn nên lưu thông tin màu của tất cả hình trong hệ thống vào db để khi cần có thể select ra thôi. Tùy cơ ứng biến, mình chỉ có thể chia sẻ đến đây thôi. Goodluck!.

  9. Lê Hậu says:

    Đi lòng vòng thấy cuốn ebook này, không biết php nên up lên đây.

  10. Lê Hậu says:

    Đi lòng vòng thấy cuốn ebook này, không biết php nên up lên đây, anh em nào quan tâm thì down về đọc ha.

    PHP Master Write Cutting Edge

    http://uploaded.to/file/cq4mlsxn

  11. Dior Fanci says:

    Chào bạn
    Bạn cho mình hỏi 1 chút là nếu chỉ đơn giản là mình muốn cắt cái 2 biên của hình thôi, ko quan trọng màu sắc ví dụ hình có kích thước w=500px; h=300px; giờ cắt mỗi bên 100px để còn lại kích thước là 300*300 thì làm thế nào hả bạn 😀

Gởi bình luận