心愿作品——图片搜索系统(功能可用了,下一步布置到公网。笔记更新...)

从大一的C++实训,究试过了图片查重

后来写了:后缀修正/图片分类

最后一步就是把整个系统合并起来


大一到大三都很散漫,浪费了不少时间,大四考研,现在在等最后的分数线和调剂,不过通过的几率很渺茫

毕设又不是很紧迫,所以还是先完成自己的心愿好了,虽然一直很想将这个作为毕设,但是又怕达不到代码量


三月以来这些天,熟悉linux,搭建LAMP架构,第一次接触php代码(最常的错误漏$),重新用php实现了那时候C++写的图片特征码的代码(现在看回以前的作品,太简单了)

图片特征码的算法写的太简单了,不过先搭建整个系统,最后才换用高级的算法


首先有个测试图库,916个文件,主要是jpg,少量gif,有txt,180+MB。

然后预先遍历整个图库,写入MySQL数据库(数据库设计在文章后面)

用刚学的php配合HTML写一个简单的首页,提供上传功能,可以查看测试库里面的所有图片。

再要跳转到另外一个页面,显示搜索结果,包括上传的图片及其特征码,匹配到相似的图片及其相关的所有内容(从数据库中提取)


现在的进度,数据库内容还没有填充,预计一个上午可以,新页面没有写,估计要一个下午加上一个晚上,毕竟是新的东西

2014年03月06日,上午决定暂停php语法学习,下午重写了以前C++代码、熟悉php语言,晚上如果听宣讲会回来有时间就完成数据库的设计

数据库设计,一个表就够,功能简单嘛,数据库名:pic_db

数据库表设计:

一、要包含的数据,本地全路径(含文件名),图片类型(严格匹配后缀名),md5,SHA,图片特征码phash,大小,尺寸(宽高),修改日期(入库时间)

二、键:先添加一个唯一的编号作为主键,本地全路径是唯一的(文件系统已经默认满足),因为只有一个表,不需要外键

三、建表SQL语句: 

CREATE TABLE picinfo
(
id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
path text NOT NULL,
tpye varchar(10),
size_w int,
size_h int,
size_kb int,
modify_time DATE,
md5 varchar(255),
sha varchar(255),
phash varchar(255)
)
如果不用AUTO_INCREMENT,插入第一条默认是0,插入第二天如果还不输入id就会主键重复。如果有这个属性,从1开始自增。

删除的条目,再插入,不会填坑,id继续自增。

如果想删除全部表项可以用 truncate table 表名;   保留表的结构,连id都自动恢复从1开始。如果用同样功能的delete from table 表名,id不会重新从1计数。而且会引入很多记录,是逐条执行,没有truncate那么快。这个可以用来测试功能的时候用。


数据库操作实例:
连接mysql,关闭数据库:
$con = mysql_connect("localhost","root","123");

mysql_close($con);

连接数据库:

$db = mysql_select_db("pic_db",$con);

如果只连一个数据库,不要db也可以;但是据说要同时操作多个数据库的时候就要记下资源号。

执行语句:

mysql_query($sql);



批量插入:
当然可以单句多次插入:

insert into pic_db(path,phash) value("asdf","asdf");

我的老笔记本测试插入900+行要5s。

换用一句的sql语句,0s: insert into pic_db(path,pash) values("asdf","asdf"),("ew","we").....

我记得以前有句命令是说等会的所有操作都不submit,可以分次插入n句,随后执行一句submit之类的语句就行,在sqlite的时候用过。


2014-03-08 02:08更新,已经可以生成所有phash code,写入库,查询验证无误。明天写php页面……加油,快要ok了~~

测试效率: 900张左右的图片,180MB+,生成phash code和写入库要20s,比我大一C++的快多了,现在想想主要是当时先将图片压缩到了8×8,而这次是直接取点计算灰度。估计究这一步产生最多效益。


2014-03-09: 巨坑,用chrome浏览器,最简单的上传页面,没有指定字符集,chrome直接不反悔中文,firefox默认就可以,后来知道了有系统字符集/apache2字符集/页面字符集。这个网友做的实验很能说明问题:http://www.laruence.com/2008/04/17/110.html


 2014-03-12:这几天都没更新,一直被url中文路径卡住。期间看了js的escape,encodeURL,看了正则,看了unicode和UTF-8。今天终于用正则十六进制形式匹配出中文,可以转义了,有些特殊符号不行。找到了php的rawurlencode,之前觉得他把'/'也转义,不适合我,后来发现,跳过这个字符就正常了。最后还是没有自己写,就用原有的好了。这个世界的轮子已经造的七七八八的了,基本不用懂橡胶相关技术的。

现在贴出代码:

第一步,先在mysql建立好,假设root用户,密码是123,数据库是pic_db,主表是picinfo.

建表

CREATE TABLE picinfo
(
id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
path text NOT NULL,
tpye varchar(10),
size_w int,
size_h int,
size_kb int,
modify_time DATE,
md5 varchar(255),
sha varchar(255),
phash varchar(255),
ones int
)
ones是用来记录phash的1的个数,现在的代码米有用上。


第二步,建立图片的信息库,setupDB.php

<?php
	require_once("phash.php");
	
	$t=time();//计时开始,单位:秒

	$dirname='/home/tony/pic';//图片库的路径
	$fp = fopen("temp.txt","w");
	dodir($dirname,$fp);//遍历整个图片库,提取所有地址,写到temp.txt
	fclose($fp);

	$fp = fopen("temp.txt","r");
	$con = mysql_connect("localhost","root","123");//连mysql
	if(!$con){
		die('could not connect:'.mysql_error());
	}
	$db = mysql_select_db("pic_db",$con);//选择数据库
	$sql="insert into picinfo(path,phash,ones) values";
	//sql语句的前半句,数据不是很大的时候,values比value快不少。数据量再大就要用本地文件插入的语法了
	while($line=rtrim(fgets($fp))){//不是图片的,不插入数据库
		if(NOT_SUPPORTED === ($phash_str=phash($line))
			|| NOT_A_PIC === $phash_str){
			continue;
		}
		$cnt=0;//算一下有几个1在phash中,以后可以先用1的个数来筛选,不过暂时没有这个性能需求,不增加代码复杂度
		for($i=0;$i<64 ;$i++){
			if($phash_str[$i] == '1'){
				$cnt++;
			}
		}
		$sql .= "(\"$line\",\"$phash_str\",\"$cnt\"),";
	}
	mysql_query(rtrim($sql,','));//php5.5.0说这个迟早要过时,用替换品。来源:www.php.net
	mysql_close($con);
	fclose($fp);

	printf("elapsed time: %ds",time()-$t);//总结时间


	function dodir($dirname,&$fp){
		$dir=opendir($dirname);
		while(($filename=readdir($dir))!==false){
			if($filename=='.' || $filename=='..'){
				continue;
			}
			if(is_dir($dirname.'/'.$filename)){
				dodir($dirname.'/'.$filename,$fp);
				
			}else{
				//print($dirname.'/'.$filename."<br>");
				fwrite($fp,$dirname.'/'.$filename.PHP_EOL);
			}

		}
	}
?>

  第三步,可供上传图片的首页.   index.php

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>

<form action="showResult.php" method="post"
enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" /> 
<br />
<input type="submit" name="submit" value="Submit" />
</form>

</body>
</html>

第四步,展示搜索结果  showResult.php

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<?php	
	require_once("phash.php");
	require_once("encodeURL.php");
	
	$t=microtime();//计时开始,单位:秒

	if ($_FILES["file"]["error"] > 0){
		echo "Error: " . $_FILES["file"]["error"] . "<br />";
	}
	else{//显示上传的文件的基本信息
		echo "Upload: " .$_FILES["file"]["name"]. "<br />";
		echo "Type: " . $_FILES["file"]["type"] . "<br />";
		echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
		//echo "Stored in: " . $_FILES["file"]["tmp_name"]."<br />";
	}
  if(!is_uploaded_file($_FILES["file"]["tmp_name"])){
  	echo "illegal uploaded file<br>";
  }
  $src_path= $_FILES["file"]["tmp_name"];
  $des_path= '/home/tony/www/upload/'.$_FILES["file"]["name"];
  if(move_uploaded_file($src_path,$des_path)){
    echo "Stored in: $des_path"."<br />";
  }else die("<br>move failed.");

	
	$ph = phash($des_path);//计算phash
	echo "phash: $ph";
	echo "<hr>";
	$link = mysqli_connect("localhost","root","123","pic_db") or dir("error: ".mysqli_error($link));//连接mysql并选择数据库。新版的语法吧,还有面向对象版,这个是过程化的版本
	
	$query = "select path,phash from picinfo";
	$result = $link->query($query) or die("error: ".mysqli_error($link));
	$paths = array();
	
	$idx = 0;//全部phash取出来,逐个比对,拙劣的brute-force。 -_-||
	while($row = mysqli_fetch_array($result)){
		if(phashCmp($ph,$row["phash"],5)){
			$paths[$idx++] = $row["path"];
		}
	}
	
	for($i=0;$i<$idx;$i++){//逐个展示
		$p = $paths[$i];
		$p = substr($p,strpos($p,"pic"));//截取相对路径
		echo $p."<br />";
		printf("<img src = %s /><hr>",encodeURL($p));//对中文路径编码转义
	}
	
	printf("elapsed microtime: %ds",microtime()-$t);//总时间
	
?>

还有两个关键的支持文件,就是require_once()里面两个,其中phash占了大部分运行时间:

phash.php

<?php
	//"$pic is not a pic<br>" <===> -1;
	define("NOT_A_PIC",-1);
	//"The picType is not supported." <===> =-2
	define("NOT_SUPPORTED",-2);
	
	function phash($pic){
		if(!($size=getimagesize($pic))){
			return NOT_A_PIC;
		}
		
		switch($size[2]){
			case 1:$format="gif";break;
			case 2:$format="jpeg";break;
			case 3:$format="png";break;
			default:return NOT_SUPPORTED;
		}
		
		$func="imagecreatefrom".$format;
		$im=$func($pic);
		if(!imageistruecolor($im))	{
			//echo "$pic isn't true color<br>";
		}

		$sum=0;
		for($i=0;$i<8;$i++){
			for($j=0;$j<8;$j++){
				$color_index=imagecolorat($im,$j*$size[0]/8,$i*$size[1]/8);
				$rgb=imagecolorsforindex($im,$color_index);
				$gray[$i*8+$j] = round(0.229 * $rgb['red'] + 0.587 * $rgb['green'] + 0.114 * $rgb['blue']);
				$sum+=$gray[i*8+j];
			}			
		}
		$avg=$sum/64;

		for($i=0;$i<64;$i++){
			if($gray[$i] > $avg){
				$result.="1";
			}else{
				$result.="0";
			}
		}
		return $result;		
	}

	//例如limit是5,那么如果有5个不一样,就返回false,也就是只有4个以下的才通过
	function phashCmp($str1,$str2,$limit){//增加形参ones可以先判断1的个数,减少遍历。
	//不过通常是不像的图片比较多,所以$cnt<$limit已经减少到很少的比较次数了
		$cnt=0;
		for($i=0;$i<64 && $cnt<$limit;$i++){
			if($str1[$i]!=$str2[$i]){
				$cnt++;
			}
		}
		return ($cnt<$limit)?true:false;
	}
	
?>

对中文url路径进行转码的encodeURL.php

<?php
	function encodeURL($str){
		$parts = explode('/',$str);
		$rstr = "";
		foreach($parts as $line){
			$rstr .= rawurlencode($line)."/";
		}
		return rtrim($rstr,"/\n");

	}

?>



最后要补充说明一下系统,编码:

LAMP环境:

Linux mint 16 32bits xfce

apache 2.4.6 Ubuntu

php 5.5.3

$LANG :zh_CN.UTF-8

apache2:没有使用AddDefaultCharset,

可以访问的页面,可能涉及unicode编码的页面都在html的header里面指定使用utf-8



暂时应该不更新了,下次如果更新的应该是 phash算法取得进展的时候了。

最多等会补充一下我用20G测试数据时候的结果了,不敢想象如此暴力的代码在08年的奔腾酷睿上跑会怎样。。。

如果有路过的大神,给我点提示吧,看看怎么才能加快这个建立图片信息库的时间(phash算法)

最后,感谢阮一峰,最先的灵感思路就是他网站的那个图片搜索原理,完全按他的实现的。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值