एक पुरानी कैप्चा साइट हैब्राहाब हैक करना

परिचय



यह आलेख संक्षिप्त रूप से हबराब्र साइट में प्रवेश करते समय पहले उपयोग की गई कैप्चा हैकिंग प्रक्रिया का वर्णन करता है।
कार्य का उद्देश्य ज्ञान को व्यवहार में लाना और कैप्चा की जटिलता का परीक्षण करना है।
एल्गोरिथ्म को विकसित करते समय, मैटलैब का उपयोग किया गया था।


कार्य अवलोकन



पुराने कैप्चा हब्रह्ब इस तरह दिखते थे:





इस कैप्चा को पहचानने में मुख्य कठिनाइयाँ:
  1. विकृत वर्ण
  2. शोर और धुंधला
  3. चरित्र के आकार बहुत अलग हैं
  4. चरित्र चौराहा

ईमानदारी से, मैं हमेशा पहली बार कैप्चा को सही ढंग से पढ़ने में सफल नहीं हुआ, क्योंकि अक्सर ए और 4, एल और 4 के अक्षर अप्रभेद्य होते थे।

फिर भी, कुछ कठिनाइयों के बावजूद, हम इस कैप्चा को पढ़ने के लिए मुख्य विचारों पर विचार करेंगे।

स्टेज 1. एक विभाजन प्रणाली का निर्माण



प्रत्येक वर्ण पहचान प्रणाली को निम्नानुसार दर्शाया जा सकता है:


हमारे मामले में, अंतिम दो उप-प्रणालियों को लागू करना आवश्यक है।

अध्ययन किए गए कैप्चा का विस्तृत विश्लेषण इसकी मुख्य विशेषताओं को उजागर करने की अनुमति देता है:
  1. कैप्चा पर हमेशा 6 अक्षर होते हैं
  2. एक समान (सबसे अधिक संभावना समान) शोर विधि का हमेशा उपयोग किया जाता है।
  3. दुर्लभ चौराहों या अतिव्यापी पात्रों

इसके आधार पर, निम्नलिखित विभाजन एल्गोरिथ्म बनाया गया था:
  1. शोर को दूर करें
  2. छवि को बायनेरिज़ करें
  3. 6 सबसे बड़े जुड़े क्षेत्रों का चयन करें


मतलाब में विभाजन कार्यान्वयन
function [ rects ] = segmentator( aImg, nRect, lightMode ) %find all symbols on habrahabr login captcha %use: rects = segmentator( aImg, nRect ) %where: % rects - rects coordinates % aImg - resized image data % nRect - count of rect to find % lightMode - find all rects without imOpen % ,          . if nargin < 4 fSRects = 0; end if nargin < 3 lightMode = 0; end minX = 8; if lightMode minX = 11; end %px minY = 16; if lightMode minY = 18; end %px %% Change color mode to 8-bit gray if(size(size(aImg),2) > 2) aImg = imadjust(rgb2gray(aImg)); end %Save aImg aImgCopy = aImg; %structuring element for imopen se = strel('disk', 2); %Remove some noise aImg(aImg > 0.92) = 1; aImg = imopen(imadjust(aImg), se); if lightMode aImg = aImgCopy; end imBW3 = adaptivethreshold(aImg,50,0.2,0); if ~lightMode imBW3 = imopen(imBW3, se); end %% find rects imBin = 1 - imBW3; CC = bwconncomp(imBin); numPixels = cellfun(@numel,CC.PixelIdxList); [biggest, idx] = sort(numPixels, 'descend'); bb = regionprops(CC, 'BoundingBox'); if lightMode imshow(aImgCopy); end %Primitive filter %copy only good rects bbCounter = 1; for i = 1 : length(bb) curRect = bb(i).BoundingBox; if (curRect(3) < minX || curRect(4) < minY) continue; end bbNew(bbCounter) = bb(i); bbCounter = bbCounter + 1; end if bbCounter == 1 rects = {-1}; return; end if DEBUG_MODE for i = 1:length(bbNew) rectangle('Position', bbNew(i).BoundingBox, 'EdgeColor', 'r'); end end %analize count of rects %1: if rectC == nrect -> all rects find %2: else if rectC > nrect -> delete smallest %3: else -> find subrects if nRect == length(bbNew) || fSRects == 1 rects = {bbNew(1:end).BoundingBox}; elseif nRect < length(bbNew) rects = deleteSmallest( bbNew, nRect ) else for i = 1 : length(bbNew) curRect = bbNew(i).BoundingBox; rectArea(i) = curRect(3) .* curRect(4); end needRect = nRect - length(bbNew); aImg = aImgCopy; [biggest, idx] = sort(rectArea, 'descend'); switch(needRect) %@todo: Redesign (check constant) case 1 subRects{1} = findSubRects( aImg, bbNew( idx(1)).BoundingBox, 2 ); subRectIdx = idx(1); case 2 if( biggest(1) > 2 * biggest(2) ) subRects{1} = findSubRects( aImg, bbNew(idx(1)).BoundingBox, 3 ); subRectIdx = idx(1); else subRects{1} = findSubRects( aImg, bbNew(idx(1)).BoundingBox, 2 ); subRects{2} = findSubRects( aImg, bbNew(idx(2)).BoundingBox, 2 ); subRectIdx = idx(1:2); end case 3 if( biggest(1) > 3 * biggest(2) ) subRects{1} = findSubRects( aImg, bbNew(idx(1)).BoundingBox, 4 ); subRectIdx = idx(1); elseif( biggest(1) > 1.5 * biggest(2) ) subRects{1} = findSubRects( aImg, bbNew(idx(1)).BoundingBox, 3 ); subRects{2} = findSubRects( aImg, bbNew(idx(2)).BoundingBox, 2 ); subRectIdx = idx(1:2); else subRects{1} = findSubRects( aImg, bbNew(idx(1)).BoundingBox, 2 ); subRects{2} = findSubRects( aImg, bbNew(idx(2)).BoundingBox, 2 ); subRects{3} = findSubRects( aImg, bbNew(idx(3)).BoundingBox, 2 ); subRectIdx = idx(1:3); end otherwise display('Not supported now'); %@todo: add more mode rects = {-1}; return; end %create return value rC = 1; for srC = 1:length(bbNew) if(sum(srC == subRectIdx)) curIdx = find(subRectIdx == srC); for srC2 = 1 : length(subRects{curIdx}) rects(rC) = subRects{curIdx}(srC2); rC = rC + 1; end else rects{rC} = bbNew(srC).BoundingBox; rC = rC + 1; end end end end function [ subRects ] = findSubRects( aImg, curRect, nSubRect ) coord{1} = [0]; pr(1) = 100; coord{2} = [0 40]; pr(2) = 60; coord{3} = [0 30 56]; pr(3) = 44; coord{4} = [0 23 46 70]; pr(4) = 30; MIN_AREA = 250; if DEBUG_MODE imshow(aImg); end wide = curRect(3); for i = 1 : nSubRect subRects{i}(1) = curRect(1) + coord{nSubRect}(i) * wide / 100; subRects{i}(2) = curRect(2); subRects{i}(3) = wide * pr(nSubRect) / 100; subRects{i}(4) = curRect(4); rect{i} = imcrop(aImg, subRects{i}); tmpRect = rect{i}; lvl = graythresh(tmpRect); tmpRect = imadjust(tmpRect); tmpRect(tmpRect > lvl + 0.3) = 1;imshow(tmpRect); tmpRect(tmpRect < lvl - 0.3) = 0;imshow(tmpRect); imbw3 = multiScaleBin(tmpRect, 0.22, 1.4, 30, 1);imshow(imbw3); imbin = 1 - imbw3; %imBin = binBlur(tmpRect, 13, 1); imshow(imBin); %other method of %adaptive binarization cc = bwconncomp(imbin); numpixels = cellfun(@numel,cc.PixelIdxList); [biggest, idx] = sort(numpixels, 'descend'); bb = regionprops(cc, 'Boundingbox'); imshow(rect{i}); %find biggest rect clear rectArea; for j = 1 : length(bb) rectArea(j) = bb(j).BoundingBox(3) .* bb(j).BoundingBox(4); end [biggest, idx] = sort(rectArea, 'descend'); newRect = bb(idx(1)).BoundingBox; rectangle('Position', newRect, 'EdgeColor', 'r'); if newRect(3) * newRect(4) > MIN_AREA subRects{i}(1) = subRects{i}(1) + newRect(1) - 1; subRects{i}(2) = subRects{i}(2) + newRect(2) - 1; subRects{i}(3) = newRect(3); subRects{i}(4) = newRect(4); end end end function [ retValue ] = deleteSmallest( bbRects, nRects ) %1: calc area for i = 1 : length(bbRects) curRect = bbRects(i).BoundingBox; rectArea(i) = curRect(3) .* curRect(4); end %2: sort area [~, idx] = sort(rectArea, 'descend'); idx = idx(1:nRects); idx = sort(idx); %copy biggest retValue = {bbRects(idx).BoundingBox}; end function [ imBIN ] = sauvola( X, k ) h = fspecial('average'); local_mean = imfilter(X, h, 'symmetric'); local_std = sqrt(imfilter(X .^ 2, h, 'symmetric')); imBIN = X >= (local_mean + k * local_std); end 



हम शोर को बहुत सरलता से हटाते हैं, इसके लिए हम छवि के सभी पिक्सेल बनाते हैं जो कुछ स्तर के सफेद की तुलना में हल्के होते हैं।
प्रसंस्करण उदाहरण:
मूल छवि
प्रसंस्करण के बाद छवि



Binarization के लिए, हम अनुकूली binarization एल्गोरिथ्म का उपयोग करते हैं, जिसमें प्रत्येक पिक्सेल (या पिक्सेल क्षेत्रों) के लिए अपने स्वयं के binarization थ्रेशोल्ड को निर्धारित किया जाता है [ Adaptive binarization ]।
द्वारीकरण के उदाहरण:
विभाजन का एक अच्छा उदाहरण (3XJ6YR)
सरेस से जोड़ा हुआ और फटे पात्रों का उदाहरण (4TAMMY)



छवि में वर्णों की खोज करने के लिए, यह जुड़ा हुआ क्षेत्रों की खोज करने की विधि का उपयोग करने का निर्णय लिया गया था, जिसे माटलाब में किया जा सकता है:
 CC = bwconncomp(imBin); bb = regionprops(CC, 'BoundingBox'); 

उसके बाद, परिणामी क्षेत्रों का विश्लेषण करें, उनमें से सबसे बड़े का चयन करें, और यदि आवश्यक हो, तो उन्हें उप-डोमेन (चिपके हुए पात्रों के मामले में) में विभाजित करें। प्रतीकों में अंतराल दिखाई देने पर मामला प्रदान नहीं किया जाता है।

विभाजन के अंतिम परिणाम के उदाहरण:







विभाजन की गुणवत्ता संतोषजनक है, हम अगले चरण पर जाते हैं।

चरण 2. एक प्रशिक्षण सेट बनाना


विभाजन के बाद, हमें आयत निर्देशांक का एक सेट मिलता है जिसमें कथित रूप से कैप्चा अक्षर होते हैं।
इसलिए, पहले हम सभी कैप्चा को मैन्युअल रूप से पहचानते हैं और उनका नाम बदल देते हैं (उस क्षण मुझे एक पेशेवर कैप्चा पहचानकर्ता की तरह महसूस हुआ, यह अफ़सोस की बात है कि उन्होंने प्रत्येक मान्यता प्राप्त के लिए 1 प्रतिशत का भुगतान नहीं किया)। फिर हम प्रशिक्षण सेट बनाने के लिए निम्नलिखित स्क्रिप्ट का उपयोग करते हैं:
प्रशिक्षण सेट बनाने की स्क्रिप्ट
 %CreateTrainSet.m clear; clc; workDir = '.\captch'; fileList = dir([workDir '\*.png']); N_SYMB = 6; SYMB_W = 18; %px SYMB_H = 28; %px WIDTH = 166; %px HIGH = 75; %px SAVE_DIR = [workDir '\Alphabet']; %process data for CC = 1 : length(fileList) imName = fileList(CC).name; recognizedRes = imName(1:N_SYMB); %open image [cdata, map] = imread( [workDir '\' imName] ); %change color mode if ~isempty( map ) cdata = ind2rgb( cdata, map ); end %resize image cdata = imresize(cdata, [HIGH WIDTH], 'lanczos3'); %find all symbols on image rects = segmentator(cdata, N_SYMB, 1); if rects{1} == -1 continue; end %imcrop and save if length(rects) == N_SYMB if ~exist(SAVE_DIR) mkdir(SAVE_DIR); end for j = 1 : N_SYMB if ~exist([SAVE_DIR '\' recognizedRes(j)]) mkdir([SAVE_DIR '\' recognizedRes(j)]); end imList = dir([SAVE_DIR '\' recognizedRes(j) '\*.jpg']); newname = num2str(length(imList) + 1); nameS = floor(log10(length(imList) + 1)) + 1; for z = nameS : 4 newname = ['0' newname]; end tim = imcrop(cdata, rects{j}); if ( size( size(tim), 2 ) > 2 ) tim = imadjust(rgb2gray(tim)); end tim = imresize(tim, [SYMB_H SYMB_W], 'lanczos3'); imwrite(tim, [SAVE_DIR '\' recognizedRes(j) '\' newname '.jpg']); end end end 



एक चयन बनाने के बाद, आपको इसे गलत वर्णों से साफ करने की आवश्यकता है जो गलत विभाजन के कारण दिखाई देते हैं।
एक दिलचस्प तथ्य यह था कि कैप्चा केवल 23 लैटिन वर्णों का उपयोग करता है।
प्रशिक्षण का नमूना लेख से जुड़ी सामग्रियों में मौजूद है।

स्टेज 3. तंत्रिका नेटवर्क प्रशिक्षण


एक तंत्रिका नेटवर्क के प्रशिक्षण के लिए, तंत्रिका नेटवर्क टूलबॉक्स का उपयोग किया गया था।
प्रशिक्षण समारोह नीचे वर्णित है:
एक तंत्रिका नेटवर्क का निर्माण और प्रशिक्षण
 function [net, alphabet, inpD, tarD] = train_NN(alphabet_dir, IM_W, IM_H, neuronC) %inputs: % alphabet_dir - path to directory with alphabet % IM_W - image width % IM_H - image height % neuronC - count of neuron in hidden layer %outputs: % net - trained net % alphabet - net alphabet % inpD - input data % tarD - target data % % Vadym Drozd drozdvadym@gmail.com dirList = dir([alphabet_dir '\']); dir_name = {dirList([dirList(:).isdir]).name}; alphabetSize = length(dir_name) - 2; try a = load([alphabet_dir '\' 'trainData_' num2str(IM_W) 'x' num2str(IM_H) '_.dat'], '-mat'); catch end try target = a.target; inpData = a.inpData; alphabet = a.alphabet; catch for i = 3 : length(dir_name) alphabet(i - 2) = dir_name{i}; imgList = dir([alphabet_dir '\' alphabet(i - 2) '\*.jpg']); t_tar = zeros(alphabetSize, length(imgList)); t_tar(i - 2,:) = 1; for j = 1 : length(imgList) im = imread([alphabet_dir '\' dir_name{i} '\' imgList(j).name]); im = imresize(im, [IM_H IM_W], 'lanczos3'); %resize image im = imadjust(im); im = double(im) /255.0; im = im(:); if i == 3 && j == 1 inpData = im; else inpData = [inpData im]; end end if i == 3 target = t_tar; else target = [target t_tar ]; end end end %create and train NN toc min_max = minmax(inpData); habrNN = newff(min_max, [IM_H * IM_W neuronC 23], {'logsig', 'tansig','logsig'}, 'trainrp'); habrNN.trainParam.min_grad = 10E-12; habrNN = train(habrNN, inpData, target); display('Training time:'); timeE = toc; display(timeE); net = habrNN; inpD = inpData; tarD= target; save([alphabet_dir '\' 'trainData_' num2str(IM_W) 'x' num2str(IM_H) '_.dat'], 'inpData', 'target', 'alphabet'); end 



निम्नलिखित तंत्रिका नेटवर्क वास्तुकला का चयन किया गया था:


तंत्रिका नेटवर्क के इनपुट पर प्राप्त छवियों का आकार 10 * 12 पिक्सेल है। जैसा कि आप जानते हैं, एक तंत्रिका नेटवर्क को प्रशिक्षित करना एक आसान काम नहीं है, क्योंकि यह तुरंत ज्ञात नहीं है कि नेटवर्क आर्किटेक्चर क्या होना चाहिए, प्रत्येक परत में न्यूरॉन्स की संख्या, और यह भी ज्ञात नहीं है कि नेटवर्क प्रशिक्षण कितने मिनीमा के लिए नीचे स्लाइड करेगा। इसलिए, प्रशिक्षण कई बार किया गया था, जिसके बाद सबसे अच्छे परिणामों में से एक को चुना गया था।

चरण 4. एल्गोरिथ्म का परीक्षण


एल्गोरिथ्म का परीक्षण करने के लिए, निम्न स्क्रिप्ट लिखी गई थी:

कैप्चा पहचान के लिए स्क्रिप्ट
 %% captchReader.m clear; close all; clc; cdir = './captch/'; fileList = dir([ cdir '\*.png']); load('49_67_net.mat'); load('alphabet.mat'); N_SYMB = 6; SYMB_W = 10; SYMB_H = 12; WIDTH = 166; %px HIGH = 75; %px SHOW_RECT = 1; %1 - show rects, else - don't show correct = 0; %correct recognized results correctSymbC = 0; allSymbC = 0; for CC = 1 : length(fileList) imName = fileList(CC).name; %open image [cdata, map] = imread( [cdir '\' imName] ); %change color mode if ~isempty( map ) cdata = ind2rgb( cdata, map ); end %resize image cdata = imresize(cdata, [HIGH WIDTH], 'lanczos3'); display(CC); if ( size( size(cdata), 2 ) > 2 ) cdata = imadjust(rgb2gray(cdata)); end rects = segmentator(cdata, N_SYMB, 0); if SHOW_RECT imshow(cdata); for i = 1:length(rects) colors = {'r', 'y', 'b', 'g', 'c', 'm'}; rectangle('Position', rects{i}, 'EdgeColor', colors{i}); end end if rects{1} == -1 continue; end %recognize recognized = zeros(1, N_SYMB); if length(rects) == N_SYMB for j = 1 : N_SYMB tim = imcrop(cdata, rects{j}); %resize image tim = imadjust(imresize(tim, [SYMB_H SYMB_W], 'lanczos3')); res = net(tim(:)); [sort_res, idx] = sort(res, 'descend'); recognized(j) = alphabet(idx(1)); end end correctSymbC = sum( (recognized - imName(1:6)) == 0); allSymbC = allSymbC + N_SYMB; if strcmp(recognized, imName(1:6)) correct = correct + 1; end if SHOW_RECT title(['Recognize: ' recognized]); end end fprintf('CAPTCH precision is: %2.2f %%', 100 * correct / length(fileList)); fprintf('Symbol precision: %2.2f %%', 100 * correctSymbC / allSymbC); 



मान्यता उदाहरण:








परिणामस्वरूप, निम्नलिखित परिणाम प्राप्त हुए:
सही ढंग से मान्यता प्राप्त कैप्चा की संख्या: 49.17%
सही ढंग से मान्यता प्राप्त वर्णों की संख्या: 87.02%

निष्कर्ष


जैसा कि यह निकला, कैप्चा बहुत जटिल नहीं है और आसानी से टूट सकता है। इसे जटिल करने के लिए, वर्णों के अधिक चौराहों और साथ ही साथ उनकी विभिन्न संख्याओं का उपयोग करना आवश्यक है।
मान्यता की वर्तमान गुणवत्ता में सुधार करने के लिए, निम्नलिखित सुधार किए जा सकते हैं:


अगर मुझे लेख पसंद आया, तो अगले में मैं आपको एक मानवीय चेहरे को पहचानने के बुनियादी तरीकों के बारे में विस्तार से बता सकता हूं।

प्रशिक्षण स्रोत


सूत्रों का कहना है। Dropboks
यैंडेक्स लोग

Source: https://habr.com/ru/post/In161159/


All Articles