blog.Ring.idv.tw

分散式處理Sobel Edge Detector

分散式處理Sobel Edge Detector


.2010/05/24 已新增MapReduce New API版本

大約兩年前我曾用ActionScript寫了「Sobel - 邊緣偵測 for AS2」,那時純粹只是抱持著好玩的心態~ 而現在用同樣的例子改成Hadoop版本來試試~ 當然最主要就是要藉重它分散式運算的能力~ 只是這樣的應用僅需要透過「Map」階段將處理後的影像直接寫入HDFS就行了~ 不需要再經過shuffle和reduce階段來浪費頻寬等資源~ 另外值得一提的是~ 這個例子要處理的是整張影像檔~ 所以要避免在進行「Map」階段之前處於被分割的命運~ 這裡採用的作法是覆寫「isSplitable()」method並將整份檔案當作一筆Record來處理,有興趣的朋友請見附檔:

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.MapReduceBase;
import org.apache.hadoop.mapred.Mapper;
import org.apache.hadoop.mapred.OutputCollector;
import org.apache.hadoop.mapred.Reporter;
import org.apache.hadoop.mapred.lib.NullOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

public class SobelProcessing extends Configured implements Tool
{

    public static class Map extends MapReduceBase implements
            Mapper<NullWritable, BytesWritable, Text, Text>
    {

        private JobConf conf;

        @Override
        public void configure(JobConf conf)
        {
            this.conf = conf;
        }

        public void map(NullWritable key, BytesWritable value,
                OutputCollector<Text, Text> output, Reporter reporter)
                throws IOException
        {
            String filename = conf.get("map.input.file");
            String output_dir = conf.get("output.dir");
            filename = getFileName(filename);
            FileSystem fs = FileSystem.get(conf);
            FSDataOutputStream dos = fs.create(new Path(output_dir + filename + ".jpg"));

            BufferedImage src = ImageIO.read(new ByteArrayInputStream(value.getBytes()));

            float sobscale = Float.valueOf(conf.get("sobscale"));
            int offsetval = Integer.valueOf(conf.get("offsetval"));

            int iw = src.getWidth();
            int ih = src.getHeight();
            BufferedImage dest = new BufferedImage(iw, ih, src.getType());

            int[][] gray = new int[iw][ih];

            for (int x = 0; x < iw; x++)
            {
                for (int y = 0; y < ih; y++)
                {
                    int rgb = src.getRGB(x, y);
                    int r = 0xFF & (rgb >> 16);
                    int g = 0xFF & (rgb >> 8);
                    int b = 0xFF & rgb;
                    gray[x][y] = (int) (0.299 * r + 0.587 * g + 0.114 * b);
                }
            }

            for (int x = 1; x < iw - 1; x++)
            {
                for (int y = 1; y < ih - 1; y++)
                {
                    int a = gray[x - 1][y - 1];
                    int b = gray[x][y - 1];
                    int c = gray[x + 1][y - 1];
                    int d = gray[x - 1][y];
                    int e = gray[x + 1][y];
                    int f = gray[x - 1][y + 1];
                    int g = gray[x][y + 1];
                    int h = gray[x + 1][y + 1];

                    int hor = (a + d + f) - (c + e + h);

                    if (hor < 0)
                        hor = -hor;

                    int vert = (a + b + c) - (f + g + h);

                    if (vert < 0)
                        vert = -vert;

                    int gc = (int) (sobscale * (hor + vert));
                    gc = (gc + offsetval);

                    if (gc > 255)
                        gc = 255;

                    int sobel = 0xff000000 | gc << 16 | gc << 8 | gc;
                    dest.setRGB(x, y, sobel);
                }
            }

            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(dos);
            encoder.encode(dest);
            dos.close();
        }

        public String getFileName(String s)
        {
            return s.substring(s.lastIndexOf("/"), s.lastIndexOf("."));
        }
    }

    public int run(String[] args) throws Exception
    {
        JobConf conf = new JobConf(getConf(), SobelProcessing.class);

        conf.set("sobscale", "1.0");
        conf.set("offsetval", "0");
        conf.set("output.dir", args[1]);

        conf.setJobName("SobelProcessing");
        conf.setMapperClass(Map.class);

        conf.setInputFormat(WholeFileInputFormat.class);
        conf.setOutputFormat(NullOutputFormat.class);

        conf.set("mapred.child.java.opts", "-Xmx256m");
        conf.setNumReduceTasks(0);

        WholeFileInputFormat.setInputPaths(conf, new Path(args[0]));
        JobClient.runJob(conf);
        return 0;
    }

    public static void main(String[] args)
    {
        try
        {
            int res = ToolRunner.run(new Configuration(), new SobelProcessing(), args);
            System.exit(res);
        } catch (Exception e)
        {
            e.printStackTrace();
        }

    }
}

結果:

原始碼

原始碼(New API)

2009-03-13 23:22:21

58 comments on "分散式處理Sobel Edge Detector "

  1. 1. Sophia 說:

    請問一下,如果我在VMware上面設了Hadoop,那要怎麼讓他執行這篇文章中的程式?而且要怎麼傳入圖片呢?

    2009-06-09 23:30:43

  2. 2. Shen 說:

    請參考Hadoop的相關文件說明,例如:HDFS File System Shell Guide

    2009-06-10 01:32:16

  3. 3. Mayra 說:

    我需要编辑使用Hadoop,Hadoop的哪个版本的使用,因为它给我的错误使用WholeFileInputFormat图像

    2009-12-18 03:46:39

  4. 4. Shen 說:

    有更明確的錯誤訊息嗎?

    2009-12-18 08:38:54

  5. 5. Mayra 說:

    My error message is:

    "WholeFileInputFormat cannot be resolved to a type".

    In this link you can see a image with the error
    http://www.box.net/shared/7hxkc16de6

    I'm using Hadoop-0.18.0. How i can run your code?? I need an special library?

    Thanks

    2009-12-18 23:02:16

  6. 6. Shen 說:

    @Mayra
    Please download the whole source code:
    http://blog.ring.idv.tw/upload/a315/sobel.zip

    2009-12-18 23:39:01

  7. 7. Mayra 說:

    Thank's, worked using Hadoop 0.20

    2009-12-22 01:37:18

  8. 8. Gra 說:

    請問一下
    如果map的key/value pair之中的 value想要寫成另外一個.bat檔案 要怎麼在hdfs上存取

    2010-02-10 23:51:46

  9. 9. Shen 說:

    你指的是在map階段直接輸出到一個.bat檔案,然後如何從HDFS上存取它嗎?
    如果是的話,直接透過FileSystem的open()即可,可參考:http://blog.ring.idv.tw/comment.ser?i=329

    2010-02-11 00:30:25

  10. 10. Gra 說:

    我需要在map的class裡面 把value拆成一個array存到hdfs上的一個.bat檔
    不是做完map的output檔案 對他做存取

    2010-02-11 15:04:17

  11. 11. Shen 說:

    這麼說好了,如果你只是要在map就直接輸出到hdfs,而不執行reduce,那直接參考上述範例即可,重點在於將reduce數量設為0並設置你的輸出格式和位置。

    2010-02-11 15:25:19

  12. 12. Gra 說:

    reduce部分我還有其他的工作要分配給他,所以無法使用上述範例。
    我目前要做的是在map的部分,要對value(會是String)拆成一個array,再把array存到.bat檔。
    不知道這樣的表達,你是否可以理解?T_T

    2010-02-11 16:04:59

  13. 13. Shen 說:

    Hi, Gra
    我要強調的是,存成.bat檔不是重點,拆成array也不是重點,重點在於你是要在map階段就輸出某些資訊到hdfs,這樣的話就直接在map寫下:
    FileSystem fs = FileSystem.get(conf);
    FSDataOutputStream dos = fs.create(new Path(output_dir + filename + ".bat"));
    以進行IO操作即可。

    2010-02-11 16:30:39

  14. 14. Gra 說:

    問題解決了,謝謝。 :)

    2010-02-11 21:27:01

  15. 15. Gra 說:

    Hi,Shen
    我又有新問題了。
    請問我可以在hadoop上面直接執行shell檔去執行一個執行檔嗎?

    2010-02-12 15:59:23

  16. 16. Shen 說:

    可以,但你必須保證在你每台slave的機器上都有同樣的環境。

    2010-02-12 16:08:35

  17. 17. Gra 說:

    確定是同樣的環境
    我目前已經把 .sh和執行檔放上hadoop
    Shell.ShellCommandExecutor shl=new Shell.ShellCommandExecutor(sh_temp);
    shl.execute();

    但都會有下面錯誤
    java.ioIOException: Cannot rum program "hdfs://master:9000/user/hadoop/learn.sh" : java.io.IOException: error:2, NO such file or directory

    是我給的指令不對嗎?

    2010-02-12 17:19:15

  18. 18. Shen 說:

    請將你要執行的檔案放在local disk即可,不需要寫到HDFS。

    2010-02-12 17:25:40

  19. 19. Gra 說:

    為什麼不需要把執行的檔案寫到hdfs? 執行檔要讀的data也都在hdfs上面

    2010-02-12 17:51:04

  20. 20. Shen 說:

    因為要存取HDFS必須透過HDFS所提供的API才能存取,它不同於一般我們使用的檔案系統。

    2010-02-12 18:05:22

  21. 21. Gra 說:

    1.若要在hadoop run 執行檔,執行runtime.exe會出錯,那可能會是什麼問題?
    2.放在local端,那這樣還可以用大象去跑程式嗎?
    3.我本來預計是要把寫好的input data存入了hdfs,要在map的class裡面,呼叫一個執行檔(這執行檔是用C寫的,要利用shell去呼叫),這個執行檔會去讀存在hdfs上的data。如果照你說的,他必須要用到hdfs的api,這樣可行嗎?因為如果存在在local disk他就不能做我想要的工作了。

    不好意思,問題有點多.....

    2010-02-12 18:37:03

  22. 22. Shen 說:

    1. 需要看錯誤訊息才能得知,一般不會有問題(不過為何你的Hadoop要跑在Windows?)。
    2. 可以,在Map/Reduce透過Java所提供的ProcessBuilder去執行一個外部的執行檔是OK的,但是還有許多細節需要了解的,例如:speculation execution
    3. 能不能移植到Java?或透過JNI直接呼叫C所提供的函式?或者是如果要從C去存取HDFS可參考:http://hadoop.apache.org/common/docs/r0.20.1/libhdfs.html

    問題有很多種解法,我只能提供你一些可能的方向。
    先休息吧~ 都快過年了 ^^

    2010-02-12 19:02:18

  23. 23. Gra 說:

    1. SORRY,我打錯指令,在call shell還是用上面的方法。
    2. Java所提供的ProcessBuilder是指java的api?
    3. 前面你提到的每台電腦環境要一樣,若把資料放在HDFS上,不代表每台電腦都放,是嗎?
    4.sh檔的權限是-rw-r--r-- 會因此不能執行他嗎?

    因為是新手在趕東西,還是得把握時間趕快弄,新年快樂。

    2010-02-12 23:58:54

  24. 24. Shen 說:

    2. Java所提供的ProcessBuilder是指java的api?
    是的

    3. 前面你提到的每台電腦環境要一樣,若把資料放在HDFS上,不代表每台電腦都放,是嗎?
    我指的環境是你的shell執行檔,還有它所需要執行時的相關資料都要一樣。

    4.sh檔的權限是-rw-r--r-- 會因此不能執行他嗎?
    當然,請加個「a+x」

    2010-02-22 16:11:15

  25. 25. woo 說:

    我用Java Jdk Gel軟體寫程式,但是文章中的方法,轉不過來,
    參考過上面建議的JAVA那篇,但是效果不同,懇請版主交一下,
    以JAVA寫要怎麼寫?

    2010-04-28 23:24:02

  26. 26. Shen 說:

    @@? 上述就是用Java啊~

    2010-04-29 00:17:59

  27. 27. 你好 說:

    請問一下sobscale跟offsetval作用是什麼?
    不好意思我只是一個大學生有一些東西還不是很懂可以幫我解答一下嗎?

    2010-05-12 16:50:07

  28. 28. Shen 說:

    sobscale = scale factor
    offsetval = 純粹偏移值的設定
    你可以觀察一下影像的變化。

    2010-05-12 21:50:29

  29. 29. newbie 說:

    新手請教一下:

    新版0.20.2中org.apache.hadoop.mapred.FileInputFormat這個class已被deprecated
    由org.apache.hadoop.mapreduce.lib.input.FileInputFormat取代
    但新的FileInputFormat已沒有getRecordReader這個抽象函數
    查了一下發現這函數現在在org.apache.hadoop.mapred.lib.CombineFileInputFormat
    那麼您所寫的WholeFileInputFormat.java該如何修改呢 ?

    謝謝

    2010-05-16 15:00:04

  30. 30. Shen 說:

    請參考:http://www.slideshare.net/sh1mmer/upgrading-to-the-new-map-reduce-api
    或 Hadoop API文件改寫一下即可。

    2010-05-17 23:36:05

  31. 31. wu 說:

    請問 , JPEGCodec及JPEGImageEncoder在新版java已不能使用
    那麼
    JPEGImageEncoder encoder=JPEGCodec.createJPEGEncoder(dos);
    encoder.encode(dest);
    dos.close();
    該怎麼修正? 是用javax.imageio.ImageIO嗎?

    2010-05-20 21:48:39

  32. 32. Shen 說:

    你用的是哪一版?可能被移掉了~
    用ImageIO也是可行~ 直接將BufferedImage傳進去就行了

    2010-05-20 22:52:34

  33. 33. wu 說:

    是最新版的jdk6 update 20 , 在ubuntu上運作
    ---
    也就是說可以用ImageIO.write嗎? 感謝解答~

    2010-05-20 23:05:10

  34. 34. wu 說:

    不好意思上述沒寫完整 ,
    所以是用 ImageIO.write(dest, "jpg", dos); 去取代嗎 ?

    2010-05-20 23:22:46

  35. 35. Shen 說:

    是的

    2010-05-20 23:40:09

  36. 36. Barry 說:

    你好, 我使用ImageIO.write(dest, "jpg", dos); 寫到HDFS時
    發現有檔案名稱, 但是都是空檔, 不知你有沒有遇到類似的問題

    2010-05-21 04:10:32

  37. 37. Shen 說:

    我剛跑了一下,一切都正常!

    2010-05-21 13:52:58

  38. 38. young 說:

    你好 , 我照新版hadoop api修改了程式如下:

    http://users3.jabry.com/sobeltest/sobel.html
    http://users3.jabry.com/sobeltest/WholeFileInputFormat.html
    http://users3.jabry.com/sobeltest/WholeFileRecordReader.html

    是能夠編譯 , 但跑了數十次總是出現下面的exception , 能請你指教一下嗎

    java.lang.NullPointerException
    at Sobel.SobelEdgeDetector$SobelMapper.map(SobelEdgeDetector.java:34)
    at Sobel.SobelEdgeDetector$SobelMapper.map(SobelEdgeDetector.java:23)
    at org.apache.hadoop.mapreduce.Mapper.run(Mapper.java:144)
    at org.apache.hadoop.mapred.MapTask.runNewMapper(MapTask.java:621)
    at org.apache.hadoop.mapred.MapTask.run(MapTask.java:305)
    at org.apache.hadoop.mapred.Child.main(Child.java:170)

    能請你指教一下嗎

    2010-05-21 17:01:53

  39. 39. Shen 說:

    請修正你的SobelEdgeDetector程式裡的main function

    2010-05-21 17:13:10

  40. 40. young 說:

    不好意思,能給我比較明確的問題方向嗎?
    起先以為要指明class的package,修改後問題還是存在
    剛接觸hadoop,請多包涵

    2010-05-21 23:03:43

  41. 41. young 說:

    我把WholeFileRecordReader改成如下:
    http://users3.jabry.com/sobeltest/WholeFileRecordReader.html
    暫時解決了java.lang.NullPointerException

    但執行的訊息是
    10/05/23 17:56:40 WARN mapred.JobClient: Use GenericOptionsParser for parsing the arguments. Applications should implement Tool for the same.
    10/05/23 17:56:41 INFO input.FileInputFormat: Total input paths to process : 1
    10/05/23 17:56:41 INFO mapred.JobClient: Running job: job_201005121949_0076
    10/05/23 17:56:42 INFO mapred.JobClient: map 0% reduce 0%
    10/05/23 17:56:49 INFO mapred.JobClient: map 100% reduce 0%
    10/05/23 17:56:51 INFO mapred.JobClient: Job complete: job_201005121949_0076
    10/05/23 17:56:51 INFO mapred.JobClient: Counters: 5
    10/05/23 17:56:51 INFO mapred.JobClient: Job Counters
    10/05/23 17:56:51 INFO mapred.JobClient: Launched map tasks=1
    10/05/23 17:56:51 INFO mapred.JobClient: Data-local map tasks=1
    10/05/23 17:56:51 INFO mapred.JobClient: Map-Reduce Framework
    10/05/23 17:56:51 INFO mapred.JobClient: Map input records=0
    10/05/23 17:56:51 INFO mapred.JobClient: Spilled Records=0
    10/05/23 17:56:51 INFO mapred.JobClient: Map output records=0

    似乎沒把資料讀到map phase

    2010-05-23 19:31:14

  42. 42. Shen 說:

    Hi young,

    我已經新增MapReduce New API版本,請參考:http://blog.ring.idv.tw/upload/a315/sobel_new.zip

    2010-05-24 22:42:22

  43. 43. young 說:

    謝謝Shen兄 , 總算知道我問題出在哪裡了

    2010-05-25 14:28:23

  44. 44. tom 說:

    1.我在map的函式中打入:
    Runtime.getRuntime().exec("執行檔絕對路徑");
    System.out.println("test");
    但是在執行map時,上面兩個指令似乎都沒有任何作用。

    2.此外,請問用Runtime.getRuntime().exec("執行檔絕對路徑"); 的方式可以
    讓hadoop的每個map主機均執行一次此執行檔嗎?


    請指教,謝謝

    2010-07-02 19:32:23

  45. 45. Shen 說:

    >Runtime.getRuntime().exec("執行檔絕對路徑");
    >System.out.println("test");
    >但是在執行map時,上面兩個指令似乎都沒有任何作用。

    1.請檢查權限是否允許
    2.sysout並不會在Console上顯示,它會記錄在${HadoopHome}/logs/userlogs/[map|reduce]/目錄之下

    >此外,請問用Runtime.getRuntime().exec("執行檔絕對路徑"); 的方式可以讓hadoop的每個map主機均執行一次此執行檔嗎?

    可以。

    2010-07-05 14:47:33

  46. 46. fan 說:

    您好 請問有辦法用map引入video做處理嗎 像.avi這類的video 謝謝

    2011-02-24 16:09:47

  47. 47. Shen 說:

    Hi fan,
    當然可以啊~ 你可以照著這篇用WholeFileInputFormat將一個個video讀進來處理。

    2011-02-24 17:01:56

  48. 48. fan 說:

    11/02/25 16:45:05 WARN conf.Configuration: DEPRECATED: hadoop-site.xml found in the classpath. Usage of hadoop-site.xml is deprecated. Instead use core-site.xml, mapred-site.xml and hdfs-site.xml to override properties of core-default.xml, mapred-default.xml and hdfs-default.xml respectively
    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
    at sobel.SobelProcessing.main(SobelProcessing.java:116)

    你好 我想先測試shen寫的這個程式 出現這個錯誤 想請問shen該如何解決

    2011-02-25 16:48:25

  49. 49. Shen 說:

    你必須設定你的Hadoop組態設定,如:core-site.xml, mapred-site.xml and hdfs-site.xml,你可能是參考我之前寫的Hadoop架設那篇,因為Hadoop在0.20改版之後,將Hadoop Core, HDFS and MapReduce都區分開來,所以設定檔要各別設定,麻煩你找一下網路上的相關資源,我有空時在補篇新版的架設方式 >"<

    2011-02-25 17:05:18

  50. 50. fan 說:

    shen您好 那些我之前就都有設定了呢 我去跑hdfs上傳下載檔案的程式也是會跑出
    WARN conf.Configuration: DEPRECATED: hadoop-site.xml found in the classpath. Usage of hadoop-site.xml is deprecated. Instead use core-site.xml, mapred-site.xml and hdfs-site.xml to override properties of core-default.xml, mapred-default.xml and hdfs-default.xml respectively

    但是還是會成功上傳檔案到hdfs和hdfs下載檔案到本機目錄

    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
    at sobel.SobelProcessing.main(SobelProcessing.java:116)
    想請問這兩行錯誤再哪呢?

    2011-02-25 18:15:18

  51. 51. Shen 說:

    這個錯誤訊息是操作陣列時超過索引範圍所造成的,你有傳個要讀的檔案路徑給程式嗎?

    2011-02-25 22:33:22

  52. 52. fna 說:

    shen你好 我是把圖片命為test.jpg並上傳到hdfs 請問路徑是要加在哪呢
    我改了很多地方 但是還是無法正確執行

    2011-03-09 10:17:16

  53. 53. Shen 說:

    執行你的hadoop job時所要輸入的指令如下:
    $HADOOP/bin/hadoop jar your.jar [input] [output]
    實際情況可能如下:
    $HADOOP/bin/hadoop jar sobel.jar /test/*.jpg /test_out/

    2011-03-09 14:16:56

  54. 54. fan 說:

    shen 您好我再eclipse把程式打包輸出sobel.jar之後執行出現下面錯誤訊息

    root@hdp-2:/home/hdp/hadoop-0.20.2/bin# ./hadoop jar sobel.jar/test/*.jpg/test_out/
    Exception in thread "main" java.io.IOException: Error opening job jar: sobel.jar/test/*.jpg/test_out/
    at org.apache.hadoop.util.RunJar.main(RunJar.java:90)
    Caused by: java.util.zip.ZipException: error in opening zip file
    at java.util.zip.ZipFile.open(Native Method)
    at java.util.zip.ZipFile.<init>(ZipFile.java:114)
    at java.util.jar.JarFile.<init>(JarFile.java:133)
    at java.util.jar.JarFile.<init>(JarFile.java:70)
    at org.apache.hadoop.util.RunJar.main(RunJar.java:88)

    2011-03-09 17:27:06

  55. 55. fan 說:

    shen您好 我測試成功了

    如果把video抓進來做處理 是不是有些參數要變更?

    2011-03-09 18:16:02

  56. 56. Shen 說:

    上述範例是處理影像,影片就照你自己的需求去改寫~ 我只是提供一個可以在Hadoop上處理特定格式的作法。

    2011-03-10 10:43:45

  57. 57. Wei 說:

    你好,我想請問一下,有關文件再map只要超過設定的mb大小就會被分割!這部分是要如何使用isSplitable()方法可以讓他不分割文件,能否請您提供一個小小的範例!?
    hadoop 初學中,希望shen大大不吝惜指教,謝謝

    2011-07-06 11:02:18

  58. 58. Shen 說:

    我這篇所附的原始碼都有覆寫isSplitable(),你可以參考看看。(文章下方有下載點)

    2011-07-06 11:34:49

Leave a Comment

Copyright (C) Ching-Shen Chen. All rights reserved.

::: 搜尋 :::

::: 分類 :::

::: Ads :::

::: 最新文章 :::

::: 最新回應 :::

::: 訂閱 :::

Atom feed
Atom Comment