2013-08-13 10 views
7

में एक छवि और सोबेल फ़िल्टर ऑप्टिमाइज़ेशन की तेज ट्रांसपोजिशन मैं एक वास्तव में (वास्तव में) तेजी से Sobel operator को एक रे-ट्रैसर के लिए मेरे मित्र के लिए लागू करना चाहता हूं और मैंने लिखा (स्रोत here पाया जा सकता है)। निम्नानुसार है जो मैं अब तक समझता हूं ...सी (सिम)

सबसे पहले, मान लें कि छवि एक बिट-स्केल पिक्चर स्टोर लाइन है जो 8 बिट्स हस्ताक्षरित पूर्णांक सरणी में लाइन द्वारा लाइन है।

असली सोबेल फ़िल्टर लिखने के लिए, मुझे प्रत्येक पिक्सेल के लिए जीएक्स और जी की गणना करने की आवश्यकता है। इन संख्याओं में से प्रत्येक की उत्पत्ति के बगल में 6 पिक्सेल के लिए गणना की जाती है। लेकिन सिमड निर्देश मुझे 16 या 32 (एवीएक्स) पिक्सल से निपटने की अनुमति देता है। उम्मीद है, ऑपरेटर की गिरी, कुछ अच्छा संपत्ति है, ताकि मैं द्वारा Gy की गणना कर सकते हैं:

  • प्रत्येक मैं और मैं + 2 पंक्तियाँ घटाकर और (एक i + 1 कुछ अन्य चित्र की पंक्ति में परिणाम की दुकान सरणी)
  • i + 1 और मैं +2 स्तंभों की, मैं जोड़ने दो बार तो जोड़ने i + 1 अंतिम तस्वीर के स्तंभ

मैं Gx गणना करने के लिए एक ही (लेकिन स्थानांतरित) करना होगा देना दो चित्र

कुछ नोट:

  • मैं स्मृति आवंटन बारे में परवाह नहीं के बाद से सब कुछ शुरुआत में आवंटित किया जाएगा है।
  • मैं अतिप्रवाह से निपटने और (_mm_srli_epi8 करने के लिए धन्यवाद) द्वारा चार मूल्यों को विभाजित समस्या प्रवेश कर सकते हैं (uint8_t >> 2 - uint8_t >> 2) = int7_t //really store as int8_t
    int7_t + uint8_t << 1 >> 2 + int7_t = uint8_t
    //some precision is lost but I don't care

वास्तविक समस्या का सामना करना पड़ रहा हूँ स्तंभों को पंक्तियों से जाने के लिए है। चूंकि मैं सिम रजिस्टर में अन्यथा तस्वीर लोड नहीं कर सका। मुझे छवि को तीन बार कम से कम फ्लिप करना चाहिए, है ना?

मूल तस्वीर के बाद। फिर मैं जीएक्स और जी के लिए पहले चरण की गणना कर सकता हूं और फिर परिणामस्वरूप चित्रों को दूसरे चरण की गणना करने के लिए फ्लिप कर सकता हूं।

  • एक अच्छा विचार इस प्रकार का कार्यान्वयन है:

    तो, यहाँ मेरे सवालों का है?

  • क्या गूंगा एल्गोरिदम से तेज सरणी को स्थानांतरित करने का कोई तरीका है? (मुझे ऐसा नहीं लगता)
  • बाधाएं कहां होंगी? (कोई अनुमान है?: पी)
+3

यह धागा [क्या सी ++ में एक मैट्रिक्स स्थानांतरित करने के लिए सबसे तेज़ तरीका है?] (Http://stackoverflow.com/questions/16737298/what-is-the-fastest-way-to-transpose-a -मैट्रिक्स-इन-सी) इसमें कुछ अच्छी सामग्री है और आप इसे उपयोगी पा सकते हैं, इनमें से अधिकांश सी –

+0

पर लागू है। निस्संदेह मैं "मेरे दृष्टिकोण को बदलने" का जोखिम नहीं उठा सकता क्योंकि मुझे इन आंकड़ों को सिमड रजिस्टरों में लोड करना होगा। लेकिन ओपनएमपी ... मैं इस भविष्य को पढ़ूंगा। – matovitch

+0

यह। है। महान। ब्लॉक द्वारा एसएसई। मुझे _MM_TRANSPOSE4_PS नहीं पता था जो कि शफल का एक गुच्छा है। एक बार फिर धन्यवाद ! – matovitch

उत्तर

8

मुझे लगता है कि सोबेल ऑपरेटर कोड को अनुकूलित करने के लिए ट्रांसपोज़/2-पास अच्छा नहीं है। सोबेल ऑपरेटर कम्प्यूटेशनल फ़ंक्शन नहीं है, इसलिए इस मामले के लिए स्थानांतरण/2-पास पहुंच के लिए मेमोरी एक्सेस बर्बाद करना अच्छा नहीं है। मैंने कुछ सोबेल ऑपरेटर टेस्ट कोड लिखे हैं यह देखने के लिए कि एसएसई कितनी तेजी से प्राप्त कर सकता है। यह कोड पहले और अंतिम किनारे पिक्सेल को संभाल नहीं करता है, और sqrt() मान की गणना करने के लिए एफपीयू का उपयोग करता है।

सोबेल ऑपरेटर को 14 गुणा, 1 वर्ग रूट, 11 अतिरिक्त, 2 मिनट/अधिकतम, 12 पढ़ने का उपयोग और 1 लेखन पहुंच ऑपरेटर की आवश्यकता है। इसका मतलब है कि यदि आप कोड को अच्छी तरह अनुकूलित करते हैं तो आप 20 ~ 30 चक्र में एक घटक को संसाधित कर सकते हैं।

फ़्लोटसोबेल() फ़ंक्शन ने 256 * 256 छवि प्रसंस्करण 32.76 चक्र/घटक को संसाधित करने के लिए 2113044 CPU चक्र लिया। मैं इस नमूना कोड को एसएसई में बदल दूंगा।

void FPUSobel() 
{ 
    BYTE* image_0 = g_image + g_image_width * 0; 
    BYTE* image_1 = g_image + g_image_width * 1; 
    BYTE* image_2 = g_image + g_image_width * 2; 
    DWORD* screen = g_screen + g_screen_width*1; 

    for(int y=1; y<g_image_height-1; ++y) 
    { 
     for(int x=1; x<g_image_width-1; ++x) 
     { 
      float gx = image_0[x-1] * (+1.0f) + 
         image_0[x+1] * (-1.0f) + 
         image_1[x-1] * (+2.0f) + 
         image_1[x+1] * (-2.0f) + 
         image_2[x-1] * (+1.0f) + 
         image_2[x+1] * (-1.0f); 

      float gy = image_0[x-1] * (+1.0f) + 
         image_0[x+0] * (+2.0f) + 
         image_0[x+1] * (+1.0f) + 
         image_2[x-1] * (-1.0f) + 
         image_2[x+0] * (-2.0f) + 
         image_2[x+1] * (-1.0f); 


      int result = (int)min(255.0f, max(0.0f, sqrtf(gx * gx + gy * gy))); 

      screen[x] = 0x01010101 * result; 
     } 
     image_0 += g_image_width; 
     image_1 += g_image_width; 
     image_2 += g_image_width; 
     screen += g_screen_width; 
    } 
} 

SseSobel() फ़ंक्शन एक ही 256 * 256 छवि पर कार्रवाई करने 613,220 सीपीयू चक्र ले लिया। इसमें 9.51 चक्र/घटक और FPUSobel() से 3.4 गुना तेजी से लिया गया। ऑप्टिमाइज़ करने के लिए कुछ रिक्त स्थान हैं लेकिन यह 4 गुना से अधिक तेज़ नहीं होगा क्योंकि यह 4-तरफा सिमड का उपयोग करता है।

यह फ़ंक्शन एक बार में 4 पिक्सल को संसाधित करने के लिए सोए दृष्टिकोण का उपयोग करता था। एसओए अधिकांश सरणी या छवि डेटा में एओएस से बेहतर है क्योंकि आपको एओएस का उपयोग करने के लिए ट्रांसफर/शफल करना होगा। और एसएएस कोडों के लिए सामान्य सी कोड को बदलना बहुत आसान है।

void SseSobel() 
{ 
    BYTE* image_0 = g_image + g_image_width * 0; 
    BYTE* image_1 = g_image + g_image_width * 1; 
    BYTE* image_2 = g_image + g_image_width * 2; 
    DWORD* screen = g_screen + g_screen_width*1; 

    __m128 const_p_one = _mm_set1_ps(+1.0f); 
    __m128 const_p_two = _mm_set1_ps(+2.0f); 
    __m128 const_n_one = _mm_set1_ps(-1.0f); 
    __m128 const_n_two = _mm_set1_ps(-2.0f); 

    for(int y=1; y<g_image_height-1; ++y) 
    { 
     for(int x=1; x<g_image_width-1; x+=4) 
     { 
      // load 16 components. (0~6 will be used) 
      __m128i current_0 = _mm_unpacklo_epi8(_mm_loadu_si128((__m128i*)(image_0+x-1)), _mm_setzero_si128()); 
      __m128i current_1 = _mm_unpacklo_epi8(_mm_loadu_si128((__m128i*)(image_1+x-1)), _mm_setzero_si128()); 
      __m128i current_2 = _mm_unpacklo_epi8(_mm_loadu_si128((__m128i*)(image_2+x-1)), _mm_setzero_si128()); 

      // image_00 = { image_0[x-1], image_0[x+0], image_0[x+1], image_0[x+2] } 
      __m128 image_00 = _mm_cvtepi32_ps(_mm_unpacklo_epi16(current_0, _mm_setzero_si128())); 
      // image_01 = { image_0[x+0], image_0[x+1], image_0[x+2], image_0[x+3] } 
      __m128 image_01 = _mm_cvtepi32_ps(_mm_unpacklo_epi16(_mm_srli_si128(current_0, 2), _mm_setzero_si128())); 
      // image_02 = { image_0[x+1], image_0[x+2], image_0[x+3], image_0[x+4] } 
      __m128 image_02 = _mm_cvtepi32_ps(_mm_unpacklo_epi16(_mm_srli_si128(current_0, 4), _mm_setzero_si128())); 
      __m128 image_10 = _mm_cvtepi32_ps(_mm_unpacklo_epi16(current_1, _mm_setzero_si128())); 
      __m128 image_12 = _mm_cvtepi32_ps(_mm_unpacklo_epi16(_mm_srli_si128(current_1, 4), _mm_setzero_si128())); 
      __m128 image_20 = _mm_cvtepi32_ps(_mm_unpacklo_epi16(current_2, _mm_setzero_si128())); 
      __m128 image_21 = _mm_cvtepi32_ps(_mm_unpacklo_epi16(_mm_srli_si128(current_2, 2), _mm_setzero_si128())); 
      __m128 image_22 = _mm_cvtepi32_ps(_mm_unpacklo_epi16(_mm_srli_si128(current_2, 4), _mm_setzero_si128())); 

      __m128 gx = _mm_add_ps(_mm_mul_ps(image_00,const_p_one), 
         _mm_add_ps(_mm_mul_ps(image_02,const_n_one), 
         _mm_add_ps(_mm_mul_ps(image_10,const_p_two), 
         _mm_add_ps(_mm_mul_ps(image_12,const_n_two), 
         _mm_add_ps(_mm_mul_ps(image_20,const_p_one), 
            _mm_mul_ps(image_22,const_n_one)))))); 

      __m128 gy = _mm_add_ps(_mm_mul_ps(image_00,const_p_one), 
         _mm_add_ps(_mm_mul_ps(image_01,const_p_two), 
         _mm_add_ps(_mm_mul_ps(image_02,const_p_one), 
         _mm_add_ps(_mm_mul_ps(image_20,const_n_one), 
         _mm_add_ps(_mm_mul_ps(image_21,const_n_two), 
            _mm_mul_ps(image_22,const_n_one)))))); 

      __m128 result = _mm_min_ps(_mm_set1_ps(255.0f), 
          _mm_max_ps(_mm_set1_ps(0.0f), 
             _mm_sqrt_ps(_mm_add_ps(_mm_mul_ps(gx, gx), _mm_mul_ps(gy,gy))))); 

      __m128i pack_32 = _mm_cvtps_epi32(result); //R32,G32,B32,A32 
      __m128i pack_16 = _mm_packs_epi32(pack_32, pack_32); //R16,G16,B16,A16,R16,G16,B16,A16 
      __m128i pack_8 = _mm_packus_epi16(pack_16, pack_16); //RGBA,RGBA,RGBA,RGBA 
      __m128i unpack_2 = _mm_unpacklo_epi8(pack_8, pack_8); //RRGG,BBAA,RRGG,BBAA 
      __m128i unpack_4 = _mm_unpacklo_epi8(unpack_2, unpack_2); //RRRR,GGGG,BBBB,AAAA 

      _mm_storeu_si128((__m128i*)(screen+x),unpack_4); 
     } 
     image_0 += g_image_width; 
     image_1 += g_image_width; 
     image_2 += g_image_width; 
     screen += g_screen_width; 
    } 
} 
+0

धन्यवाद, लेकिन किसी भी तरह से मैं अपना खुद का लिखूंगा (और फिर इसे अपने खिलाफ बेंचमार्क करें)।असल में, मैं एक आदर्श सोबेल ऑपरेटर की तलाश नहीं कर रहा हूं: मुझे लगता है कि मैं यूक्लिडियन मानदंड की बजाय लंबाई_1 की गणना करूंगा और दो चित्रों का उपयोग करके एक ही समय में 16 पिक्सल (8 बिट पिक्सल) संसाधित करूंगा। मैं दो _mm_avg_epu8 का उपयोग कर आरजीबीए तस्वीर को कम करने की योजना बना रहा हूं और फिर 8 बिट्स सोबेल फ़िल्टर लागू करता हूं। – matovitch

संबंधित मुद्दे